From edff5e3be84b12da8b5b9866278491877093db8d Mon Sep 17 00:00:00 2001 From: tanxiaoyan Date: Sat, 5 Aug 2023 15:08:55 +0800 Subject: [PATCH 001/441] Update application info for activity record when application info changed Activity manager will schedule application info changed for process record and app thread When OMS#setEnabled is called. But the app process may be restarted for top-activity, it use application of the top activity which may not be updated. Bug:294624020 Change-Id: I7fac9fab11c6f62350b31e486dcea89268bd086b Signed-off-by: tanxiaoyan --- services/core/java/com/android/server/am/ProcessList.java | 1 + .../com/android/server/wm/WindowProcessController.java | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index bce31734d064..ed16f353363c 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -4698,6 +4698,7 @@ public final class ProcessList { if (ai != null) { if (ai.packageName.equals(app.info.packageName)) { app.info = ai; + app.getWindowProcessController().updateApplicationInfo(ai); PlatformCompatCache.getInstance() .onApplicationInfoChanged(ai); } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 1c10380c2a4c..9e9592ae6670 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -101,7 +101,7 @@ public class WindowProcessController extends ConfigurationContainer Date: Fri, 1 Dec 2023 07:25:10 +0000 Subject: [PATCH 002/441] [BugFix][MEM]Fix process memory data during dump Assign hasSwapPss to true as long as there is a process with swap pss Change-Id: Ia72167177c8721941df13dbd46cace3d92406fe6 BUG:314245234 --- .../java/com/android/server/am/ActivityManagerService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c0da30d0e153..544e08ff8d55 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -11826,7 +11826,7 @@ public class ActivityManagerService extends IActivityManager.Stub continue; } endTime = SystemClock.currentThreadTimeMillis(); - hasSwapPss = mi.hasSwappedOutPss; + hasSwapPss = hasSwapPss || mi.hasSwappedOutPss; memtrackGraphics = mi.getOtherPrivate(Debug.MemoryInfo.OTHER_GRAPHICS); memtrackGl = mi.getOtherPrivate(Debug.MemoryInfo.OTHER_GL); } else { @@ -12472,7 +12472,7 @@ public class ActivityManagerService extends IActivityManager.Stub continue; } endTime = SystemClock.currentThreadTimeMillis(); - hasSwapPss = mi.hasSwappedOutPss; + hasSwapPss = hasSwapPss || mi.hasSwappedOutPss; } else { reportType = ProcessStats.ADD_PSS_EXTERNAL; startTime = SystemClock.currentThreadTimeMillis(); -- GitLab From 2958d1748574d479dad8589f4356c8030339622d Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Tue, 23 Jul 2024 14:54:03 -0700 Subject: [PATCH 003/441] Make IMM#getCurrentInputMethod{Info,Subtype} consistent Since it was originall introduced [1], InputMethodManager#getCurrentInputMethodInfo() has been built by using Settings.Secure.DEFAULT_INPUT_METHOD as the source of truth when determinig the currently selected IME for the given user. On the other hand, InputMethodManager#getCurrentInputMethodSubtype() uses InputMethodBindingController#getCurrentInputMethodSubtype() as the source of truth when determing the currently selected subtype for the given user. With this CL #getCurrentInputMethodInfo() starts using InputMethodBindingController as the source of truth as well, because that's what InputMethodManagerService actually does when establishing a connection. The new behavior is guarged with a build time flag. [1]: I60a0f67bf7d261d3a4a733adcb8a022ceac6e1db 2422bcffe2351789dc80be68b6f6d5515dc0fa11 Bug: 355034523 Test: presubmit Flag: build.consistent_get_current_input_method_info Change-Id: I83716695094a58ee6ceab1a26c6d514c49cb4c15 --- core/java/android/view/inputmethod/flags.aconfig | 11 +++++++++++ .../server/inputmethod/InputMethodManagerService.java | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index e294ee2d3a91..cfaaf2b671f7 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -72,6 +72,17 @@ flag { } } +flag { + name: "consistent_get_current_input_method_info" + namespace: "input_method" + description: "Use BindingController as the source of truth in getCurrentInputMethodInfo" + bug: "355034523" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "ime_switcher_revamp" is_exported: true diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index eed34b86f744..c2b721944f16 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1470,7 +1470,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); - return settings.getMethodMap().get(settings.getSelectedInputMethod()); + final String selectedImeId; + if (Flags.consistentGetCurrentInputMethodInfo()) { + final var bindingController = getInputMethodBindingController(userId); + synchronized (ImfLock.class) { + selectedImeId = bindingController.getSelectedMethodId(); + } + } else { + selectedImeId = settings.getSelectedInputMethod(); + } + return settings.getMethodMap().get(selectedImeId); } @BinderThread -- GitLab From bf47618b933d526e7050546022445fbe5fe20bf2 Mon Sep 17 00:00:00 2001 From: wilsonshih Date: Fri, 30 Aug 2024 17:09:30 +0800 Subject: [PATCH 004/441] Add TaskSnapshot reference when used for Content suggestion. Add reference, so the hardware buffer of the snapshot won't get release too early. Flag: com.android.window.flags.release_snapshot_aggressively Bug: 238206323 Test: enter recents to check content suggestion several times without seeing crash. Change-Id: I622e471bfc0f40cf97653dd3ace56e29b49185f7 --- core/java/android/window/TaskSnapshot.java | 7 +++-- .../ContentSuggestionsManagerService.java | 9 +++++-- .../wm/ActivityTaskManagerInternal.java | 2 +- .../server/wm/ActivityTaskManagerService.java | 27 +++++++++++++++++-- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index 20d1b3bd12ae..a37bef80ff04 100644 --- a/core/java/android/window/TaskSnapshot.java +++ b/core/java/android/window/TaskSnapshot.java @@ -83,13 +83,16 @@ public class TaskSnapshot implements Parcelable { public static final int REFERENCE_CACHE = 1 << 1; /** This snapshot object is being persistent. */ public static final int REFERENCE_PERSIST = 1 << 2; + /** This snapshot object is being used for content suggestion. */ + public static final int REFERENCE_CONTENT_SUGGESTION = 1 << 3; @IntDef(flag = true, prefix = { "REFERENCE_" }, value = { REFERENCE_BROADCAST, REFERENCE_CACHE, - REFERENCE_PERSIST + REFERENCE_PERSIST, + REFERENCE_CONTENT_SUGGESTION }) @Retention(RetentionPolicy.SOURCE) - @interface ReferenceFlags {} + public @interface ReferenceFlags {} public TaskSnapshot(long id, long captureTime, @NonNull ComponentName topActivityComponent, HardwareBuffer snapshot, diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java index 57c643bb08a1..a7aab49cde56 100644 --- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java @@ -160,11 +160,13 @@ public class ContentSuggestionsManagerService extends HardwareBuffer snapshotBuffer = null; int colorSpaceId = 0; + TaskSnapshot snapshot = null; // Skip taking TaskSnapshot when bitmap is provided. if (!imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) { // Can block, so call before acquiring the lock. - TaskSnapshot snapshot = - mActivityTaskManagerInternal.getTaskSnapshotBlocking(taskId, false); + snapshot = mActivityTaskManagerInternal.getTaskSnapshotBlocking( + taskId, false /* isLowResolution */, + TaskSnapshot.REFERENCE_CONTENT_SUGGESTION); if (snapshot != null) { snapshotBuffer = snapshot.getHardwareBuffer(); ColorSpace colorSpace = snapshot.getColorSpace(); @@ -185,6 +187,9 @@ public class ContentSuggestionsManagerService extends } } } + if (snapshot != null) { + snapshot.removeReference(TaskSnapshot.REFERENCE_CONTENT_SUGGESTION); + } } @Override diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 26a6b00254d3..3e9c19f01a1a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -607,7 +607,7 @@ public abstract class ActivityTaskManagerInternal { * sensitive environment. */ public abstract TaskSnapshot getTaskSnapshotBlocking(int taskId, - boolean isLowResolution); + boolean isLowResolution, @TaskSnapshot.ReferenceFlags int usage); /** Returns true if uid is considered foreground for activity start purposes. */ public abstract boolean isUidForeground(int uid); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0f108c5ed5d7..eedc3b870bbf 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3913,6 +3913,28 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + private TaskSnapshot getTaskSnapshotInner(int taskId, boolean isLowResolution, + @TaskSnapshot.ReferenceFlags int usage) { + final Task task; + synchronized (mGlobalLock) { + task = mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS); + if (task == null) { + Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found"); + return null; + } + // Try to load snapshot from cache first, and add reference if the snapshot is in cache. + final TaskSnapshot snapshot = mWindowManager.mTaskSnapshotController.getSnapshot(taskId, + task.mUserId, false /* restoreFromDisk */, isLowResolution); + if (snapshot != null) { + snapshot.addReference(usage); + return snapshot; + } + } + // Don't call this while holding the lock as this operation might hit the disk. + return mWindowManager.mTaskSnapshotController.getSnapshot(taskId, + task.mUserId, true /* restoreFromDisk */, isLowResolution); + } + @Override public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) { mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()"); @@ -7235,8 +7257,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public TaskSnapshot getTaskSnapshotBlocking( - int taskId, boolean isLowResolution) { - return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution); + int taskId, boolean isLowResolution, @TaskSnapshot.ReferenceFlags int usage) { + return ActivityTaskManagerService.this.getTaskSnapshotInner( + taskId, isLowResolution, usage); } @Override -- GitLab From db7bdcc175039ba1c70a6affccf46a0d399ec2ba Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Fri, 23 Aug 2024 22:17:02 +0000 Subject: [PATCH 005/441] Update PowerHAL version Bug: 359965565 Bug: 355264141 Change-Id: Ia523a13227034db93478aa084cdc622ce752c2ca Test: n/a Flag: EXEMPT HAL interface change --- core/java/Android.bp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/java/Android.bp b/core/java/Android.bp index 92bca3cfbef2..b10281100b2b 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -176,6 +176,9 @@ filegroup { aidl_interface { name: "android.os.hintmanager_aidl", + defaults: [ + "android.hardware.power-aidl", + ], srcs: [ "android/os/IHintManager.aidl", "android/os/IHintSession.aidl", @@ -193,9 +196,6 @@ aidl_interface { enabled: true, }, }, - imports: [ - "android.hardware.power-V5", - ], } aidl_library { -- GitLab From 5deda323c2c33ef1320bcc224f7c2cf261ec8bdb Mon Sep 17 00:00:00 2001 From: rambowang Date: Wed, 4 Sep 2024 15:16:04 -0500 Subject: [PATCH 006/441] Clean up flag show_call_id_and_call_waiting_in_additional_settings_menu The flag has been advaced to next for a long while and no regression is observed. Clean it up for now to keep code healthy. Bug: 310264981 Test: atest FrameworkTelephonyTests and CtsTelephonyTestCases Flag: EXEMPT flag clean up Change-Id: Ia640b9bdb79d3e79908321aeef01811ab955e057 --- core/api/current.txt | 4 ++-- telephony/java/android/telephony/CarrierConfigManager.java | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index ddfd364cc55d..d88a8d79448a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -43724,8 +43724,8 @@ package android.telephony { field public static final String KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrq_thresholds_int_array"; field public static final String KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY = "5g_nr_sssinr_thresholds_int_array"; field public static final String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; - field @FlaggedApi("com.android.internal.telephony.flags.show_call_id_and_call_waiting_in_additional_settings_menu") public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool"; - field @FlaggedApi("com.android.internal.telephony.flags.show_call_id_and_call_waiting_in_additional_settings_menu") public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool"; + field public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool"; + field public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool"; field public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool"; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 13bd5eb67e44..502899fcb103 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -253,7 +253,6 @@ public class CarrierConfigManager { * * The default value is true. */ - @FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU) public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool"; @@ -263,7 +262,6 @@ public class CarrierConfigManager { * * The default value is true. */ - @FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU) public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool"; -- GitLab From 380ccd13878f0b818bc3a1d403b01597c9eaa9f1 Mon Sep 17 00:00:00 2001 From: Yining Liu Date: Mon, 16 Sep 2024 23:17:15 +0000 Subject: [PATCH 007/441] Add an aconfig flag for the new settings page for lock screen notifs Add an aconfig flag for the new settings page to manage the lock screen notifications. Bug: 367455695 Flag: com.android.server.notification.notification_lock_screen_settings Test: atest SystemUITests Change-Id: I89297ad183b5025e3d9b412e8057ef05f06fb36f --- .../java/com/android/server/notification/flags.aconfig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index be3adc142fa4..d70bdf594073 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -157,6 +157,13 @@ flag { bug: "336488844" } +flag { + name: "notification_lock_screen_settings" + namespace: "systemui" + description: "This flag enables the new settings page for the notifications on lock screen." + bug: "367455695" +} + flag { name: "notification_vibration_in_sound_uri" namespace: "systemui" -- GitLab From 2dbfd4f891c57b4c87842302017086bb56130f88 Mon Sep 17 00:00:00 2001 From: Rahul Banerjee Date: Mon, 16 Sep 2024 23:58:59 +0000 Subject: [PATCH 008/441] Add rahulbanerjee@ to bootanimation OWNERS Bug: 366034613 Change-Id: Icb8b13f9680bd4af3e2d0e7f40d172b0d52b77a8 --- cmds/bootanimation/OWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/cmds/bootanimation/OWNERS b/cmds/bootanimation/OWNERS index b6fb007bea52..2eda44dabec3 100644 --- a/cmds/bootanimation/OWNERS +++ b/cmds/bootanimation/OWNERS @@ -1,3 +1,4 @@ dupin@google.com shanh@google.com jreck@google.com +rahulbanerjee@google.com \ No newline at end of file -- GitLab From 10a403190c6b852ccd090e9e684bca9bba55c493 Mon Sep 17 00:00:00 2001 From: Abhishek Gadewar Date: Mon, 23 Sep 2024 23:56:03 -0700 Subject: [PATCH 009/441] Fix the wrong light doze state check Summary: There is a typo when checking the light doze state. Test: Build and load the OS. The change is pretty safe since the new value is the same as the old one. Change-Id: Ib999400cbe4c35fc02e39fa1775b1477ff30d957 Signed-off-by: Abhishek Gadewar --- .../service/java/com/android/server/DeviceIdleController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index c1894f0f795f..a37779e681fb 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -3568,7 +3568,7 @@ public class DeviceIdleController extends SystemService Slog.i(TAG, "becomeActiveLocked, reason=" + activeReason + ", changeLightIdle=" + changeLightIdle); } - if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) { + if (mState != STATE_ACTIVE || mLightState != LIGHT_STATE_ACTIVE) { moveToStateLocked(STATE_ACTIVE, activeReason); mInactiveTimeout = newInactiveTimeout; resetIdleManagementLocked(); -- GitLab From 966c7f2d0462f0adf9795d2f8c70a9fa8acb0aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Tue, 12 Mar 2024 16:51:58 +0100 Subject: [PATCH 010/441] Fix allowlist token issues 1) Don't accept enqueued notifications with an unexpected token. 2) Ensure allowlist token matches for all parceled and unparceled notifications (by only using the "root notification" one). 3) Simplify cookie usage in allowlist token serialization. 4) Ensure group summary (and any notifications added directly by NMS) have the correct token. Bug: 328254922 Bug: 305695605 Test: atest NotificationManagerServiceTest ParcelTest CloseSystemDialogsTest + manually Change-Id: I232e9b74eece745560ed2e762071b48984b3f176 Merged-In: I232e9b74eece745560ed2e762071b48984b3f176 --- core/java/android/app/Notification.java | 48 ++- core/java/android/os/Parcel.java | 22 ++ .../coretests/src/android/os/ParcelTest.java | 49 +++ .../NotificationManagerService.java | 18 +- .../NotificationManagerServiceTest.java | 327 ++++++++++++++++++ 5 files changed, 451 insertions(+), 13 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index ced35549769a..b1261ae719e6 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2598,8 +2598,11 @@ public class Notification implements Parcelable if (mAllowlistToken == null) { mAllowlistToken = processAllowlistToken; } - // Propagate this token to all pending intents that are unmarshalled from the parcel. - parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + // Propagate this token to all pending intents that are unmarshalled from the parcel, + // or keep the one we're already propagating, if that's the case. + if (!parcel.hasClassCookie(PendingIntent.class)) { + parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + } when = parcel.readLong(); creationTime = parcel.readLong(); @@ -3061,9 +3064,24 @@ public class Notification implements Parcelable PendingIntent.addOnMarshaledListener(addedListener); } try { - // IMPORTANT: Add marshaling code in writeToParcelImpl as we - // want to intercept all pending events written to the parcel. - writeToParcelImpl(parcel, flags); + boolean mustClearCookie = false; + if (!parcel.hasClassCookie(Notification.class)) { + // This is the "root" notification, and not an "inner" notification (including + // publicVersion or anything else that might be embedded in extras). So we want + // to use its token for every inner notification (might be null). + parcel.setClassCookie(Notification.class, mAllowlistToken); + mustClearCookie = true; + } + try { + // IMPORTANT: Add marshaling code in writeToParcelImpl as we + // want to intercept all pending events written to the parcel. + writeToParcelImpl(parcel, flags); + } finally { + if (mustClearCookie) { + parcel.removeClassCookie(Notification.class, mAllowlistToken); + } + } + synchronized (this) { // Must be written last! parcel.writeArraySet(allPendingIntents); @@ -3078,7 +3096,10 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); - parcel.writeStrongBinder(mAllowlistToken); + // Always use the same token as the root notification (might be null). + IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class); + parcel.writeStrongBinder(rootNotificationToken); + parcel.writeLong(when); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { @@ -3471,18 +3492,23 @@ public class Notification implements Parcelable * Sets the token used for background operations for the pending intents associated with this * notification. * - * This token is automatically set during deserialization for you, you usually won't need to - * call this unless you want to change the existing token, if any. + * Note: Should only be invoked by NotificationManagerService, since this is normally + * populated by unparceling (and also used there). Any other usage is suspect. * * @hide */ - public void clearAllowlistToken() { - mAllowlistToken = null; + public void overrideAllowlistToken(IBinder token) { + mAllowlistToken = token; if (publicVersion != null) { - publicVersion.clearAllowlistToken(); + publicVersion.overrideAllowlistToken(token); } } + /** @hide */ + public IBinder getAllowlistToken() { + return mAllowlistToken; + } + /** * @hide */ diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index e784c2669575..453aba34dbcf 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -815,6 +815,28 @@ public final class Parcel { return mClassCookies != null ? mClassCookies.get(clz) : null; } + /** @hide */ + public void removeClassCookie(Class clz, Object expectedCookie) { + if (mClassCookies != null) { + Object removedCookie = mClassCookies.remove(clz); + if (removedCookie != expectedCookie) { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but instead removed " + removedCookie); + } + } else { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but no cookies were present"); + } + } + + /** + * Whether {@link #setClassCookie} has been called with the specified {@code clz}. + * @hide + */ + public boolean hasClassCookie(Class clz) { + return mClassCookies != null && mClassCookies.containsKey(clz); + } + /** @hide */ public final void adoptClassCookies(Parcel from) { mClassCookies = from.mClassCookies; diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index e2fe87b4cfe3..9143f74ea054 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -16,18 +16,23 @@ package android.os; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; +import android.util.Log; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; + @Presubmit @RunWith(AndroidJUnit4.class) public class ParcelTest { @@ -246,4 +251,48 @@ public class ParcelTest { assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, -1, pB, iB, 0)); assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, 0, pB, -1, 0)); } + + @Test + public void testClassCookies() { + Parcel p = Parcel.obtain(); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + + p.setClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isTrue(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie"); + + p.removeClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null); + + p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie"); + p.recycle(); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); + } + + @Test + public void testClassCookies_removeUnexpected() { + Parcel p = Parcel.obtain(); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present")); + + p.setClassCookie(ParcelTest.class, "value"); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different")); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed + + p.recycle(); + } + + private static void assertLogsWtf(Runnable test) { + ArrayList wtfs = new ArrayList<>(); + Log.TerribleFailureHandler oldHandler = Log.setWtfHandler( + (tag, what, system) -> wtfs.add(what)); + try { + test.run(); + } finally { + Log.setWtfHandler(oldHandler); + } + assertThat(wtfs).hasSize(1); + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0e48c3f83bbe..bb1de5eada8b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -665,7 +665,7 @@ public class NotificationManagerService extends SystemService { private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); - private static final IBinder ALLOWLIST_TOKEN = new Binder(); + static final IBinder ALLOWLIST_TOKEN = new Binder(); protected RankingHandler mRankingHandler; private long mLastOverRateLogTime; private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; @@ -4477,7 +4477,7 @@ public class NotificationManagerService extends SystemService { // Remove background token before returning notification to untrusted app, this // ensures the app isn't able to perform background operations that are // associated with notification interactions. - notification.clearAllowlistToken(); + notification.overrideAllowlistToken(null); return new StatusBarNotification( sbn.getPackageName(), sbn.getOpPkg(), @@ -6736,6 +6736,15 @@ public class NotificationManagerService extends SystemService { + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId); } + IBinder allowlistToken = notification.getAllowlistToken(); + if (allowlistToken != null && allowlistToken != ALLOWLIST_TOKEN) { + throw new SecurityException( + "Unexpected allowlist token received from " + callingUid); + } + // allowlistToken is populated by unparceling, so it can be null if the notification was + // posted from inside system_server. Ensure it's the expected value. + notification.overrideAllowlistToken(ALLOWLIST_TOKEN); + checkRestrictedCategories(notification); // Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE, @@ -7800,6 +7809,11 @@ public class NotificationManagerService extends SystemService { */ private boolean enqueueNotification() { synchronized (mNotificationLock) { + // allowlistToken is populated by unparceling, so it will be absent if the + // EnqueueNotificationRunnable is created directly by NMS (as we do for group + // summaries) instead of via notify(). Fix that. + r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN); + final long snoozeAt = mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), 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 4deaea99f117..096478dfc000 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -309,6 +309,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private final int mUid = Binder.getCallingUid(); private final @UserIdInt int mUserId = UserHandle.getUserId(mUid); + private final UserHandle mUser = UserHandle.of(mUserId); + private final String mPkg = mContext.getPackageName(); private TestableNotificationManagerService mService; private INotificationManager mBinderService; @@ -12453,6 +12455,331 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old } + @Test + public void enqueueNotification_acceptsCorrectToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_acceptsNullToken_andPopulatesIt() throws RemoteException { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + assertThat(receivedWithoutParceling.getAllowlistToken()).isNull(); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedWithoutParceling, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_directlyThroughRunnable_populatesAllowlistToken() { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + NotificationRecord record = new NotificationRecord( + mContext, + new StatusBarNotification(mPkg, mPkg, 1, "tag", mUid, 44, receivedWithoutParceling, + mUser, "groupKey", 0), + mTestNotificationChannel); + assertThat(record.getNotification().getAllowlistToken()).isNull(); + + mWorkerHandler.post( + mService.new EnqueueNotificationRunnable(mUserId, record, false, + mPostNotificationTrackerFactory.newTracker(null))); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_rejectsOtherToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + sent.overrideAllowlistToken(new Binder()); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo(sent.getAllowlistToken()); + + assertThrows(SecurityException.class, () -> + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId)); + waitForIdle(); + + assertThat(mService.mNotificationList).isEmpty(); + } + + @Test + public void enqueueNotification_customParcelingWithFakeInnerToken_hasCorrectTokenInIntents() + throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .build(); + sentFromApp.publicVersion.overrideAllowlistToken(new Binder()); + + // Instead of using the normal parceling, assume the caller parcels it by hand, including a + // null token in the outer notification (as would be expected, and as is verified by + // enqueue) but trying to sneak in a different one in the inner notification, hoping it gets + // propagated to the PendingIntents. + Parcel parcelSentFromApp = Parcel.obtain(); + writeNotificationToParcelCustom(parcelSentFromApp, sentFromApp, new ArraySet<>( + Lists.newArrayList(sentFromApp.contentIntent, + sentFromApp.publicVersion.contentIntent))); + + // Use the unparceling as received in enqueueNotificationWithTag() + parcelSentFromApp.setDataPosition(0); + Notification receivedByNms = new Notification(parcelSentFromApp); + + // Verify that all the pendingIntents have the correct token. + assertThat(receivedByNms.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(receivedByNms.publicVersion.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + } + + /** + * Replicates the behavior of {@link Notification#writeToParcel} but excluding the + * "always use the same allowlist token as the root notification" parts. + */ + private static void writeNotificationToParcelCustom(Parcel parcel, Notification notif, + ArraySet allPendingIntents) { + int flags = 0; + parcel.writeInt(1); // version? + + parcel.writeStrongBinder(notif.getAllowlistToken()); + parcel.writeLong(notif.when); + parcel.writeLong(1234L); // notif.creationTime is private + if (notif.getSmallIcon() != null) { + parcel.writeInt(1); + notif.getSmallIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.number); + if (notif.contentIntent != null) { + parcel.writeInt(1); + notif.contentIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.deleteIntent != null) { + parcel.writeInt(1); + notif.deleteIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.tickerText != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.tickerText, parcel, flags); + } else { + parcel.writeInt(0); + } + if (notif.tickerView != null) { + parcel.writeInt(1); + notif.tickerView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.contentView != null) { + parcel.writeInt(1); + notif.contentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.getLargeIcon() != null) { + parcel.writeInt(1); + notif.getLargeIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.defaults); + parcel.writeInt(notif.flags); + + if (notif.sound != null) { + parcel.writeInt(1); + notif.sound.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.audioStreamType); + + if (notif.audioAttributes != null) { + parcel.writeInt(1); + notif.audioAttributes.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeLongArray(notif.vibrate); + parcel.writeInt(notif.ledARGB); + parcel.writeInt(notif.ledOnMS); + parcel.writeInt(notif.ledOffMS); + parcel.writeInt(notif.iconLevel); + + if (notif.fullScreenIntent != null) { + parcel.writeInt(1); + notif.fullScreenIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.priority); + + parcel.writeString8(notif.category); + + parcel.writeString8(notif.getGroup()); + + parcel.writeString8(notif.getSortKey()); + + parcel.writeBundle(notif.extras); // null ok + + parcel.writeTypedArray(notif.actions, 0); // null ok + + if (notif.bigContentView != null) { + parcel.writeInt(1); + notif.bigContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + if (notif.headsUpContentView != null) { + parcel.writeInt(1); + notif.headsUpContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.visibility); + + if (notif.publicVersion != null) { + parcel.writeInt(1); + writeNotificationToParcelCustom(parcel, notif.publicVersion, new ArraySet<>()); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.color); + + if (notif.getChannelId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getChannelId()); + } else { + parcel.writeInt(0); + } + parcel.writeLong(notif.getTimeoutAfter()); + + if (notif.getShortcutId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getShortcutId()); + } else { + parcel.writeInt(0); + } + + if (notif.getLocusId() != null) { + parcel.writeInt(1); + notif.getLocusId().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getBadgeIconType()); + + if (notif.getSettingsText() != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.getSettingsText(), parcel, flags); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getGroupAlertBehavior()); + + if (notif.getBubbleMetadata() != null) { + parcel.writeInt(1); + notif.getBubbleMetadata().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeBoolean(notif.getAllowSystemGeneratedContextualActions()); + + parcel.writeInt(Notification.FOREGROUND_SERVICE_DEFAULT); // no getter for mFgsDeferBehavior + + // mUsesStandardHeader is not written because it should be recomputed in listeners + + parcel.writeArraySet(allPendingIntents); + } + + @Test + @SuppressWarnings("unchecked") + public void getActiveNotifications_doesNotLeakAllowlistToken() throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .extend(new Notification.WearableExtender() + .addPage(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("wearPage")) + .build())) + .build(); + // Binder transition: app -> NMS + Notification receivedByNms = parcelAndUnparcel(sentFromApp, Notification.CREATOR); + assertThat(receivedByNms.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedByNms, Notification.CREATOR), mUserId); + waitForIdle(); + assertThat(mService.mNotificationList).hasSize(1); + Notification posted = mService.mNotificationList.get(0).getNotification(); + assertThat(posted.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(posted.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + ParceledListSlice listSentFromNms = + mBinderService.getAppActiveNotifications(mPkg, mUserId); + // Binder transition: NMS -> app. App doesn't have the allowlist token so clear it + // (having a different one would produce the same effect; the relevant thing is to not let + // out ALLOWLIST_TOKEN). + // Note: for other tests, this is restored by constructing TestableNMS in setup(). + Notification.processAllowlistToken = null; + ParceledListSlice listReceivedByApp = parcelAndUnparcel( + listSentFromNms, ParceledListSlice.CREATOR); + Notification gottenBackByApp = listReceivedByApp.getList().get(0).getNotification(); + + assertThat(gottenBackByApp.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.contentIntent.getWhitelistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.contentIntent.getWhitelistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).getAllowlistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).contentIntent.getWhitelistToken()).isNull(); + } + @Test public void enqueueNotification_allowlistsPendingIntents() throws RemoteException { PendingIntent contentIntent = createPendingIntent("content"); -- GitLab From 697c31d74c775da1ae0caa57839b4d2ddb83eba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Tue, 12 Mar 2024 16:51:58 +0100 Subject: [PATCH 011/441] Fix allowlist token issues 1) Don't accept enqueued notifications with an unexpected token. 2) Ensure allowlist token matches for all parceled and unparceled notifications (by only using the "root notification" one). 3) Simplify cookie usage in allowlist token serialization. 4) Ensure group summary (and any notifications added directly by NMS) have the correct token. Bug: 328254922 Bug: 305695605 Test: atest NotificationManagerServiceTest ParcelTest CloseSystemDialogsTest + manually Change-Id: I232e9b74eece745560ed2e762071b48984b3f176 Merged-In: I232e9b74eece745560ed2e762071b48984b3f176 --- core/java/android/app/Notification.java | 48 ++- core/java/android/os/Parcel.java | 22 ++ .../coretests/src/android/os/ParcelTest.java | 49 +++ .../NotificationManagerService.java | 18 +- .../NotificationManagerServiceTest.java | 349 +++++++++++++++++- 5 files changed, 470 insertions(+), 16 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 862b785183c7..dfbb7cc2eb85 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2598,8 +2598,11 @@ public class Notification implements Parcelable if (mAllowlistToken == null) { mAllowlistToken = processAllowlistToken; } - // Propagate this token to all pending intents that are unmarshalled from the parcel. - parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + // Propagate this token to all pending intents that are unmarshalled from the parcel, + // or keep the one we're already propagating, if that's the case. + if (!parcel.hasClassCookie(PendingIntent.class)) { + parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + } when = parcel.readLong(); creationTime = parcel.readLong(); @@ -3059,9 +3062,24 @@ public class Notification implements Parcelable }); } try { - // IMPORTANT: Add marshaling code in writeToParcelImpl as we - // want to intercept all pending events written to the parcel. - writeToParcelImpl(parcel, flags); + boolean mustClearCookie = false; + if (!parcel.hasClassCookie(Notification.class)) { + // This is the "root" notification, and not an "inner" notification (including + // publicVersion or anything else that might be embedded in extras). So we want + // to use its token for every inner notification (might be null). + parcel.setClassCookie(Notification.class, mAllowlistToken); + mustClearCookie = true; + } + try { + // IMPORTANT: Add marshaling code in writeToParcelImpl as we + // want to intercept all pending events written to the parcel. + writeToParcelImpl(parcel, flags); + } finally { + if (mustClearCookie) { + parcel.removeClassCookie(Notification.class, mAllowlistToken); + } + } + synchronized (this) { // Must be written last! parcel.writeArraySet(allPendingIntents); @@ -3076,7 +3094,10 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); - parcel.writeStrongBinder(mAllowlistToken); + // Always use the same token as the root notification (might be null). + IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class); + parcel.writeStrongBinder(rootNotificationToken); + parcel.writeLong(when); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { @@ -3469,18 +3490,23 @@ public class Notification implements Parcelable * Sets the token used for background operations for the pending intents associated with this * notification. * - * This token is automatically set during deserialization for you, you usually won't need to - * call this unless you want to change the existing token, if any. + * Note: Should only be invoked by NotificationManagerService, since this is normally + * populated by unparceling (and also used there). Any other usage is suspect. * * @hide */ - public void clearAllowlistToken() { - mAllowlistToken = null; + public void overrideAllowlistToken(IBinder token) { + mAllowlistToken = token; if (publicVersion != null) { - publicVersion.clearAllowlistToken(); + publicVersion.overrideAllowlistToken(token); } } + /** @hide */ + public IBinder getAllowlistToken() { + return mAllowlistToken; + } + /** * @hide */ diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index e784c2669575..453aba34dbcf 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -815,6 +815,28 @@ public final class Parcel { return mClassCookies != null ? mClassCookies.get(clz) : null; } + /** @hide */ + public void removeClassCookie(Class clz, Object expectedCookie) { + if (mClassCookies != null) { + Object removedCookie = mClassCookies.remove(clz); + if (removedCookie != expectedCookie) { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but instead removed " + removedCookie); + } + } else { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but no cookies were present"); + } + } + + /** + * Whether {@link #setClassCookie} has been called with the specified {@code clz}. + * @hide + */ + public boolean hasClassCookie(Class clz) { + return mClassCookies != null && mClassCookies.containsKey(clz); + } + /** @hide */ public final void adoptClassCookies(Parcel from) { mClassCookies = from.mClassCookies; diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index e2fe87b4cfe3..9143f74ea054 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -16,18 +16,23 @@ package android.os; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; +import android.util.Log; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; + @Presubmit @RunWith(AndroidJUnit4.class) public class ParcelTest { @@ -246,4 +251,48 @@ public class ParcelTest { assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, -1, pB, iB, 0)); assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, 0, pB, -1, 0)); } + + @Test + public void testClassCookies() { + Parcel p = Parcel.obtain(); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + + p.setClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isTrue(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie"); + + p.removeClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null); + + p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie"); + p.recycle(); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); + } + + @Test + public void testClassCookies_removeUnexpected() { + Parcel p = Parcel.obtain(); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present")); + + p.setClassCookie(ParcelTest.class, "value"); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different")); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed + + p.recycle(); + } + + private static void assertLogsWtf(Runnable test) { + ArrayList wtfs = new ArrayList<>(); + Log.TerribleFailureHandler oldHandler = Log.setWtfHandler( + (tag, what, system) -> wtfs.add(what)); + try { + test.run(); + } finally { + Log.setWtfHandler(oldHandler); + } + assertThat(wtfs).hasSize(1); + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index cf67ef399092..b62fd616d299 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -667,7 +667,7 @@ public class NotificationManagerService extends SystemService { private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); - private static final IBinder ALLOWLIST_TOKEN = new Binder(); + static final IBinder ALLOWLIST_TOKEN = new Binder(); protected RankingHandler mRankingHandler; private long mLastOverRateLogTime; private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; @@ -4472,7 +4472,7 @@ public class NotificationManagerService extends SystemService { // Remove background token before returning notification to untrusted app, this // ensures the app isn't able to perform background operations that are // associated with notification interactions. - notification.clearAllowlistToken(); + notification.overrideAllowlistToken(null); return new StatusBarNotification( sbn.getPackageName(), sbn.getOpPkg(), @@ -6713,6 +6713,15 @@ public class NotificationManagerService extends SystemService { + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId); } + IBinder allowlistToken = notification.getAllowlistToken(); + if (allowlistToken != null && allowlistToken != ALLOWLIST_TOKEN) { + throw new SecurityException( + "Unexpected allowlist token received from " + callingUid); + } + // allowlistToken is populated by unparceling, so it can be null if the notification was + // posted from inside system_server. Ensure it's the expected value. + notification.overrideAllowlistToken(ALLOWLIST_TOKEN); + checkRestrictedCategories(notification); // Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE, @@ -7776,6 +7785,11 @@ public class NotificationManagerService extends SystemService { */ private boolean enqueueNotification() { synchronized (mNotificationLock) { + // allowlistToken is populated by unparceling, so it will be absent if the + // EnqueueNotificationRunnable is created directly by NMS (as we do for group + // summaries) instead of via notify(). Fix that. + r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN); + final long snoozeAt = mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), 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 59b20bba7390..f203cfa9e442 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -71,6 +71,7 @@ import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -186,6 +187,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Parcel; +import android.os.Parcelable; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.Process; @@ -302,6 +304,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private final int mUid = Binder.getCallingUid(); private final @UserIdInt int mUserId = UserHandle.getUserId(mUid); + private final UserHandle mUser = UserHandle.of(mUserId); + private final String mPkg = mContext.getPackageName(); private TestableNotificationManagerService mService; private INotificationManager mBinderService; @@ -2288,7 +2292,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true, - notif.getUserId(), 0, null); + notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -3026,7 +3030,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, - Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null); + Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -3054,7 +3058,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true, - notif.getUserId(), 0, null); + notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -12244,6 +12248,345 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertFalse(n.isUserInitiatedJob()); } + @Test + public void enqueueNotification_acceptsCorrectToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_acceptsNullToken_andPopulatesIt() throws RemoteException { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + assertThat(receivedWithoutParceling.getAllowlistToken()).isNull(); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedWithoutParceling, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_directlyThroughRunnable_populatesAllowlistToken() { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + NotificationRecord record = new NotificationRecord( + mContext, + new StatusBarNotification(mPkg, mPkg, 1, "tag", mUid, 44, receivedWithoutParceling, + mUser, "groupKey", 0), + mTestNotificationChannel); + assertThat(record.getNotification().getAllowlistToken()).isNull(); + + mWorkerHandler.post( + mService.new EnqueueNotificationRunnable(mUserId, record, false, + mPostNotificationTrackerFactory.newTracker(null))); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_rejectsOtherToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + sent.overrideAllowlistToken(new Binder()); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo(sent.getAllowlistToken()); + + assertThrows(SecurityException.class, () -> + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId)); + waitForIdle(); + + assertThat(mService.mNotificationList).isEmpty(); + } + + @Test + public void enqueueNotification_customParcelingWithFakeInnerToken_hasCorrectTokenInIntents() + throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .build(); + sentFromApp.publicVersion.overrideAllowlistToken(new Binder()); + + // Instead of using the normal parceling, assume the caller parcels it by hand, including a + // null token in the outer notification (as would be expected, and as is verified by + // enqueue) but trying to sneak in a different one in the inner notification, hoping it gets + // propagated to the PendingIntents. + Parcel parcelSentFromApp = Parcel.obtain(); + writeNotificationToParcelCustom(parcelSentFromApp, sentFromApp, new ArraySet<>( + Lists.newArrayList(sentFromApp.contentIntent, + sentFromApp.publicVersion.contentIntent))); + + // Use the unparceling as received in enqueueNotificationWithTag() + parcelSentFromApp.setDataPosition(0); + Notification receivedByNms = new Notification(parcelSentFromApp); + + // Verify that all the pendingIntents have the correct token. + assertThat(receivedByNms.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(receivedByNms.publicVersion.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + } + + /** + * Replicates the behavior of {@link Notification#writeToParcel} but excluding the + * "always use the same allowlist token as the root notification" parts. + */ + private static void writeNotificationToParcelCustom(Parcel parcel, Notification notif, + ArraySet allPendingIntents) { + int flags = 0; + parcel.writeInt(1); // version? + + parcel.writeStrongBinder(notif.getAllowlistToken()); + parcel.writeLong(notif.when); + parcel.writeLong(1234L); // notif.creationTime is private + if (notif.getSmallIcon() != null) { + parcel.writeInt(1); + notif.getSmallIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.number); + if (notif.contentIntent != null) { + parcel.writeInt(1); + notif.contentIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.deleteIntent != null) { + parcel.writeInt(1); + notif.deleteIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.tickerText != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.tickerText, parcel, flags); + } else { + parcel.writeInt(0); + } + if (notif.tickerView != null) { + parcel.writeInt(1); + notif.tickerView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.contentView != null) { + parcel.writeInt(1); + notif.contentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.getLargeIcon() != null) { + parcel.writeInt(1); + notif.getLargeIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.defaults); + parcel.writeInt(notif.flags); + + if (notif.sound != null) { + parcel.writeInt(1); + notif.sound.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.audioStreamType); + + if (notif.audioAttributes != null) { + parcel.writeInt(1); + notif.audioAttributes.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeLongArray(notif.vibrate); + parcel.writeInt(notif.ledARGB); + parcel.writeInt(notif.ledOnMS); + parcel.writeInt(notif.ledOffMS); + parcel.writeInt(notif.iconLevel); + + if (notif.fullScreenIntent != null) { + parcel.writeInt(1); + notif.fullScreenIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.priority); + + parcel.writeString8(notif.category); + + parcel.writeString8(notif.getGroup()); + + parcel.writeString8(notif.getSortKey()); + + parcel.writeBundle(notif.extras); // null ok + + parcel.writeTypedArray(notif.actions, 0); // null ok + + if (notif.bigContentView != null) { + parcel.writeInt(1); + notif.bigContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + if (notif.headsUpContentView != null) { + parcel.writeInt(1); + notif.headsUpContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.visibility); + + if (notif.publicVersion != null) { + parcel.writeInt(1); + writeNotificationToParcelCustom(parcel, notif.publicVersion, new ArraySet<>()); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.color); + + if (notif.getChannelId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getChannelId()); + } else { + parcel.writeInt(0); + } + parcel.writeLong(notif.getTimeoutAfter()); + + if (notif.getShortcutId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getShortcutId()); + } else { + parcel.writeInt(0); + } + + if (notif.getLocusId() != null) { + parcel.writeInt(1); + notif.getLocusId().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getBadgeIconType()); + + if (notif.getSettingsText() != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.getSettingsText(), parcel, flags); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getGroupAlertBehavior()); + + if (notif.getBubbleMetadata() != null) { + parcel.writeInt(1); + notif.getBubbleMetadata().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeBoolean(notif.getAllowSystemGeneratedContextualActions()); + + parcel.writeInt(Notification.FOREGROUND_SERVICE_DEFAULT); // no getter for mFgsDeferBehavior + + // mUsesStandardHeader is not written because it should be recomputed in listeners + + parcel.writeArraySet(allPendingIntents); + } + + @Test + @SuppressWarnings("unchecked") + public void getActiveNotifications_doesNotLeakAllowlistToken() throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .extend(new Notification.WearableExtender() + .addPage(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("wearPage")) + .build())) + .build(); + // Binder transition: app -> NMS + Notification receivedByNms = parcelAndUnparcel(sentFromApp, Notification.CREATOR); + assertThat(receivedByNms.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedByNms, Notification.CREATOR), mUserId); + waitForIdle(); + assertThat(mService.mNotificationList).hasSize(1); + Notification posted = mService.mNotificationList.get(0).getNotification(); + assertThat(posted.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(posted.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + ParceledListSlice listSentFromNms = + mBinderService.getAppActiveNotifications(mPkg, mUserId); + // Binder transition: NMS -> app. App doesn't have the allowlist token so clear it + // (having a different one would produce the same effect; the relevant thing is to not let + // out ALLOWLIST_TOKEN). + // Note: for other tests, this is restored by constructing TestableNMS in setup(). + Notification.processAllowlistToken = null; + ParceledListSlice listReceivedByApp = parcelAndUnparcel( + listSentFromNms, ParceledListSlice.CREATOR); + Notification gottenBackByApp = listReceivedByApp.getList().get(0).getNotification(); + + assertThat(gottenBackByApp.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.contentIntent.getWhitelistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.contentIntent.getWhitelistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).getAllowlistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).contentIntent.getWhitelistToken()).isNull(); + } + + private static T parcelAndUnparcel(T source, + Parcelable.Creator creator) { + Parcel parcel = Parcel.obtain(); + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return creator.createFromParcel(parcel); + } + + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, + new Intent(action).setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + } + private void setDpmAppOppsExemptFromDismissal(boolean isOn) { DeviceConfig.setProperty( DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, -- GitLab From 46aafeb8a6c00bd52d66507c33238197f81c8a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Tue, 12 Mar 2024 16:51:58 +0100 Subject: [PATCH 012/441] Fix allowlist token issues 1) Don't accept enqueued notifications with an unexpected token. 2) Ensure allowlist token matches for all parceled and unparceled notifications (by only using the "root notification" one). 3) Simplify cookie usage in allowlist token serialization. 4) Ensure group summary (and any notifications added directly by NMS) have the correct token. Bug: 328254922 Bug: 305695605 Test: atest NotificationManagerServiceTest ParcelTest CloseSystemDialogsTest + manually Change-Id: I232e9b74eece745560ed2e762071b48984b3f176 Merged-In: I232e9b74eece745560ed2e762071b48984b3f176 --- core/java/android/app/Notification.java | 48 ++- core/java/android/os/Parcel.java | 22 ++ .../coretests/src/android/os/ParcelTest.java | 49 +++ .../NotificationManagerService.java | 18 +- .../NotificationManagerServiceTest.java | 354 +++++++++++++++++- 5 files changed, 474 insertions(+), 17 deletions(-) mode change 100755 => 100644 services/core/java/com/android/server/notification/NotificationManagerService.java diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 9c3aa6e47788..f0c766fb7010 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2565,8 +2565,11 @@ public class Notification implements Parcelable if (mAllowlistToken == null) { mAllowlistToken = processAllowlistToken; } - // Propagate this token to all pending intents that are unmarshalled from the parcel. - parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + // Propagate this token to all pending intents that are unmarshalled from the parcel, + // or keep the one we're already propagating, if that's the case. + if (!parcel.hasClassCookie(PendingIntent.class)) { + parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + } when = parcel.readLong(); creationTime = parcel.readLong(); @@ -3027,9 +3030,24 @@ public class Notification implements Parcelable }); } try { - // IMPORTANT: Add marshaling code in writeToParcelImpl as we - // want to intercept all pending events written to the parcel. - writeToParcelImpl(parcel, flags); + boolean mustClearCookie = false; + if (!parcel.hasClassCookie(Notification.class)) { + // This is the "root" notification, and not an "inner" notification (including + // publicVersion or anything else that might be embedded in extras). So we want + // to use its token for every inner notification (might be null). + parcel.setClassCookie(Notification.class, mAllowlistToken); + mustClearCookie = true; + } + try { + // IMPORTANT: Add marshaling code in writeToParcelImpl as we + // want to intercept all pending events written to the parcel. + writeToParcelImpl(parcel, flags); + } finally { + if (mustClearCookie) { + parcel.removeClassCookie(Notification.class, mAllowlistToken); + } + } + synchronized (this) { // Must be written last! parcel.writeArraySet(allPendingIntents); @@ -3044,7 +3062,10 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); - parcel.writeStrongBinder(mAllowlistToken); + // Always use the same token as the root notification (might be null). + IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class); + parcel.writeStrongBinder(rootNotificationToken); + parcel.writeLong(when); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { @@ -3400,18 +3421,23 @@ public class Notification implements Parcelable * Sets the token used for background operations for the pending intents associated with this * notification. * - * This token is automatically set during deserialization for you, you usually won't need to - * call this unless you want to change the existing token, if any. + * Note: Should only be invoked by NotificationManagerService, since this is normally + * populated by unparceling (and also used there). Any other usage is suspect. * * @hide */ - public void clearAllowlistToken() { - mAllowlistToken = null; + public void overrideAllowlistToken(IBinder token) { + mAllowlistToken = token; if (publicVersion != null) { - publicVersion.clearAllowlistToken(); + publicVersion.overrideAllowlistToken(token); } } + /** @hide */ + public IBinder getAllowlistToken() { + return mAllowlistToken; + } + /** * @hide */ diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index a7349f9e473e..4bdef89154c2 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -782,6 +782,28 @@ public final class Parcel { return mClassCookies != null ? mClassCookies.get(clz) : null; } + /** @hide */ + public void removeClassCookie(Class clz, Object expectedCookie) { + if (mClassCookies != null) { + Object removedCookie = mClassCookies.remove(clz); + if (removedCookie != expectedCookie) { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but instead removed " + removedCookie); + } + } else { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but no cookies were present"); + } + } + + /** + * Whether {@link #setClassCookie} has been called with the specified {@code clz}. + * @hide + */ + public boolean hasClassCookie(Class clz) { + return mClassCookies != null && mClassCookies.containsKey(clz); + } + /** @hide */ public final void adoptClassCookies(Parcel from) { mClassCookies = from.mClassCookies; diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index fdd278b9c621..29d533c6eb7a 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -16,18 +16,23 @@ package android.os; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; +import android.util.Log; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; + @Presubmit @RunWith(AndroidJUnit4.class) public class ParcelTest { @@ -239,4 +244,48 @@ public class ParcelTest { assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, -1, pB, iB, 0)); assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, 0, pB, -1, 0)); } + + @Test + public void testClassCookies() { + Parcel p = Parcel.obtain(); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + + p.setClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isTrue(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie"); + + p.removeClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null); + + p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie"); + p.recycle(); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); + } + + @Test + public void testClassCookies_removeUnexpected() { + Parcel p = Parcel.obtain(); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present")); + + p.setClassCookie(ParcelTest.class, "value"); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different")); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed + + p.recycle(); + } + + private static void assertLogsWtf(Runnable test) { + ArrayList wtfs = new ArrayList<>(); + Log.TerribleFailureHandler oldHandler = Log.setWtfHandler( + (tag, what, system) -> wtfs.add(what)); + try { + test.run(); + } finally { + Log.setWtfHandler(oldHandler); + } + assertThat(wtfs).hasSize(1); + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java old mode 100755 new mode 100644 index 3b48fcb71abd..e61b73c857f9 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -643,7 +643,7 @@ public class NotificationManagerService extends SystemService { private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); - private static final IBinder ALLOWLIST_TOKEN = new Binder(); + static final IBinder ALLOWLIST_TOKEN = new Binder(); protected RankingHandler mRankingHandler; private long mLastOverRateLogTime; private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; @@ -4396,7 +4396,7 @@ public class NotificationManagerService extends SystemService { // Remove background token before returning notification to untrusted app, this // ensures the app isn't able to perform background operations that are // associated with notification interactions. - notification.clearAllowlistToken(); + notification.overrideAllowlistToken(null); return new StatusBarNotification( sbn.getPackageName(), sbn.getOpPkg(), @@ -6558,6 +6558,15 @@ public class NotificationManagerService extends SystemService { + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId); } + IBinder allowlistToken = notification.getAllowlistToken(); + if (allowlistToken != null && allowlistToken != ALLOWLIST_TOKEN) { + throw new SecurityException( + "Unexpected allowlist token received from " + callingUid); + } + // allowlistToken is populated by unparceling, so it can be null if the notification was + // posted from inside system_server. Ensure it's the expected value. + notification.overrideAllowlistToken(ALLOWLIST_TOKEN); + checkRestrictedCategories(notification); // Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE, @@ -7478,6 +7487,11 @@ public class NotificationManagerService extends SystemService { @Override public void run() { synchronized (mNotificationLock) { + // allowlistToken is populated by unparceling, so it will be absent if the + // EnqueueNotificationRunnable is created directly by NMS (as we do for group + // summaries) instead of via notify(). Fix that. + r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN); + final Long snoozeAt = mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), 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 755bc1d35cf3..810c94d9e7ee 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -68,6 +68,7 @@ import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -113,6 +114,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import android.annotation.SuppressLint; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -167,6 +169,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Parcel; +import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -230,6 +233,7 @@ import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import org.junit.After; import org.junit.Assert; @@ -268,7 +272,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private static final int TEST_TASK_ID = 1; private static final int UID_HEADLESS = 1000000; + private TestableContext mContext = spy(getContext()); private final int mUid = Binder.getCallingUid(); + private final @UserIdInt int mUserId = UserHandle.getUserId(mUid); + private final UserHandle mUser = UserHandle.of(mUserId); + private final String mPkg = mContext.getPackageName(); + private TestableNotificationManagerService mService; private INotificationManager mBinderService; private NotificationManagerInternal mInternalService; @@ -286,7 +295,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock private PermissionHelper mPermissionHelper; private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake(); - private TestableContext mContext = spy(getContext()); private final String PKG = mContext.getPackageName(); private TestableLooper mTestableLooper; @Mock @@ -2023,7 +2031,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true, - notif.getUserId(), 0, null); + notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -2753,7 +2761,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, - Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null); + Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -2781,7 +2789,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true, - notif.getUserId(), 0, null); + notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -10623,4 +10631,342 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertFalse(n.isForegroundService()); assertFalse(n.hasColorizedPermission()); } + + @Test + public void enqueueNotification_acceptsCorrectToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_acceptsNullToken_andPopulatesIt() throws RemoteException { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + assertThat(receivedWithoutParceling.getAllowlistToken()).isNull(); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedWithoutParceling, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_directlyThroughRunnable_populatesAllowlistToken() { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + NotificationRecord record = new NotificationRecord( + mContext, + new StatusBarNotification(mPkg, mPkg, 1, "tag", mUid, 44, receivedWithoutParceling, + mUser, "groupKey", 0), + mTestNotificationChannel); + assertThat(record.getNotification().getAllowlistToken()).isNull(); + + mWorkerHandler.post( + mService.new EnqueueNotificationRunnable(mUserId, record, false, 0)); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_rejectsOtherToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + sent.overrideAllowlistToken(new Binder()); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo(sent.getAllowlistToken()); + + assertThrows(SecurityException.class, () -> + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId)); + waitForIdle(); + + assertThat(mService.mNotificationList).isEmpty(); + } + + @Test + public void enqueueNotification_customParcelingWithFakeInnerToken_hasCorrectTokenInIntents() + throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .build(); + sentFromApp.publicVersion.overrideAllowlistToken(new Binder()); + + // Instead of using the normal parceling, assume the caller parcels it by hand, including a + // null token in the outer notification (as would be expected, and as is verified by + // enqueue) but trying to sneak in a different one in the inner notification, hoping it gets + // propagated to the PendingIntents. + Parcel parcelSentFromApp = Parcel.obtain(); + writeNotificationToParcelCustom(parcelSentFromApp, sentFromApp, new ArraySet<>( + Lists.newArrayList(sentFromApp.contentIntent, + sentFromApp.publicVersion.contentIntent))); + + // Use the unparceling as received in enqueueNotificationWithTag() + parcelSentFromApp.setDataPosition(0); + Notification receivedByNms = new Notification(parcelSentFromApp); + + // Verify that all the pendingIntents have the correct token. + assertThat(receivedByNms.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(receivedByNms.publicVersion.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + } + + /** + * Replicates the behavior of {@link Notification#writeToParcel} but excluding the + * "always use the same allowlist token as the root notification" parts. + */ + private static void writeNotificationToParcelCustom(Parcel parcel, Notification notif, + ArraySet allPendingIntents) { + int flags = 0; + parcel.writeInt(1); // version? + + parcel.writeStrongBinder(notif.getAllowlistToken()); + parcel.writeLong(notif.when); + parcel.writeLong(1234L); // notif.creationTime is private + if (notif.getSmallIcon() != null) { + parcel.writeInt(1); + notif.getSmallIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.number); + if (notif.contentIntent != null) { + parcel.writeInt(1); + notif.contentIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.deleteIntent != null) { + parcel.writeInt(1); + notif.deleteIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.tickerText != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.tickerText, parcel, flags); + } else { + parcel.writeInt(0); + } + if (notif.tickerView != null) { + parcel.writeInt(1); + notif.tickerView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.contentView != null) { + parcel.writeInt(1); + notif.contentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.getLargeIcon() != null) { + parcel.writeInt(1); + notif.getLargeIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.defaults); + parcel.writeInt(notif.flags); + + if (notif.sound != null) { + parcel.writeInt(1); + notif.sound.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.audioStreamType); + + if (notif.audioAttributes != null) { + parcel.writeInt(1); + notif.audioAttributes.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeLongArray(notif.vibrate); + parcel.writeInt(notif.ledARGB); + parcel.writeInt(notif.ledOnMS); + parcel.writeInt(notif.ledOffMS); + parcel.writeInt(notif.iconLevel); + + if (notif.fullScreenIntent != null) { + parcel.writeInt(1); + notif.fullScreenIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.priority); + + parcel.writeString8(notif.category); + + parcel.writeString8(notif.getGroup()); + + parcel.writeString8(notif.getSortKey()); + + parcel.writeBundle(notif.extras); // null ok + + parcel.writeTypedArray(notif.actions, 0); // null ok + + if (notif.bigContentView != null) { + parcel.writeInt(1); + notif.bigContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + if (notif.headsUpContentView != null) { + parcel.writeInt(1); + notif.headsUpContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.visibility); + + if (notif.publicVersion != null) { + parcel.writeInt(1); + writeNotificationToParcelCustom(parcel, notif.publicVersion, new ArraySet<>()); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.color); + + if (notif.getChannelId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getChannelId()); + } else { + parcel.writeInt(0); + } + parcel.writeLong(notif.getTimeoutAfter()); + + if (notif.getShortcutId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getShortcutId()); + } else { + parcel.writeInt(0); + } + + if (notif.getLocusId() != null) { + parcel.writeInt(1); + notif.getLocusId().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getBadgeIconType()); + + if (notif.getSettingsText() != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.getSettingsText(), parcel, flags); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getGroupAlertBehavior()); + + if (notif.getBubbleMetadata() != null) { + parcel.writeInt(1); + notif.getBubbleMetadata().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeBoolean(notif.getAllowSystemGeneratedContextualActions()); + + parcel.writeInt(Notification.FOREGROUND_SERVICE_DEFAULT); // no getter for mFgsDeferBehavior + + // mUsesStandardHeader is not written because it should be recomputed in listeners + + parcel.writeArraySet(allPendingIntents); + } + + @Test + @SuppressWarnings("unchecked") + public void getActiveNotifications_doesNotLeakAllowlistToken() throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .extend(new Notification.WearableExtender() + .addPage(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("wearPage")) + .build())) + .build(); + // Binder transition: app -> NMS + Notification receivedByNms = parcelAndUnparcel(sentFromApp, Notification.CREATOR); + assertThat(receivedByNms.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedByNms, Notification.CREATOR), mUserId); + waitForIdle(); + assertThat(mService.mNotificationList).hasSize(1); + Notification posted = mService.mNotificationList.get(0).getNotification(); + assertThat(posted.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(posted.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + ParceledListSlice listSentFromNms = + mBinderService.getAppActiveNotifications(mPkg, mUserId); + // Binder transition: NMS -> app. App doesn't have the allowlist token so clear it + // (having a different one would produce the same effect; the relevant thing is to not let + // out ALLOWLIST_TOKEN). + // Note: for other tests, this is restored by constructing TestableNMS in setup(). + Notification.processAllowlistToken = null; + ParceledListSlice listReceivedByApp = parcelAndUnparcel( + listSentFromNms, ParceledListSlice.CREATOR); + Notification gottenBackByApp = listReceivedByApp.getList().get(0).getNotification(); + + assertThat(gottenBackByApp.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.contentIntent.getWhitelistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.contentIntent.getWhitelistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).getAllowlistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).contentIntent.getWhitelistToken()).isNull(); + } + + private static T parcelAndUnparcel(T source, + Parcelable.Creator creator) { + Parcel parcel = Parcel.obtain(); + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return creator.createFromParcel(parcel); + } + + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, + new Intent(action).setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + } } -- GitLab From 61274fc61fb33a0ae9525188d969cb56503c124a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Tue, 12 Mar 2024 16:51:58 +0100 Subject: [PATCH 013/441] Fix allowlist token issues 1) Don't accept enqueued notifications with an unexpected token. 2) Ensure allowlist token matches for all parceled and unparceled notifications (by only using the "root notification" one). 3) Simplify cookie usage in allowlist token serialization. 4) Ensure group summary (and any notifications added directly by NMS) have the correct token. Bug: 328254922 Bug: 305695605 Test: atest NotificationManagerServiceTest ParcelTest CloseSystemDialogsTest + manually Change-Id: I232e9b74eece745560ed2e762071b48984b3f176 Merged-In: I232e9b74eece745560ed2e762071b48984b3f176 --- core/java/android/app/Notification.java | 48 ++- core/java/android/os/Parcel.java | 22 ++ .../coretests/src/android/os/ParcelTest.java | 49 +++ .../NotificationManagerService.java | 18 +- .../NotificationManagerServiceTest.java | 354 +++++++++++++++++- 5 files changed, 474 insertions(+), 17 deletions(-) mode change 100755 => 100644 services/core/java/com/android/server/notification/NotificationManagerService.java diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 787d920ba992..c7d406f46605 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2565,8 +2565,11 @@ public class Notification implements Parcelable if (mAllowlistToken == null) { mAllowlistToken = processAllowlistToken; } - // Propagate this token to all pending intents that are unmarshalled from the parcel. - parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + // Propagate this token to all pending intents that are unmarshalled from the parcel, + // or keep the one we're already propagating, if that's the case. + if (!parcel.hasClassCookie(PendingIntent.class)) { + parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + } when = parcel.readLong(); creationTime = parcel.readLong(); @@ -3027,9 +3030,24 @@ public class Notification implements Parcelable }); } try { - // IMPORTANT: Add marshaling code in writeToParcelImpl as we - // want to intercept all pending events written to the parcel. - writeToParcelImpl(parcel, flags); + boolean mustClearCookie = false; + if (!parcel.hasClassCookie(Notification.class)) { + // This is the "root" notification, and not an "inner" notification (including + // publicVersion or anything else that might be embedded in extras). So we want + // to use its token for every inner notification (might be null). + parcel.setClassCookie(Notification.class, mAllowlistToken); + mustClearCookie = true; + } + try { + // IMPORTANT: Add marshaling code in writeToParcelImpl as we + // want to intercept all pending events written to the parcel. + writeToParcelImpl(parcel, flags); + } finally { + if (mustClearCookie) { + parcel.removeClassCookie(Notification.class, mAllowlistToken); + } + } + synchronized (this) { // Must be written last! parcel.writeArraySet(allPendingIntents); @@ -3044,7 +3062,10 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); - parcel.writeStrongBinder(mAllowlistToken); + // Always use the same token as the root notification (might be null). + IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class); + parcel.writeStrongBinder(rootNotificationToken); + parcel.writeLong(when); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { @@ -3400,18 +3421,23 @@ public class Notification implements Parcelable * Sets the token used for background operations for the pending intents associated with this * notification. * - * This token is automatically set during deserialization for you, you usually won't need to - * call this unless you want to change the existing token, if any. + * Note: Should only be invoked by NotificationManagerService, since this is normally + * populated by unparceling (and also used there). Any other usage is suspect. * * @hide */ - public void clearAllowlistToken() { - mAllowlistToken = null; + public void overrideAllowlistToken(IBinder token) { + mAllowlistToken = token; if (publicVersion != null) { - publicVersion.clearAllowlistToken(); + publicVersion.overrideAllowlistToken(token); } } + /** @hide */ + public IBinder getAllowlistToken() { + return mAllowlistToken; + } + /** * @hide */ diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index a7349f9e473e..4bdef89154c2 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -782,6 +782,28 @@ public final class Parcel { return mClassCookies != null ? mClassCookies.get(clz) : null; } + /** @hide */ + public void removeClassCookie(Class clz, Object expectedCookie) { + if (mClassCookies != null) { + Object removedCookie = mClassCookies.remove(clz); + if (removedCookie != expectedCookie) { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but instead removed " + removedCookie); + } + } else { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but no cookies were present"); + } + } + + /** + * Whether {@link #setClassCookie} has been called with the specified {@code clz}. + * @hide + */ + public boolean hasClassCookie(Class clz) { + return mClassCookies != null && mClassCookies.containsKey(clz); + } + /** @hide */ public final void adoptClassCookies(Parcel from) { mClassCookies = from.mClassCookies; diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index fdd278b9c621..29d533c6eb7a 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -16,18 +16,23 @@ package android.os; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; +import android.util.Log; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; + @Presubmit @RunWith(AndroidJUnit4.class) public class ParcelTest { @@ -239,4 +244,48 @@ public class ParcelTest { assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, -1, pB, iB, 0)); assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, 0, pB, -1, 0)); } + + @Test + public void testClassCookies() { + Parcel p = Parcel.obtain(); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + + p.setClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isTrue(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie"); + + p.removeClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null); + + p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie"); + p.recycle(); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); + } + + @Test + public void testClassCookies_removeUnexpected() { + Parcel p = Parcel.obtain(); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present")); + + p.setClassCookie(ParcelTest.class, "value"); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different")); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed + + p.recycle(); + } + + private static void assertLogsWtf(Runnable test) { + ArrayList wtfs = new ArrayList<>(); + Log.TerribleFailureHandler oldHandler = Log.setWtfHandler( + (tag, what, system) -> wtfs.add(what)); + try { + test.run(); + } finally { + Log.setWtfHandler(oldHandler); + } + assertThat(wtfs).hasSize(1); + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java old mode 100755 new mode 100644 index 2562d88f8996..af7032095546 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -640,7 +640,7 @@ public class NotificationManagerService extends SystemService { private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); - private static final IBinder ALLOWLIST_TOKEN = new Binder(); + static final IBinder ALLOWLIST_TOKEN = new Binder(); protected RankingHandler mRankingHandler; private long mLastOverRateLogTime; private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; @@ -4369,7 +4369,7 @@ public class NotificationManagerService extends SystemService { // Remove background token before returning notification to untrusted app, this // ensures the app isn't able to perform background operations that are // associated with notification interactions. - notification.clearAllowlistToken(); + notification.overrideAllowlistToken(null); return new StatusBarNotification( sbn.getPackageName(), sbn.getOpPkg(), @@ -6498,6 +6498,15 @@ public class NotificationManagerService extends SystemService { + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId); } + IBinder allowlistToken = notification.getAllowlistToken(); + if (allowlistToken != null && allowlistToken != ALLOWLIST_TOKEN) { + throw new SecurityException( + "Unexpected allowlist token received from " + callingUid); + } + // allowlistToken is populated by unparceling, so it can be null if the notification was + // posted from inside system_server. Ensure it's the expected value. + notification.overrideAllowlistToken(ALLOWLIST_TOKEN); + checkRestrictedCategories(notification); // Fix the notification as best we can. @@ -7349,6 +7358,11 @@ public class NotificationManagerService extends SystemService { @Override public void run() { synchronized (mNotificationLock) { + // allowlistToken is populated by unparceling, so it will be absent if the + // EnqueueNotificationRunnable is created directly by NMS (as we do for group + // summaries) instead of via notify(). Fix that. + r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN); + final Long snoozeAt = mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), 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 62adeefb8671..14e9539f0f66 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -63,6 +63,7 @@ import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -108,6 +109,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import android.annotation.SuppressLint; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -161,6 +163,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Parcel; +import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -223,6 +226,7 @@ import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import org.junit.After; import org.junit.Assert; @@ -261,7 +265,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private static final int TEST_TASK_ID = 1; private static final int UID_HEADLESS = 1000000; + private TestableContext mContext = spy(getContext()); private final int mUid = Binder.getCallingUid(); + private final @UserIdInt int mUserId = UserHandle.getUserId(mUid); + private final UserHandle mUser = UserHandle.of(mUserId); + private final String mPkg = mContext.getPackageName(); + private TestableNotificationManagerService mService; private INotificationManager mBinderService; private NotificationManagerInternal mInternalService; @@ -279,7 +288,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock private PermissionHelper mPermissionHelper; private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake(); - private TestableContext mContext = spy(getContext()); private final String PKG = mContext.getPackageName(); private TestableLooper mTestableLooper; @Mock @@ -1936,7 +1944,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true, - notif.getUserId(), 0, null); + notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -2633,7 +2641,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, - Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null); + Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -2661,7 +2669,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true, - notif.getUserId(), 0, null); + notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -10284,4 +10292,342 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mInternalService.sendReviewPermissionsNotification(); verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class)); } + + @Test + public void enqueueNotification_acceptsCorrectToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_acceptsNullToken_andPopulatesIt() throws RemoteException { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + assertThat(receivedWithoutParceling.getAllowlistToken()).isNull(); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedWithoutParceling, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_directlyThroughRunnable_populatesAllowlistToken() { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + NotificationRecord record = new NotificationRecord( + mContext, + new StatusBarNotification(mPkg, mPkg, 1, "tag", mUid, 44, receivedWithoutParceling, + mUser, "groupKey", 0), + mTestNotificationChannel); + assertThat(record.getNotification().getAllowlistToken()).isNull(); + + mWorkerHandler.post( + mService.new EnqueueNotificationRunnable(mUserId, record, false, 0)); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_rejectsOtherToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + sent.overrideAllowlistToken(new Binder()); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo(sent.getAllowlistToken()); + + assertThrows(SecurityException.class, () -> + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId)); + waitForIdle(); + + assertThat(mService.mNotificationList).isEmpty(); + } + + @Test + public void enqueueNotification_customParcelingWithFakeInnerToken_hasCorrectTokenInIntents() + throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .build(); + sentFromApp.publicVersion.overrideAllowlistToken(new Binder()); + + // Instead of using the normal parceling, assume the caller parcels it by hand, including a + // null token in the outer notification (as would be expected, and as is verified by + // enqueue) but trying to sneak in a different one in the inner notification, hoping it gets + // propagated to the PendingIntents. + Parcel parcelSentFromApp = Parcel.obtain(); + writeNotificationToParcelCustom(parcelSentFromApp, sentFromApp, new ArraySet<>( + Lists.newArrayList(sentFromApp.contentIntent, + sentFromApp.publicVersion.contentIntent))); + + // Use the unparceling as received in enqueueNotificationWithTag() + parcelSentFromApp.setDataPosition(0); + Notification receivedByNms = new Notification(parcelSentFromApp); + + // Verify that all the pendingIntents have the correct token. + assertThat(receivedByNms.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(receivedByNms.publicVersion.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + } + + /** + * Replicates the behavior of {@link Notification#writeToParcel} but excluding the + * "always use the same allowlist token as the root notification" parts. + */ + private static void writeNotificationToParcelCustom(Parcel parcel, Notification notif, + ArraySet allPendingIntents) { + int flags = 0; + parcel.writeInt(1); // version? + + parcel.writeStrongBinder(notif.getAllowlistToken()); + parcel.writeLong(notif.when); + parcel.writeLong(1234L); // notif.creationTime is private + if (notif.getSmallIcon() != null) { + parcel.writeInt(1); + notif.getSmallIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.number); + if (notif.contentIntent != null) { + parcel.writeInt(1); + notif.contentIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.deleteIntent != null) { + parcel.writeInt(1); + notif.deleteIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.tickerText != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.tickerText, parcel, flags); + } else { + parcel.writeInt(0); + } + if (notif.tickerView != null) { + parcel.writeInt(1); + notif.tickerView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.contentView != null) { + parcel.writeInt(1); + notif.contentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.getLargeIcon() != null) { + parcel.writeInt(1); + notif.getLargeIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.defaults); + parcel.writeInt(notif.flags); + + if (notif.sound != null) { + parcel.writeInt(1); + notif.sound.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.audioStreamType); + + if (notif.audioAttributes != null) { + parcel.writeInt(1); + notif.audioAttributes.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeLongArray(notif.vibrate); + parcel.writeInt(notif.ledARGB); + parcel.writeInt(notif.ledOnMS); + parcel.writeInt(notif.ledOffMS); + parcel.writeInt(notif.iconLevel); + + if (notif.fullScreenIntent != null) { + parcel.writeInt(1); + notif.fullScreenIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.priority); + + parcel.writeString8(notif.category); + + parcel.writeString8(notif.getGroup()); + + parcel.writeString8(notif.getSortKey()); + + parcel.writeBundle(notif.extras); // null ok + + parcel.writeTypedArray(notif.actions, 0); // null ok + + if (notif.bigContentView != null) { + parcel.writeInt(1); + notif.bigContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + if (notif.headsUpContentView != null) { + parcel.writeInt(1); + notif.headsUpContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.visibility); + + if (notif.publicVersion != null) { + parcel.writeInt(1); + writeNotificationToParcelCustom(parcel, notif.publicVersion, new ArraySet<>()); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.color); + + if (notif.getChannelId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getChannelId()); + } else { + parcel.writeInt(0); + } + parcel.writeLong(notif.getTimeoutAfter()); + + if (notif.getShortcutId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getShortcutId()); + } else { + parcel.writeInt(0); + } + + if (notif.getLocusId() != null) { + parcel.writeInt(1); + notif.getLocusId().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getBadgeIconType()); + + if (notif.getSettingsText() != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.getSettingsText(), parcel, flags); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getGroupAlertBehavior()); + + if (notif.getBubbleMetadata() != null) { + parcel.writeInt(1); + notif.getBubbleMetadata().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeBoolean(notif.getAllowSystemGeneratedContextualActions()); + + parcel.writeInt(Notification.FOREGROUND_SERVICE_DEFAULT); // no getter for mFgsDeferBehavior + + // mUsesStandardHeader is not written because it should be recomputed in listeners + + parcel.writeArraySet(allPendingIntents); + } + + @Test + @SuppressWarnings("unchecked") + public void getActiveNotifications_doesNotLeakAllowlistToken() throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .extend(new Notification.WearableExtender() + .addPage(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("wearPage")) + .build())) + .build(); + // Binder transition: app -> NMS + Notification receivedByNms = parcelAndUnparcel(sentFromApp, Notification.CREATOR); + assertThat(receivedByNms.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedByNms, Notification.CREATOR), mUserId); + waitForIdle(); + assertThat(mService.mNotificationList).hasSize(1); + Notification posted = mService.mNotificationList.get(0).getNotification(); + assertThat(posted.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(posted.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + ParceledListSlice listSentFromNms = + mBinderService.getAppActiveNotifications(mPkg, mUserId); + // Binder transition: NMS -> app. App doesn't have the allowlist token so clear it + // (having a different one would produce the same effect; the relevant thing is to not let + // out ALLOWLIST_TOKEN). + // Note: for other tests, this is restored by constructing TestableNMS in setup(). + Notification.processAllowlistToken = null; + ParceledListSlice listReceivedByApp = parcelAndUnparcel( + listSentFromNms, ParceledListSlice.CREATOR); + Notification gottenBackByApp = listReceivedByApp.getList().get(0).getNotification(); + + assertThat(gottenBackByApp.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.contentIntent.getWhitelistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.contentIntent.getWhitelistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).getAllowlistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).contentIntent.getWhitelistToken()).isNull(); + } + + private static T parcelAndUnparcel(T source, + Parcelable.Creator creator) { + Parcel parcel = Parcel.obtain(); + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return creator.createFromParcel(parcel); + } + + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, + new Intent(action).setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + } } -- GitLab From 492865ebcad4480562cb4f07c4b4e079af75fdf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Tue, 12 Mar 2024 16:51:58 +0100 Subject: [PATCH 014/441] Fix allowlist token issues 1) Don't accept enqueued notifications with an unexpected token. 2) Ensure allowlist token matches for all parceled and unparceled notifications (by only using the "root notification" one). 3) Simplify cookie usage in allowlist token serialization. 4) Ensure group summary (and any notifications added directly by NMS) have the correct token. Bug: 328254922 Bug: 305695605 Test: atest NotificationManagerServiceTest ParcelTest CloseSystemDialogsTest + manually Change-Id: I232e9b74eece745560ed2e762071b48984b3f176 Merged-In: I232e9b74eece745560ed2e762071b48984b3f176 --- core/java/android/app/Notification.java | 48 ++- core/java/android/os/Parcel.java | 22 ++ .../coretests/src/android/os/ParcelTest.java | 49 +++ .../NotificationManagerService.java | 18 +- .../NotificationManagerServiceTest.java | 354 +++++++++++++++++- 5 files changed, 474 insertions(+), 17 deletions(-) mode change 100755 => 100644 services/core/java/com/android/server/notification/NotificationManagerService.java diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 3aeb363b627b..fcf0f7407cb9 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2511,8 +2511,11 @@ public class Notification implements Parcelable if (mAllowlistToken == null) { mAllowlistToken = processAllowlistToken; } - // Propagate this token to all pending intents that are unmarshalled from the parcel. - parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + // Propagate this token to all pending intents that are unmarshalled from the parcel, + // or keep the one we're already propagating, if that's the case. + if (!parcel.hasClassCookie(PendingIntent.class)) { + parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + } when = parcel.readLong(); creationTime = parcel.readLong(); @@ -2969,9 +2972,24 @@ public class Notification implements Parcelable }); } try { - // IMPORTANT: Add marshaling code in writeToParcelImpl as we - // want to intercept all pending events written to the parcel. - writeToParcelImpl(parcel, flags); + boolean mustClearCookie = false; + if (!parcel.hasClassCookie(Notification.class)) { + // This is the "root" notification, and not an "inner" notification (including + // publicVersion or anything else that might be embedded in extras). So we want + // to use its token for every inner notification (might be null). + parcel.setClassCookie(Notification.class, mAllowlistToken); + mustClearCookie = true; + } + try { + // IMPORTANT: Add marshaling code in writeToParcelImpl as we + // want to intercept all pending events written to the parcel. + writeToParcelImpl(parcel, flags); + } finally { + if (mustClearCookie) { + parcel.removeClassCookie(Notification.class, mAllowlistToken); + } + } + synchronized (this) { // Must be written last! parcel.writeArraySet(allPendingIntents); @@ -2986,7 +3004,10 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); - parcel.writeStrongBinder(mAllowlistToken); + // Always use the same token as the root notification (might be null). + IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class); + parcel.writeStrongBinder(rootNotificationToken); + parcel.writeLong(when); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { @@ -3342,18 +3363,23 @@ public class Notification implements Parcelable * Sets the token used for background operations for the pending intents associated with this * notification. * - * This token is automatically set during deserialization for you, you usually won't need to - * call this unless you want to change the existing token, if any. + * Note: Should only be invoked by NotificationManagerService, since this is normally + * populated by unparceling (and also used there). Any other usage is suspect. * * @hide */ - public void clearAllowlistToken() { - mAllowlistToken = null; + public void overrideAllowlistToken(IBinder token) { + mAllowlistToken = token; if (publicVersion != null) { - publicVersion.clearAllowlistToken(); + publicVersion.overrideAllowlistToken(token); } } + /** @hide */ + public IBinder getAllowlistToken() { + return mAllowlistToken; + } + /** * @hide */ diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index d0a77a031c99..403a5be7b0ea 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -651,6 +651,28 @@ public final class Parcel { return mClassCookies != null ? mClassCookies.get(clz) : null; } + /** @hide */ + public void removeClassCookie(Class clz, Object expectedCookie) { + if (mClassCookies != null) { + Object removedCookie = mClassCookies.remove(clz); + if (removedCookie != expectedCookie) { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but instead removed " + removedCookie); + } + } else { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but no cookies were present"); + } + } + + /** + * Whether {@link #setClassCookie} has been called with the specified {@code clz}. + * @hide + */ + public boolean hasClassCookie(Class clz) { + return mClassCookies != null && mClassCookies.containsKey(clz); + } + /** @hide */ public final void adoptClassCookies(Parcel from) { mClassCookies = from.mClassCookies; diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index dcb3e2f23da8..a21ea30a4b29 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -16,15 +16,20 @@ package android.os; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; +import android.util.Log; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; + @Presubmit @RunWith(AndroidJUnit4.class) public class ParcelTest { @@ -110,4 +115,48 @@ public class ParcelTest { assertEquals(string, p.readString16()); } } + + @Test + public void testClassCookies() { + Parcel p = Parcel.obtain(); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + + p.setClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isTrue(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie"); + + p.removeClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null); + + p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie"); + p.recycle(); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); + } + + @Test + public void testClassCookies_removeUnexpected() { + Parcel p = Parcel.obtain(); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present")); + + p.setClassCookie(ParcelTest.class, "value"); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different")); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed + + p.recycle(); + } + + private static void assertLogsWtf(Runnable test) { + ArrayList wtfs = new ArrayList<>(); + Log.TerribleFailureHandler oldHandler = Log.setWtfHandler( + (tag, what, system) -> wtfs.add(what)); + try { + test.run(); + } finally { + Log.setWtfHandler(oldHandler); + } + assertThat(wtfs).hasSize(1); + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java old mode 100755 new mode 100644 index c3dc2d8f2adc..cad4beb575d3 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -592,7 +592,7 @@ public class NotificationManagerService extends SystemService { private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); - private static final IBinder ALLOWLIST_TOKEN = new Binder(); + static final IBinder ALLOWLIST_TOKEN = new Binder(); protected RankingHandler mRankingHandler; private long mLastOverRateLogTime; private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; @@ -4347,7 +4347,7 @@ public class NotificationManagerService extends SystemService { // Remove background token before returning notification to untrusted app, this // ensures the app isn't able to perform background operations that are // associated with notification interactions. - notification.clearAllowlistToken(); + notification.overrideAllowlistToken(null); return new StatusBarNotification( sbn.getPackageName(), sbn.getOpPkg(), @@ -6336,6 +6336,15 @@ public class NotificationManagerService extends SystemService { + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId); } + IBinder allowlistToken = notification.getAllowlistToken(); + if (allowlistToken != null && allowlistToken != ALLOWLIST_TOKEN) { + throw new SecurityException( + "Unexpected allowlist token received from " + callingUid); + } + // allowlistToken is populated by unparceling, so it can be null if the notification was + // posted from inside system_server. Ensure it's the expected value. + notification.overrideAllowlistToken(ALLOWLIST_TOKEN); + checkRestrictedCategories(notification); // Fix the notification as best we can. @@ -7080,6 +7089,11 @@ public class NotificationManagerService extends SystemService { @Override public void run() { synchronized (mNotificationLock) { + // allowlistToken is populated by unparceling, so it will be absent if the + // EnqueueNotificationRunnable is created directly by NMS (as we do for group + // summaries) instead of via notify(). Fix that. + r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN); + final Long snoozeAt = mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), 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 1f786c0e1fb5..76c5b1aa3c55 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -58,6 +58,7 @@ import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -101,6 +102,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -151,6 +153,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Parcel; +import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -210,6 +213,7 @@ import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import org.junit.After; import org.junit.Before; @@ -243,7 +247,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; private static final int UID_HEADLESS = 1000000; + private TestableContext mContext = spy(getContext()); private final int mUid = Binder.getCallingUid(); + private final @UserIdInt int mUserId = UserHandle.getUserId(mUid); + private final UserHandle mUser = UserHandle.of(mUserId); + private final String mPkg = mContext.getPackageName(); + private TestableNotificationManagerService mService; private INotificationManager mBinderService; private NotificationManagerInternal mInternalService; @@ -254,7 +263,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private PackageManager mPackageManagerClient; @Mock private WindowManagerInternal mWindowManagerInternal; - private TestableContext mContext = spy(getContext()); private final String PKG = mContext.getPackageName(); private TestableLooper mTestableLooper; @Mock @@ -1827,7 +1835,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true, - notif.getUserId(), 0, null); + notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -2072,7 +2080,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, - Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null); + Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -2125,7 +2133,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true, - notif.getUserId(), 0, null); + notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -8899,4 +8907,342 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nru = mService.makeRankingUpdateLocked(null); assertEquals(2, nru.getRankingMap().getOrderedKeys().length); } + + @Test + public void enqueueNotification_acceptsCorrectToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_acceptsNullToken_andPopulatesIt() throws RemoteException { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + assertThat(receivedWithoutParceling.getAllowlistToken()).isNull(); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedWithoutParceling, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_directlyThroughRunnable_populatesAllowlistToken() { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + NotificationRecord record = new NotificationRecord( + mContext, + new StatusBarNotification(mPkg, mPkg, 1, "tag", mUid, 44, receivedWithoutParceling, + mUser, "groupKey", 0), + mTestNotificationChannel); + assertThat(record.getNotification().getAllowlistToken()).isNull(); + + mWorkerHandler.post( + mService.new EnqueueNotificationRunnable(mUserId, record, false)); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_rejectsOtherToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + sent.overrideAllowlistToken(new Binder()); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo(sent.getAllowlistToken()); + + assertThrows(SecurityException.class, () -> + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId)); + waitForIdle(); + + assertThat(mService.mNotificationList).isEmpty(); + } + + @Test + public void enqueueNotification_customParcelingWithFakeInnerToken_hasCorrectTokenInIntents() + throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .build(); + sentFromApp.publicVersion.overrideAllowlistToken(new Binder()); + + // Instead of using the normal parceling, assume the caller parcels it by hand, including a + // null token in the outer notification (as would be expected, and as is verified by + // enqueue) but trying to sneak in a different one in the inner notification, hoping it gets + // propagated to the PendingIntents. + Parcel parcelSentFromApp = Parcel.obtain(); + writeNotificationToParcelCustom(parcelSentFromApp, sentFromApp, new ArraySet<>( + Lists.newArrayList(sentFromApp.contentIntent, + sentFromApp.publicVersion.contentIntent))); + + // Use the unparceling as received in enqueueNotificationWithTag() + parcelSentFromApp.setDataPosition(0); + Notification receivedByNms = new Notification(parcelSentFromApp); + + // Verify that all the pendingIntents have the correct token. + assertThat(receivedByNms.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(receivedByNms.publicVersion.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + } + + /** + * Replicates the behavior of {@link Notification#writeToParcel} but excluding the + * "always use the same allowlist token as the root notification" parts. + */ + private static void writeNotificationToParcelCustom(Parcel parcel, Notification notif, + ArraySet allPendingIntents) { + int flags = 0; + parcel.writeInt(1); // version? + + parcel.writeStrongBinder(notif.getAllowlistToken()); + parcel.writeLong(notif.when); + parcel.writeLong(1234L); // notif.creationTime is private + if (notif.getSmallIcon() != null) { + parcel.writeInt(1); + notif.getSmallIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.number); + if (notif.contentIntent != null) { + parcel.writeInt(1); + notif.contentIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.deleteIntent != null) { + parcel.writeInt(1); + notif.deleteIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.tickerText != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.tickerText, parcel, flags); + } else { + parcel.writeInt(0); + } + if (notif.tickerView != null) { + parcel.writeInt(1); + notif.tickerView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.contentView != null) { + parcel.writeInt(1); + notif.contentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.getLargeIcon() != null) { + parcel.writeInt(1); + notif.getLargeIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.defaults); + parcel.writeInt(notif.flags); + + if (notif.sound != null) { + parcel.writeInt(1); + notif.sound.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.audioStreamType); + + if (notif.audioAttributes != null) { + parcel.writeInt(1); + notif.audioAttributes.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeLongArray(notif.vibrate); + parcel.writeInt(notif.ledARGB); + parcel.writeInt(notif.ledOnMS); + parcel.writeInt(notif.ledOffMS); + parcel.writeInt(notif.iconLevel); + + if (notif.fullScreenIntent != null) { + parcel.writeInt(1); + notif.fullScreenIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.priority); + + parcel.writeString8(notif.category); + + parcel.writeString8(notif.getGroup()); + + parcel.writeString8(notif.getSortKey()); + + parcel.writeBundle(notif.extras); // null ok + + parcel.writeTypedArray(notif.actions, 0); // null ok + + if (notif.bigContentView != null) { + parcel.writeInt(1); + notif.bigContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + if (notif.headsUpContentView != null) { + parcel.writeInt(1); + notif.headsUpContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.visibility); + + if (notif.publicVersion != null) { + parcel.writeInt(1); + writeNotificationToParcelCustom(parcel, notif.publicVersion, new ArraySet<>()); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.color); + + if (notif.getChannelId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getChannelId()); + } else { + parcel.writeInt(0); + } + parcel.writeLong(notif.getTimeoutAfter()); + + if (notif.getShortcutId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getShortcutId()); + } else { + parcel.writeInt(0); + } + + if (notif.getLocusId() != null) { + parcel.writeInt(1); + notif.getLocusId().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getBadgeIconType()); + + if (notif.getSettingsText() != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.getSettingsText(), parcel, flags); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getGroupAlertBehavior()); + + if (notif.getBubbleMetadata() != null) { + parcel.writeInt(1); + notif.getBubbleMetadata().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeBoolean(notif.getAllowSystemGeneratedContextualActions()); + + parcel.writeInt(Notification.FOREGROUND_SERVICE_DEFAULT); // no getter for mFgsDeferBehavior + + // mUsesStandardHeader is not written because it should be recomputed in listeners + + parcel.writeArraySet(allPendingIntents); + } + + @Test + @SuppressWarnings("unchecked") + public void getActiveNotifications_doesNotLeakAllowlistToken() throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .extend(new Notification.WearableExtender() + .addPage(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("wearPage")) + .build())) + .build(); + // Binder transition: app -> NMS + Notification receivedByNms = parcelAndUnparcel(sentFromApp, Notification.CREATOR); + assertThat(receivedByNms.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedByNms, Notification.CREATOR), mUserId); + waitForIdle(); + assertThat(mService.mNotificationList).hasSize(1); + Notification posted = mService.mNotificationList.get(0).getNotification(); + assertThat(posted.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(posted.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + ParceledListSlice listSentFromNms = + mBinderService.getAppActiveNotifications(mPkg, mUserId); + // Binder transition: NMS -> app. App doesn't have the allowlist token so clear it + // (having a different one would produce the same effect; the relevant thing is to not let + // out ALLOWLIST_TOKEN). + // Note: for other tests, this is restored by constructing TestableNMS in setup(). + Notification.processAllowlistToken = null; + ParceledListSlice listReceivedByApp = parcelAndUnparcel( + listSentFromNms, ParceledListSlice.CREATOR); + Notification gottenBackByApp = listReceivedByApp.getList().get(0).getNotification(); + + assertThat(gottenBackByApp.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.contentIntent.getWhitelistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.contentIntent.getWhitelistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).getAllowlistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).contentIntent.getWhitelistToken()).isNull(); + } + + private static T parcelAndUnparcel(T source, + Parcelable.Creator creator) { + Parcel parcel = Parcel.obtain(); + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return creator.createFromParcel(parcel); + } + + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, + new Intent(action).setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + } } -- GitLab From 72b969e7c120b9b489ca02207ca3bcdc654d5d5d Mon Sep 17 00:00:00 2001 From: Ivan Chiang Date: Wed, 25 Sep 2024 05:51:54 +0000 Subject: [PATCH 015/441] [PM] Support update-ownership for Archived app Flag: android.content.pm.archiving Test: atest UpdateOwnershipEnforcementTest Test: atest ArchiveTest Test: atest PackageInstallerArchiveTest Bug: 348131934 Bug: 369235011 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6354d49da990bad6b17e363237a47dd24c403a3e) Merged-In: Idd019e3735859cdf80a77c0f547984fa9a8e6197 Change-Id: Idd019e3735859cdf80a77c0f547984fa9a8e6197 --- .../android/packageinstaller/PackageInstallerActivity.java | 3 ++- .../java/com/android/server/pm/PackageInstallerSession.java | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index e0398aa49dc9..824dd4a5fdaf 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -330,7 +330,8 @@ public class PackageInstallerActivity extends Activity { // data we still want to count it as "installed". mAppInfo = mPm.getApplicationInfo(pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES); - if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { + // If the package is archived, treat it as update case. + if (!mAppInfo.isArchived && (mAppInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { mAppInfo = null; } } catch (NameNotFoundException e) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 00e9d8d595f1..47a79a3c4051 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1060,7 +1060,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isInstallDpcPackagesPermissionGranted = (snapshot.checkUidPermission( android.Manifest.permission.INSTALL_DPC_PACKAGES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); - final int targetPackageUid = snapshot.getPackageUid(packageName, 0, userId); + // Also query the package uid for archived packages, so that the user confirmation + // dialog can be displayed for updating archived apps. + final int targetPackageUid = snapshot.getPackageUid(packageName, + PackageManager.MATCH_ARCHIVED_PACKAGES, userId); final boolean isUpdate = targetPackageUid != -1 || isApexSession(); final InstallSourceInfo existingInstallSourceInfo = isUpdate ? snapshot.getInstallSourceInfo(packageName, userId) -- GitLab From 5d107bcde72620926566613c5efa7f0c4ce9f63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Tue, 12 Mar 2024 16:51:58 +0100 Subject: [PATCH 016/441] Fix allowlist token issues 1) Don't accept enqueued notifications with an unexpected token. 2) Ensure allowlist token matches for all parceled and unparceled notifications (by only using the "root notification" one). 3) Simplify cookie usage in allowlist token serialization. 4) Ensure group summary (and any notifications added directly by NMS) have the correct token. Bug: 328254922 Bug: 305695605 Test: atest NotificationManagerServiceTest ParcelTest CloseSystemDialogsTest + manually Change-Id: I232e9b74eece745560ed2e762071b48984b3f176 Merged-In: I232e9b74eece745560ed2e762071b48984b3f176 --- core/java/android/app/Notification.java | 47 ++- core/java/android/os/Parcel.java | 22 ++ .../coretests/src/android/os/ParcelTest.java | 49 +++ .../NotificationManagerService.java | 18 +- .../NotificationManagerServiceTest.java | 354 +++++++++++++++++- 5 files changed, 475 insertions(+), 15 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 04d20a9298fd..3f4d5f07796e 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2519,8 +2519,11 @@ public class Notification implements Parcelable if (mAllowlistToken == null) { mAllowlistToken = processAllowlistToken; } - // Propagate this token to all pending intents that are unmarshalled from the parcel. - parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + // Propagate this token to all pending intents that are unmarshalled from the parcel, + // or keep the one we're already propagating, if that's the case. + if (!parcel.hasClassCookie(PendingIntent.class)) { + parcel.setClassCookie(PendingIntent.class, mAllowlistToken); + } when = parcel.readLong(); creationTime = parcel.readLong(); @@ -2977,9 +2980,24 @@ public class Notification implements Parcelable }); } try { - // IMPORTANT: Add marshaling code in writeToParcelImpl as we - // want to intercept all pending events written to the parcel. - writeToParcelImpl(parcel, flags); + boolean mustClearCookie = false; + if (!parcel.hasClassCookie(Notification.class)) { + // This is the "root" notification, and not an "inner" notification (including + // publicVersion or anything else that might be embedded in extras). So we want + // to use its token for every inner notification (might be null). + parcel.setClassCookie(Notification.class, mAllowlistToken); + mustClearCookie = true; + } + try { + // IMPORTANT: Add marshaling code in writeToParcelImpl as we + // want to intercept all pending events written to the parcel. + writeToParcelImpl(parcel, flags); + } finally { + if (mustClearCookie) { + parcel.removeClassCookie(Notification.class, mAllowlistToken); + } + } + synchronized (this) { // Must be written last! parcel.writeArraySet(allPendingIntents); @@ -2994,7 +3012,10 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); - parcel.writeStrongBinder(mAllowlistToken); + // Always use the same token as the root notification (might be null). + IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class); + parcel.writeStrongBinder(rootNotificationToken); + parcel.writeLong(when); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { @@ -3350,13 +3371,21 @@ public class Notification implements Parcelable * Sets the token used for background operations for the pending intents associated with this * notification. * - * This token is automatically set during deserialization for you, you usually won't need to - * call this unless you want to change the existing token, if any. + * Note: Should only be invoked by NotificationManagerService, since this is normally + * populated by unparceling (and also used there). Any other usage is suspect. * * @hide */ - public void setAllowlistToken(@Nullable IBinder token) { + public void overrideAllowlistToken(IBinder token) { mAllowlistToken = token; + if (publicVersion != null) { + publicVersion.overrideAllowlistToken(token); + } + } + + /** @hide */ + public IBinder getAllowlistToken() { + return mAllowlistToken; } /** diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index d0a77a031c99..403a5be7b0ea 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -651,6 +651,28 @@ public final class Parcel { return mClassCookies != null ? mClassCookies.get(clz) : null; } + /** @hide */ + public void removeClassCookie(Class clz, Object expectedCookie) { + if (mClassCookies != null) { + Object removedCookie = mClassCookies.remove(clz); + if (removedCookie != expectedCookie) { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but instead removed " + removedCookie); + } + } else { + Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz + + ") but no cookies were present"); + } + } + + /** + * Whether {@link #setClassCookie} has been called with the specified {@code clz}. + * @hide + */ + public boolean hasClassCookie(Class clz) { + return mClassCookies != null && mClassCookies.containsKey(clz); + } + /** @hide */ public final void adoptClassCookies(Parcel from) { mClassCookies = from.mClassCookies; diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index dcb3e2f23da8..a21ea30a4b29 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -16,15 +16,20 @@ package android.os; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; +import android.util.Log; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; + @Presubmit @RunWith(AndroidJUnit4.class) public class ParcelTest { @@ -110,4 +115,48 @@ public class ParcelTest { assertEquals(string, p.readString16()); } } + + @Test + public void testClassCookies() { + Parcel p = Parcel.obtain(); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + + p.setClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isTrue(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie"); + + p.removeClassCookie(ParcelTest.class, "string_cookie"); + assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); + assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null); + + p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie"); + p.recycle(); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); + } + + @Test + public void testClassCookies_removeUnexpected() { + Parcel p = Parcel.obtain(); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present")); + + p.setClassCookie(ParcelTest.class, "value"); + + assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different")); + assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed + + p.recycle(); + } + + private static void assertLogsWtf(Runnable test) { + ArrayList wtfs = new ArrayList<>(); + Log.TerribleFailureHandler oldHandler = Log.setWtfHandler( + (tag, what, system) -> wtfs.add(what)); + try { + test.run(); + } finally { + Log.setWtfHandler(oldHandler); + } + assertThat(wtfs).hasSize(1); + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 75fa7ac9a413..a5c4e74c46aa 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -592,7 +592,7 @@ public class NotificationManagerService extends SystemService { private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); - private static final IBinder ALLOWLIST_TOKEN = new Binder(); + static final IBinder ALLOWLIST_TOKEN = new Binder(); protected RankingHandler mRankingHandler; private long mLastOverRateLogTime; private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; @@ -4230,7 +4230,7 @@ public class NotificationManagerService extends SystemService { // Remove background token before returning notification to untrusted app, this // ensures the app isn't able to perform background operations that are // associated with notification interactions. - notification.setAllowlistToken(null); + notification.overrideAllowlistToken(null); return new StatusBarNotification( sbn.getPackageName(), sbn.getOpPkg(), @@ -6214,6 +6214,15 @@ public class NotificationManagerService extends SystemService { + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId); } + IBinder allowlistToken = notification.getAllowlistToken(); + if (allowlistToken != null && allowlistToken != ALLOWLIST_TOKEN) { + throw new SecurityException( + "Unexpected allowlist token received from " + callingUid); + } + // allowlistToken is populated by unparceling, so it can be null if the notification was + // posted from inside system_server. Ensure it's the expected value. + notification.overrideAllowlistToken(ALLOWLIST_TOKEN); + checkRestrictedCategories(notification); // Fix the notification as best we can. @@ -6958,6 +6967,11 @@ public class NotificationManagerService extends SystemService { @Override public void run() { synchronized (mNotificationLock) { + // allowlistToken is populated by unparceling, so it will be absent if the + // EnqueueNotificationRunnable is created directly by NMS (as we do for group + // summaries) instead of via notify(). Fix that. + r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN); + final Long snoozeAt = mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), 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 2eba8478cd3e..defe06fb24d2 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -58,6 +58,7 @@ import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -100,6 +101,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.annotation.SuppressLint; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -150,6 +152,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Looper; import android.os.Parcel; +import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -209,6 +212,7 @@ import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import org.junit.After; import org.junit.Before; @@ -243,7 +247,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; private static final int UID_HEADLESS = 1000000; + private TestableContext mContext = spy(getContext()); private final int mUid = Binder.getCallingUid(); + private final @UserIdInt int mUserId = UserHandle.getUserId(mUid); + private final UserHandle mUser = UserHandle.of(mUserId); + private final String mPkg = mContext.getPackageName(); + private TestableNotificationManagerService mService; private INotificationManager mBinderService; private NotificationManagerInternal mInternalService; @@ -254,7 +263,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private PackageManager mPackageManagerClient; @Mock private WindowManagerInternal mWindowManagerInternal; - private TestableContext mContext = spy(getContext()); private final String PKG = mContext.getPackageName(); private TestableLooper mTestableLooper; @Mock @@ -1811,7 +1819,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true, - notif.getUserId(), 0, null); + notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -2056,7 +2064,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, - Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null); + Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -2109,7 +2117,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; mService.addNotification(notif); mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true, - notif.getUserId(), 0, null); + notif.getUserId(), REASON_CANCEL, null); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); @@ -8991,4 +8999,342 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nru = mService.makeRankingUpdateLocked(null); assertEquals(2, nru.getRankingMap().getOrderedKeys().length); } + + @Test + public void enqueueNotification_acceptsCorrectToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_acceptsNullToken_andPopulatesIt() throws RemoteException { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + assertThat(receivedWithoutParceling.getAllowlistToken()).isNull(); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedWithoutParceling, Notification.CREATOR), mUserId); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_directlyThroughRunnable_populatesAllowlistToken() { + Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + NotificationRecord record = new NotificationRecord( + mContext, + new StatusBarNotification(mPkg, mPkg, 1, "tag", mUid, 44, receivedWithoutParceling, + mUser, "groupKey", 0), + mTestNotificationChannel); + assertThat(record.getNotification().getAllowlistToken()).isNull(); + + mWorkerHandler.post( + mService.new EnqueueNotificationRunnable(mUserId, record, false)); + waitForIdle(); + + assertThat(mService.mNotificationList).hasSize(1); + assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) + .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); + } + + @Test + public void enqueueNotification_rejectsOtherToken() throws RemoteException { + Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .build(); + sent.overrideAllowlistToken(new Binder()); + Notification received = parcelAndUnparcel(sent, Notification.CREATOR); + assertThat(received.getAllowlistToken()).isEqualTo(sent.getAllowlistToken()); + + assertThrows(SecurityException.class, () -> + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(received, Notification.CREATOR), mUserId)); + waitForIdle(); + + assertThat(mService.mNotificationList).isEmpty(); + } + + @Test + public void enqueueNotification_customParcelingWithFakeInnerToken_hasCorrectTokenInIntents() + throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .build(); + sentFromApp.publicVersion.overrideAllowlistToken(new Binder()); + + // Instead of using the normal parceling, assume the caller parcels it by hand, including a + // null token in the outer notification (as would be expected, and as is verified by + // enqueue) but trying to sneak in a different one in the inner notification, hoping it gets + // propagated to the PendingIntents. + Parcel parcelSentFromApp = Parcel.obtain(); + writeNotificationToParcelCustom(parcelSentFromApp, sentFromApp, new ArraySet<>( + Lists.newArrayList(sentFromApp.contentIntent, + sentFromApp.publicVersion.contentIntent))); + + // Use the unparceling as received in enqueueNotificationWithTag() + parcelSentFromApp.setDataPosition(0); + Notification receivedByNms = new Notification(parcelSentFromApp); + + // Verify that all the pendingIntents have the correct token. + assertThat(receivedByNms.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(receivedByNms.publicVersion.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + } + + /** + * Replicates the behavior of {@link Notification#writeToParcel} but excluding the + * "always use the same allowlist token as the root notification" parts. + */ + private static void writeNotificationToParcelCustom(Parcel parcel, Notification notif, + ArraySet allPendingIntents) { + int flags = 0; + parcel.writeInt(1); // version? + + parcel.writeStrongBinder(notif.getAllowlistToken()); + parcel.writeLong(notif.when); + parcel.writeLong(1234L); // notif.creationTime is private + if (notif.getSmallIcon() != null) { + parcel.writeInt(1); + notif.getSmallIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.number); + if (notif.contentIntent != null) { + parcel.writeInt(1); + notif.contentIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.deleteIntent != null) { + parcel.writeInt(1); + notif.deleteIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.tickerText != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.tickerText, parcel, flags); + } else { + parcel.writeInt(0); + } + if (notif.tickerView != null) { + parcel.writeInt(1); + notif.tickerView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.contentView != null) { + parcel.writeInt(1); + notif.contentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (notif.getLargeIcon() != null) { + parcel.writeInt(1); + notif.getLargeIcon().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.defaults); + parcel.writeInt(notif.flags); + + if (notif.sound != null) { + parcel.writeInt(1); + notif.sound.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(notif.audioStreamType); + + if (notif.audioAttributes != null) { + parcel.writeInt(1); + notif.audioAttributes.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeLongArray(notif.vibrate); + parcel.writeInt(notif.ledARGB); + parcel.writeInt(notif.ledOnMS); + parcel.writeInt(notif.ledOffMS); + parcel.writeInt(notif.iconLevel); + + if (notif.fullScreenIntent != null) { + parcel.writeInt(1); + notif.fullScreenIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.priority); + + parcel.writeString8(notif.category); + + parcel.writeString8(notif.getGroup()); + + parcel.writeString8(notif.getSortKey()); + + parcel.writeBundle(notif.extras); // null ok + + parcel.writeTypedArray(notif.actions, 0); // null ok + + if (notif.bigContentView != null) { + parcel.writeInt(1); + notif.bigContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + if (notif.headsUpContentView != null) { + parcel.writeInt(1); + notif.headsUpContentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.visibility); + + if (notif.publicVersion != null) { + parcel.writeInt(1); + writeNotificationToParcelCustom(parcel, notif.publicVersion, new ArraySet<>()); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.color); + + if (notif.getChannelId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getChannelId()); + } else { + parcel.writeInt(0); + } + parcel.writeLong(notif.getTimeoutAfter()); + + if (notif.getShortcutId() != null) { + parcel.writeInt(1); + parcel.writeString8(notif.getShortcutId()); + } else { + parcel.writeInt(0); + } + + if (notif.getLocusId() != null) { + parcel.writeInt(1); + notif.getLocusId().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getBadgeIconType()); + + if (notif.getSettingsText() != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(notif.getSettingsText(), parcel, flags); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(notif.getGroupAlertBehavior()); + + if (notif.getBubbleMetadata() != null) { + parcel.writeInt(1); + notif.getBubbleMetadata().writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeBoolean(notif.getAllowSystemGeneratedContextualActions()); + + parcel.writeInt(Notification.FOREGROUND_SERVICE_DEFAULT); // no getter for mFgsDeferBehavior + + // mUsesStandardHeader is not written because it should be recomputed in listeners + + parcel.writeArraySet(allPendingIntents); + } + + @Test + @SuppressWarnings("unchecked") + public void getActiveNotifications_doesNotLeakAllowlistToken() throws RemoteException { + Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("content")) + .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("public")) + .build()) + .extend(new Notification.WearableExtender() + .addPage(new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setContentIntent(createPendingIntent("wearPage")) + .build())) + .build(); + // Binder transition: app -> NMS + Notification receivedByNms = parcelAndUnparcel(sentFromApp, Notification.CREATOR); + assertThat(receivedByNms.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, + parcelAndUnparcel(receivedByNms, Notification.CREATOR), mUserId); + waitForIdle(); + assertThat(mService.mNotificationList).hasSize(1); + Notification posted = mService.mNotificationList.get(0).getNotification(); + assertThat(posted.getAllowlistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + assertThat(posted.contentIntent.getWhitelistToken()).isEqualTo( + NotificationManagerService.ALLOWLIST_TOKEN); + + ParceledListSlice listSentFromNms = + mBinderService.getAppActiveNotifications(mPkg, mUserId); + // Binder transition: NMS -> app. App doesn't have the allowlist token so clear it + // (having a different one would produce the same effect; the relevant thing is to not let + // out ALLOWLIST_TOKEN). + // Note: for other tests, this is restored by constructing TestableNMS in setup(). + Notification.processAllowlistToken = null; + ParceledListSlice listReceivedByApp = parcelAndUnparcel( + listSentFromNms, ParceledListSlice.CREATOR); + Notification gottenBackByApp = listReceivedByApp.getList().get(0).getNotification(); + + assertThat(gottenBackByApp.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.contentIntent.getWhitelistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.getAllowlistToken()).isNull(); + assertThat(gottenBackByApp.publicVersion.contentIntent.getWhitelistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).getAllowlistToken()).isNull(); + assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() + .get(0).contentIntent.getWhitelistToken()).isNull(); + } + + private static T parcelAndUnparcel(T source, + Parcelable.Creator creator) { + Parcel parcel = Parcel.obtain(); + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return creator.createFromParcel(parcel); + } + + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, + new Intent(action).setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + } } -- GitLab From a3a9f3e7f940d8a46bb8d7e642f038ead46cb308 Mon Sep 17 00:00:00 2001 From: Charles Chen Date: Mon, 23 Sep 2024 11:23:56 +0800 Subject: [PATCH 017/441] Add flag for better support non-match parent activity Bug: 356277166 Test: presubmit Flag: com.android.window.flags.better_support_non_match_parent_activity Change-Id: Id5f96a78a5a5a466b0ceb50b086f5247a3e1f1ce --- core/java/android/window/flags/windowing_sdk.aconfig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index f0ea7a82d763..b7012b68d459 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -108,3 +108,10 @@ flag { description: "Makes WindowLayoutInfo accessible without racing in the Activity#onCreate()" bug: "337820752" } + +flag { + namespace: "windowing_sdk" + name: "better_support_non_match_parent_activity" + description: "Relax the assumption of non-match parent activity" + bug: "356277166" +} -- GitLab From 37048fda1ffdb7e6ae869a42a9f868ddf88de8a1 Mon Sep 17 00:00:00 2001 From: ramindani Date: Wed, 25 Sep 2024 13:25:13 -0700 Subject: [PATCH 018/441] adds a flag for hasArrSupport BUG: 361433651 Test: N/A Flag: com.android.server.display.feature.flags.enable_has_arr_support Change-Id: I2ea2bfd93c9be60894a26c5d07298b253e71b487 --- .../android/server/display/feature/display_flags.aconfig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 2f04d9e5fdbb..6c966d30cff0 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -392,3 +392,11 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_has_arr_support" + namespace: "core_graphics" + description: "Flag for an API to get whether display supports ARR or not" + bug: "361433651" + is_fixed_read_only: true +} -- GitLab From 0ee4bebd141824fd100f683adf58a2410719426f Mon Sep 17 00:00:00 2001 From: Andy Wickham Date: Thu, 8 Aug 2024 02:25:09 +0000 Subject: [PATCH 019/441] Adds hidden constant for FEATURE_CONTEXTUAL_SEARCH. Bug: Bug: 353715553 Test: Manual Flag: EXEMPT convenience Change-Id: I21dac3289a1b74dd9808a5fb15f882c94e84c992 --- .../app/contextualsearch/ContextualSearchManager.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java index cfbe7416bf9d..3438cc861661 100644 --- a/core/java/android/app/contextualsearch/ContextualSearchManager.java +++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java @@ -102,6 +102,7 @@ public final class ContextualSearchManager { * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. */ public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN"; + /** * Intent action for contextual search invocation. The app providing the contextual search * experience must add this intent filter action to the activity it wants to be launched. @@ -111,6 +112,14 @@ public final class ContextualSearchManager { public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH"; + /** + * System feature declaring that the device supports Contextual Search. + * + * @hide + */ + public static final String FEATURE_CONTEXTUAL_SEARCH = + "com.google.android.feature.CONTEXTUAL_SEARCH"; + /** Entrypoint to be used when a user long presses on the nav handle. */ public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1; /** Entrypoint to be used when a user long presses on the home button. */ -- GitLab From 0b1a4fbfc8e374aaf4c8682332736bdf1428d69f Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Thu, 26 Sep 2024 01:44:53 +0000 Subject: [PATCH 020/441] Expand the a11y focus bounds if they're too small to be visible. Attempting to draw the focus drawable with bounds that are smaller than 2 times the focus stroke size results in it being invisible. Normally Views shouldn't be this thin in the first place, but this change helps ensure the focus is visible when drawn over those edge case ultra-thin Views. See the bug comment 10 for screenshots of the fix. Flag: android.view.accessibility.focus_rect_min_size Test: atest FrameworksCoreTests:ViewRootImplTest Test: Open problem app from bug, observe focus rect is now visible Bug: 368667566 Change-Id: I5261cbd980d2a11a28a755756a4104ac2a349870 --- core/java/android/view/ViewRootImpl.java | 29 +++++++++++++- .../flags/accessibility_flags.aconfig | 10 +++++ .../src/android/view/ViewRootImplTest.java | 38 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0ca442d66e6f..5fcae1c20e07 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -5945,7 +5945,34 @@ public final class ViewRootImpl implements ViewParent, // If no intersection, set bounds to empty. bounds.setEmpty(); } - return !bounds.isEmpty(); + + if (bounds.isEmpty()) { + return false; + } + + if (android.view.accessibility.Flags.focusRectMinSize()) { + adjustAccessibilityFocusedRectBoundsIfNeeded(bounds); + } + + return true; + } + + /** + * Adjusts accessibility focused rect bounds so that they are not invisible. + * + *

Focus bounds smaller than double the stroke width are very hard to see (or invisible). + * Expand the focus bounds if necessary to at least double the stroke width. + * @param bounds The bounds to adjust + */ + @VisibleForTesting + public void adjustAccessibilityFocusedRectBoundsIfNeeded(Rect bounds) { + final int minRectLength = mAccessibilityManager.getAccessibilityFocusStrokeWidth() * 2; + if (bounds.width() < minRectLength || bounds.height() < minRectLength) { + final float missingWidth = Math.max(0, minRectLength - bounds.width()); + final float missingHeight = Math.max(0, minRectLength - bounds.height()); + bounds.inset(-1 * (int) Math.ceil(missingWidth / 2), + -1 * (int) Math.ceil(missingHeight / 2)); + } } private Drawable getAccessibilityFocusedDrawable() { diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index b9e97502cad7..43892ac20793 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -87,6 +87,16 @@ flag { bug: "303131332" } +flag { + namespace: "accessibility" + name: "focus_rect_min_size" + description: "Ensures the a11y focus rect is big enough to be drawn as visible" + bug: "368667566" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { namespace: "accessibility" name: "force_invert_color" diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 632721126714..ed9fc1c9e547 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -71,6 +71,7 @@ import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.os.SystemProperties; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -82,6 +83,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; +import android.view.accessibility.AccessibilityManager; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -1628,6 +1630,42 @@ public class ViewRootImplTest { }); } + @Test + @EnableFlags(android.view.accessibility.Flags.FLAG_FOCUS_RECT_MIN_SIZE) + public void testAdjustAccessibilityFocusedBounds_largeEnoughBoundsAreUnchanged() { + final int strokeWidth = sContext.getSystemService(AccessibilityManager.class) + .getAccessibilityFocusStrokeWidth(); + final int left, top, width, height; + left = top = 100; + width = height = strokeWidth * 2; + final Rect bounds = new Rect(left, top, left + width, top + height); + final Rect originalBounds = new Rect(bounds); + + mViewRootImpl.adjustAccessibilityFocusedRectBoundsIfNeeded(bounds); + + assertThat(bounds).isEqualTo(originalBounds); + } + + @Test + @EnableFlags(android.view.accessibility.Flags.FLAG_FOCUS_RECT_MIN_SIZE) + public void testAdjustAccessibilityFocusedBounds_smallBoundsAreExpanded() { + final int strokeWidth = sContext.getSystemService(AccessibilityManager.class) + .getAccessibilityFocusStrokeWidth(); + final int left, top, width, height; + left = top = 100; + width = height = strokeWidth; + final Rect bounds = new Rect(left, top, left + width, top + height); + final Rect originalBounds = new Rect(bounds); + + mViewRootImpl.adjustAccessibilityFocusedRectBoundsIfNeeded(bounds); + + // Bounds should be centered on the same point, but expanded to at least strokeWidth * 2 + assertThat(bounds.centerX()).isEqualTo(originalBounds.centerX()); + assertThat(bounds.centerY()).isEqualTo(originalBounds.centerY()); + assertThat(bounds.width()).isAtLeast(strokeWidth * 2); + assertThat(bounds.height()).isAtLeast(strokeWidth * 2); + } + private boolean setForceDarkSysProp(boolean isForceDarkEnabled) { try { SystemProperties.set( -- GitLab From 684ecbab1bfabe830a7a88446ccb5fbc63fa45b0 Mon Sep 17 00:00:00 2001 From: Dan Sandler Date: Wed, 9 Oct 2024 10:04:33 -0400 Subject: [PATCH 021/441] Opt tuner settings activity out of edge-to-edge. Still fun for some, but not for all: this screen is no longer accessible by end users but is useful in OS development. (This quick fix does, however, fix a similar bug in the Demo Mode activity.) Fixes: 371216803 Fixes: 341039611 Flag: EXEMPT bugfix in legacy code Test: adb root \ && adb shell pm enable com.android.systemui/.tuner.TunerActivity \ && adb shell am start -n com.android.systemui/.tuner/TunerActivity Change-Id: Ibb6984ab4b44e12956c5c60b9903b73d12e1cd26 --- packages/SystemUI/res/values/styles.xml | 2 ++ .../SystemUI/src/com/android/systemui/tuner/TunerActivity.java | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index a02c35461031..5b9c64ff3e3e 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -676,10 +676,12 @@ - - - - - - - - - - - \ No newline at end of file -- GitLab From 7899f8dad41a5e0f4cbca5d3de97d9999f6001e7 Mon Sep 17 00:00:00 2001 From: Wenhao Wang Date: Thu, 10 Oct 2024 17:34:07 -0700 Subject: [PATCH 111/441] Add OWNERS files for forensic tests Bug: 365994454 Test: N/A Change-Id: Idbb96c07fe5eeee1e24d3eb847a6ebced8abed68 --- services/tests/security/forensic/OWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 services/tests/security/forensic/OWNERS diff --git a/services/tests/security/forensic/OWNERS b/services/tests/security/forensic/OWNERS new file mode 100644 index 000000000000..80c9afb96033 --- /dev/null +++ b/services/tests/security/forensic/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 36824 + +file:platform/frameworks/base:main:/core/java/android/security/forensic/OWNERS -- GitLab From 0a80242ad14192ab55c3e0141eb0d20382efc65c Mon Sep 17 00:00:00 2001 From: Jerry Wu Date: Mon, 14 Oct 2024 17:56:27 +0000 Subject: [PATCH 112/441] Update to consistently highlight selection for single field. Confirmed with UX that: 1. Highlight all fields filled by Autofill. 2. Do NOT highlight for Augmented Autofill. We will stop hiding the highlight in normal Autofill session, but still keep hiding the highlight for Augmented Autofill: https://source.corp.google.com/h/googleplex-android/platform/frameworks/base/+/main:core/java/android/service/autofill/augmented/AugmentedAutofillService.java;l=496. With that saying, we will keep the flags PFLAG4_AUTOFILL_HIDE_HIGHLIGHT and its related helper functions. Local test: Before, single field, no highlight: https://screenshot.googleplex.com/7E9wSTdD8cM4Fiy Before, multi fields, with highlight: https://screenshot.googleplex.com/4QdUueUJYTPShin After, single field, with highlight: https://screenshot.googleplex.com/6oEmeKnugVZCbhm After, multi fields, with highlight: https://screenshot.googleplex.com/98MezWKRm745yX5 After, Augmented Autofill, Smart Reply, suggested: https://screenshot.googleplex.com/4tXMp4W8VFiVKJS After, Augmented Autofill, Smart Reply, no highlight: https://screenshot.googleplex.com/5KnUvN5jR6WmKqC Flag: android.service.autofill.highlight_autofill_single_field Bug:b/41496744 Test: atest android.autofillservice.cts.LoginWithCustomHighlightActivityTest Change-Id: Ie56586634ad8836695d4c8a26282cb1375f49113 --- services/autofill/bugfixes.aconfig | 7 + .../com/android/server/autofill/Session.java | 3096 +++++++++++------ 2 files changed, 1944 insertions(+), 1159 deletions(-) diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig index ba8448548365..1803424ae7d7 100644 --- a/services/autofill/bugfixes.aconfig +++ b/services/autofill/bugfixes.aconfig @@ -49,3 +49,10 @@ flag { description: "Include the session id into the FillEventHistory events as part of ClientState" bug: "333927465" } + +flag { + name: "highlight_autofill_single_field" + namespace: "autofill" + description: "Highlight single field after autofill selection" + bug: "41496744" +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 2fa0e0d0d946..8f12b1db8f29 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -39,6 +39,7 @@ import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG; import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED; import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE; import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; +import static android.service.autofill.Flags.highlightAutofillSingleField; import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED; import static android.view.autofill.AutofillManager.ACTION_START_SESSION; import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED; @@ -49,7 +50,6 @@ import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN; import static android.view.autofill.AutofillManager.EXTRA_AUTOFILL_REQUEST_ID; import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM; import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; - import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_EXPLICITLY_REQUESTED; import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_NORMAL_TRIGGER; @@ -104,7 +104,6 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUC import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; import android.app.ActivityTaskManager; import android.app.IAssistDataReceiver; import android.app.PendingIntent; @@ -143,7 +142,6 @@ import android.os.TransactionTooLargeException; import android.service.assist.classification.FieldClassificationRequest; import android.service.assist.classification.FieldClassificationResponse; import android.service.autofill.AutofillFieldClassificationService.Scores; -import android.service.autofill.AutofillService; import android.service.autofill.CompositeUserData; import android.service.autofill.ConvertCredentialResponse; import android.service.autofill.Dataset; @@ -186,7 +184,6 @@ import android.view.autofill.IAutoFillManagerClient; import android.view.autofill.IAutofillWindowPresenter; import android.view.inputmethod.InlineSuggestionsRequest; import android.widget.RemoteViews; - import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -198,7 +195,6 @@ import com.android.server.autofill.ui.InlineFillUi; import com.android.server.autofill.ui.PendingUi; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; - import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -222,18 +218,21 @@ import java.util.function.Function; /** * A session for a given activity. * - *

This class manages the multiple {@link ViewState}s for each view it has, and keeps track - * of the current {@link ViewState} to display the appropriate UI. + *

This class manages the multiple {@link ViewState}s for each view it has, and keeps track of + * the current {@link ViewState} to display the appropriate UI. * - *

Although the autofill requests and callbacks are stateless from the service's point of - * view, we need to keep state in the framework side for cases such as authentication. For - * example, when service return a {@link FillResponse} that contains all the fields needed - * to fill the activity but it requires authentication first, that response need to be held - * until the user authenticates or it times out. + *

Although the autofill requests and callbacks are stateless from the service's point of view, + * we need to keep state in the framework side for cases such as authentication. For example, when + * service return a {@link FillResponse} that contains all the fields needed to fill the activity + * but it requires authentication first, that response need to be held until the user authenticates + * or it times out. */ -final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener, - AutoFillUI.AutoFillUiCallback, ValueFinder, - RemoteFieldClassificationService.FieldClassificationServiceCallbacks { +final class Session + implements RemoteFillService.FillServiceCallbacks, + ViewState.Listener, + AutoFillUI.AutoFillUiCallback, + ValueFinder, + RemoteFieldClassificationService.FieldClassificationServiceCallbacks { private static final String TAG = "AutofillSession"; // This should never be true in production. This is only for local debugging. @@ -257,6 +256,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private final AutofillManagerServiceImpl mService; private final Handler mHandler; private final AutoFillUI mUi; + /** * Context associated with the session, it has the same {@link Context#getDisplayId() displayId} * of the activity being autofilled. @@ -286,14 +286,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** Session is destroyed and removed from the manager service. */ public static final int STATE_REMOVED = 3; - @IntDef(prefix = { "STATE_" }, value = { - STATE_UNKNOWN, - STATE_ACTIVE, - STATE_FINISHED, - STATE_REMOVED - }) + @IntDef( + prefix = {"STATE_"}, + value = {STATE_UNKNOWN, STATE_ACTIVE, STATE_FINISHED, STATE_REMOVED}) @Retention(RetentionPolicy.SOURCE) - @interface SessionState{} + @interface SessionState {} @GuardedBy("mLock") private final SessionFlags mSessionFlags; @@ -318,7 +315,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public final int mFlags; @GuardedBy("mLock") - @NonNull private IBinder mActivityToken; + @NonNull + private IBinder mActivityToken; /** The app activity that's being autofilled */ @NonNull private final ComponentName mComponentName; @@ -341,11 +339,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * autofill. */ @GuardedBy("mLock") - @Nullable private Pair mLastInlineSuggestionsRequest; + @Nullable + private Pair mLastInlineSuggestionsRequest; - /** - * Id of the View currently being displayed. - */ + /** Id of the View currently being displayed. */ @GuardedBy("mLock") private @Nullable AutofillId mCurrentViewId; @@ -363,8 +360,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * *

Only {@code null} when the session is for augmented autofill only. */ - @Nullable - private final RemoteFillService mRemoteFillService; + @Nullable private final RemoteFillService mRemoteFillService; /** * With the credman integration, Autofill Framework handles two types of autofill flows - @@ -379,8 +375,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * service the session was initially created with, the secondary provider handler will contain * the remaining autofill service. */ - @Nullable - private final SecondaryProviderHandler mSecondaryProviderHandler; + @Nullable private final SecondaryProviderHandler mSecondaryProviderHandler; @GuardedBy("mLock") private SparseArray mResponses; @@ -395,9 +390,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private ArrayList mContexts; - /** - * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. - */ + /** Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. */ private boolean mHasCallback; /** Whether the session has credential manager provider as the primary provider. */ @@ -426,32 +419,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private PendingUi mPendingSaveUi; - /** - * List of dataset ids selected by the user. - */ + /** List of dataset ids selected by the user. */ @GuardedBy("mLock") private ArrayList mSelectedDatasetIds; - /** - * When the session started (using elapsed time since boot). - */ + /** When the session started (using elapsed time since boot). */ private final long mStartTime; - /** - * Count of FillRequests in the session. - */ + /** Count of FillRequests in the session. */ private int mRequestCount; /** - * Starting timestamp of latency logger. - * This is set when Session created or when the view is reset. + * Starting timestamp of latency logger. This is set when Session created or when the view is + * reset. */ @GuardedBy("mLock") private long mLatencyBaseTime; - /** - * When the UI was shown for the first time (using elapsed time since boot). - */ + /** When the UI was shown for the first time (using elapsed time since boot). */ @GuardedBy("mLock") private long mUiShownTime; @@ -475,15 +460,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private final LocalLog mWtfHistory; - /** - * Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id. - */ + /** Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id. */ @GuardedBy("mLock") private final SparseArray mRequestLogs = new SparseArray<>(1); - /** - * Destroys the augmented Autofill UI. - */ + /** Destroys the augmented Autofill UI. */ // TODO(b/123099468): this runnable is called when the Autofill session is destroyed, the // main reason being the cases where user tap HOME. // Right now it's completely destroying the UI, but we need to decide whether / how to @@ -494,13 +475,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Nullable private Runnable mAugmentedAutofillDestroyer; - /** - * List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics. - */ + /** List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics. */ @GuardedBy("mLock") private ArrayList mAugmentedRequestsLogs; - /** * List of autofill ids of autofillable fields present in the AssistStructure that can be used * to trigger new augmented autofill requests (because the "standard" service was not interested @@ -509,23 +487,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private ArrayList mAugmentedAutofillableIds; - @NonNull - final AutofillInlineSessionController mInlineSessionController; + @NonNull final AutofillInlineSessionController mInlineSessionController; - /** - * Receiver of assist data from the app's {@link Activity}. - */ + /** Receiver of assist data from the app's {@link Activity}. */ private final AssistDataReceiverImpl mAssistReceiver = new AssistDataReceiverImpl(); - /** - * Receiver of assist data for pcc purpose - */ + /** Receiver of assist data for pcc purpose */ private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl(); private final ClassificationState mClassificationState = new ClassificationState(); - @Nullable - private final ComponentName mCredentialAutofillService; + @Nullable private final ComponentName mCredentialAutofillService; // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a // new one per Session. @@ -547,7 +519,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.v(TAG, "mDelayedFillBroadcastReceiver delayed fill action received"); synchronized (mLock) { int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0); - FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE, android.service.autofill.FillResponse.class); + FillResponse response = + intent.getParcelableExtra( + EXTRA_FILL_RESPONSE, + android.service.autofill.FillResponse.class); mFillRequestEventLogger.maybeSetRequestTriggerReason( TRIGGER_REASON_RETRIGGER); mAssistReceiver.processDelayedFillLocked(requestId, response); @@ -575,28 +550,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private SessionCommittedEventLogger mSessionCommittedEventLogger; - /** - * Fill dialog request would likely be sent slightly later. - */ + /** Fill dialog request would likely be sent slightly later. */ @NonNull @GuardedBy("mLock") private boolean mPreviouslyFillDialogPotentiallyStarted; /** - * Keeps track of if the user entered view, this is used to - * distinguish Fill Request that did not have user interaction - * with ones that did. + * Keeps track of if the user entered view, this is used to distinguish Fill Request that did + * not have user interaction with ones that did. * - * This is set to true when entering view - after FillDialog FillRequest - * or on plain user tap. + *

This is set to true when entering view - after FillDialog FillRequest or on plain user + * tap. */ @NonNull @GuardedBy("mLock") private boolean mLogViewEntered; /** - * Keeps the fill dialog trigger ids of the last response. This invalidates - * the trigger ids of the previous response. + * Keeps the fill dialog trigger ids of the last response. This invalidates the trigger ids of + * the previous response. */ @Nullable @GuardedBy("mLock") @@ -662,9 +634,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return false; } - /** - * Collection of flags/booleans that helps determine Session behaviors. - */ + /** Collection of flags/booleans that helps determine Session behaviors. */ private final class SessionFlags { /** Whether autofill is disabled by the service */ private boolean mAutofillDisabled; @@ -695,31 +665,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub { @GuardedBy("mLock") private boolean mWaitForInlineRequest; + @GuardedBy("mLock") private InlineSuggestionsRequest mPendingInlineSuggestionsRequest; + @GuardedBy("mLock") private FillRequest mPendingFillRequest; + @GuardedBy("mLock") private FillRequest mLastFillRequest; - @Nullable Consumer newAutofillRequestLocked(ViewState viewState, - boolean isInlineRequest) { + @Nullable + Consumer newAutofillRequestLocked( + ViewState viewState, boolean isInlineRequest) { mPendingFillRequest = null; mWaitForInlineRequest = isInlineRequest; mPendingInlineSuggestionsRequest = null; if (isInlineRequest) { WeakReference assistDataReceiverWeakReference = - new WeakReference(this); + new WeakReference(this); WeakReference viewStateWeakReference = - new WeakReference(viewState); - return new InlineSuggestionRequestConsumer(assistDataReceiverWeakReference, - viewStateWeakReference); + new WeakReference(viewState); + return new InlineSuggestionRequestConsumer( + assistDataReceiverWeakReference, viewStateWeakReference); } return null; } - void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest, - ViewState viewState) { + void handleInlineSuggestionRequest( + InlineSuggestionsRequest inlineSuggestionsRequest, ViewState viewState) { if (sVerbose) { Slog.v(TAG, "handleInlineSuggestionRequest(): inline suggestion request received"); } @@ -738,8 +712,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void maybeRequestFillLocked() { if (mPendingFillRequest == null) { if (sVerbose) { - Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request " - + "due to empty pending fill request"); + Slog.v( + TAG, + "maybeRequestFillLocked(): cancelling calling fill request " + + "due to empty pending fill request"); } return; } @@ -748,27 +724,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mWaitForInlineRequest) { if (mPendingInlineSuggestionsRequest == null) { if (sVerbose) { - Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request " - + "due to waiting for inline request and pending inline request is " - + "currently empty"); + Slog.v( + TAG, + "maybeRequestFillLocked(): cancelling calling fill request due to" + + " waiting for inline request and pending inline request is" + + " currently empty"); } return; } if (sVerbose) { - Slog.v(TAG, "maybeRequestFillLocked(): adding inline request to pending " - + "fill request"); + Slog.v( + TAG, + "maybeRequestFillLocked(): adding inline request to pending " + + "fill request"); } - mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), - mPendingFillRequest.getFillContexts(), - mPendingFillRequest.getHints(), - mPendingFillRequest.getClientState(), - mPendingFillRequest.getFlags(), - mPendingInlineSuggestionsRequest, - mPendingFillRequest.getDelayedFillIntentSender()); + mPendingFillRequest = + new FillRequest( + mPendingFillRequest.getId(), + mPendingFillRequest.getFillContexts(), + mPendingFillRequest.getHints(), + mPendingFillRequest.getClientState(), + mPendingFillRequest.getFlags(), + mPendingInlineSuggestionsRequest, + mPendingFillRequest.getDelayedFillIntentSender()); } else { if (sVerbose) { - Slog.v(TAG, "maybeRequestFillLocked(): not adding inline request to pending " - + "fill request"); + Slog.v( + TAG, + "maybeRequestFillLocked(): not adding inline request to pending " + + "fill request"); } } @@ -780,19 +764,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState && mSecondaryProviderHandler != null) { Slog.v(TAG, "Requesting fill response to secondary provider."); if (!mIsPrimaryCredential) { - mPendingFillRequest = addCredentialManagerDataToClientState( - mPendingFillRequest, - mPendingInlineSuggestionsRequest, id); + mPendingFillRequest = + addCredentialManagerDataToClientState( + mPendingFillRequest, mPendingInlineSuggestionsRequest, id); } - mSecondaryProviderHandler.onFillRequest(mPendingFillRequest, - mPendingFillRequest.getFlags(), mClient.asBinder()); + mSecondaryProviderHandler.onFillRequest( + mPendingFillRequest, mPendingFillRequest.getFlags(), mClient.asBinder()); } else if (mRemoteFillService != null) { if (mIsPrimaryCredential) { - mPendingFillRequest = addCredentialManagerDataToClientState( - mPendingFillRequest, - mPendingInlineSuggestionsRequest, id); - mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, - mClient.asBinder()); + mPendingFillRequest = + addCredentialManagerDataToClientState( + mPendingFillRequest, mPendingInlineSuggestionsRequest, id); + mRemoteFillService.onFillCredentialRequest( + mPendingFillRequest, mClient.asBinder()); } else { mRemoteFillService.onFillRequest(mPendingFillRequest); } @@ -813,8 +797,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void onHandleAssistData(Bundle resultData) throws RemoteException { if (mRemoteFillService == null) { - wtf(null, "onHandleAssistData() called without a remote service. " - + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); + wtf( + null, + "onHandleAssistData() called without a remote service. " + + "mForAugmentedAutofillOnly: %s", + mSessionFlags.mAugmentedAutofillOnly); return; } // Keeps to prevent it is cleared on multiple threads. @@ -824,7 +811,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE, android.app.assist.AssistStructure.class); + final AssistStructure structure = + resultData.getParcelable( + ASSIST_KEY_STRUCTURE, android.app.assist.AssistStructure.class); if (structure == null) { Slog.e(TAG, "No assist structure - app might have crashed providing it"); return; @@ -852,13 +841,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { structure.ensureDataForAutofill(); } catch (RuntimeException e) { - wtf(e, "Exception lazy loading assist structure for %s: %s", - structure.getActivityComponent(), e); + wtf( + e, + "Exception lazy loading assist structure for %s: %s", + structure.getActivityComponent(), + e); return; } - final ArrayList ids = Helper.getAutofillIds(structure, - /* autofillableOnly= */false); + final ArrayList ids = + Helper.getAutofillIds(structure, /* autofillableOnly= */ false); for (int i = 0; i < ids.size(); i++) { ids.get(i).setSessionId(Session.this.id); } @@ -868,8 +860,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mCompatMode) { // Sanitize URL bar, if needed - final String[] urlBarIds = mService.getUrlBarResourceIdsForCompatMode( - mComponentName.getPackageName()); + final String[] urlBarIds = + mService.getUrlBarResourceIdsForCompatMode( + mComponentName.getPackageName()); if (sDebug) { Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds)); } @@ -878,11 +871,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mUrlBar != null) { final AutofillId urlBarId = mUrlBar.getAutofillId(); if (sDebug) { - Slog.d(TAG, "Setting urlBar as id=" + urlBarId + " and domain " - + mUrlBar.getWebDomain()); + Slog.d( + TAG, + "Setting urlBar as id=" + + urlBarId + + " and domain " + + mUrlBar.getWebDomain()); } - final ViewState viewState = new ViewState(urlBarId, Session.this, - ViewState.STATE_URL_BAR, mIsPrimaryCredential); + final ViewState viewState = + new ViewState( + urlBarId, + Session.this, + ViewState.STATE_URL_BAR, + mIsPrimaryCredential); mViewStates.put(urlBarId, viewState); } } @@ -907,11 +908,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final List hints = getTypeHintsForProvider(); mDelayedFillPendingIntent = createPendingIntent(requestId); - request = new FillRequest(requestId, contexts, hints, mClientState, flags, - /*inlineSuggestionsRequest=*/ null, - /*delayedFillIntentSender=*/ mDelayedFillPendingIntent == null - ? null - : mDelayedFillPendingIntent.getIntentSender()); + request = + new FillRequest( + requestId, + contexts, + hints, + mClientState, + flags, + /* inlineSuggestionsRequest= */ null, + /* delayedFillIntentSender= */ mDelayedFillPendingIntent == null + ? null + : mDelayedFillPendingIntent.getIntentSender()); mPendingFillRequest = request; maybeRequestFillLocked(); @@ -930,39 +937,49 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void processDelayedFillLocked(int requestId, FillResponse response) { if (mLastFillRequest != null && requestId == mLastFillRequest.getId()) { - Slog.v(TAG, "processDelayedFillLocked: " - + "calling onFillRequestSuccess with new response"); - onFillRequestSuccess(requestId, response, - mService.getServicePackageName(), mLastFillRequest.getFlags()); + Slog.v( + TAG, + "processDelayedFillLocked: " + + "calling onFillRequestSuccess with new response"); + onFillRequestSuccess( + requestId, + response, + mService.getServicePackageName(), + mLastFillRequest.getFlags()); } } } - private FillRequest addCredentialManagerDataToClientState(FillRequest pendingFillRequest, - InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) { + private FillRequest addCredentialManagerDataToClientState( + FillRequest pendingFillRequest, + InlineSuggestionsRequest pendingInlineSuggestionsRequest, + int sessionId) { if (pendingFillRequest.getClientState() == null) { - pendingFillRequest = new FillRequest(pendingFillRequest.getId(), - pendingFillRequest.getFillContexts(), - pendingFillRequest.getHints(), - new Bundle(), - pendingFillRequest.getFlags(), - pendingInlineSuggestionsRequest, - pendingFillRequest.getDelayedFillIntentSender()); + pendingFillRequest = + new FillRequest( + pendingFillRequest.getId(), + pendingFillRequest.getFillContexts(), + pendingFillRequest.getHints(), + new Bundle(), + pendingFillRequest.getFlags(), + pendingInlineSuggestionsRequest, + pendingFillRequest.getDelayedFillIntentSender()); } pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId); pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId()); - ResultReceiver resultReceiver = constructCredentialManagerCallback( - pendingFillRequest.getId()); - pendingFillRequest.getClientState().putParcelable( - CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, resultReceiver); + ResultReceiver resultReceiver = + constructCredentialManagerCallback(pendingFillRequest.getId()); + pendingFillRequest + .getClientState() + .putParcelable(CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, resultReceiver); return pendingFillRequest; } /** - * Get the list of valid autofill hint types from Device flags - * Returns empty list if PCC is off or no types available - */ + * Get the list of valid autofill hint types from Device flags Returns empty list if PCC is off + * or no types available + */ private List getTypeHintsForProvider() { if (!mService.isPccClassificationEnabled()) { return Collections.EMPTY_LIST; @@ -978,9 +995,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return List.of(typeHints.split(PCC_HINTS_DELIMITER)); } - /** - * Assist Data Receiver for PCC - */ + /** Assist Data Receiver for PCC */ private final class PccAssistDataReceiverImpl extends IAssistDataReceiver.Stub { @GuardedBy("mLock") @@ -998,7 +1013,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState new WeakReference<>(Session.this); remoteFieldClassificationService.onFieldClassificationRequest( mClassificationState.mPendingFieldClassificationRequest, - fieldClassificationServiceCallbacksWeakRef); + fieldClassificationServiceCallbacksWeakRef); } mClassificationState.onFieldClassificationRequestSent(); } @@ -1006,26 +1021,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void onHandleAssistData(Bundle resultData) throws RemoteException { // TODO: add a check if pcc field classification service is present - final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE, - android.app.assist.AssistStructure.class); + final AssistStructure structure = + resultData.getParcelable( + ASSIST_KEY_STRUCTURE, android.app.assist.AssistStructure.class); if (structure == null) { - Slog.e(TAG, "No assist structure for pcc detection - " - + "app might have crashed providing it"); + Slog.e( + TAG, + "No assist structure for pcc detection - " + + "app might have crashed providing it"); return; } final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS); if (receiverExtras == null) { - Slog.e(TAG, "No receiver extras for pcc detection - " - + "app might have crashed providing it"); + Slog.e( + TAG, + "No receiver extras for pcc detection - " + + "app might have crashed providing it"); return; } final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID); if (sVerbose) { - Slog.v(TAG, "New structure for PCC Detection: requestId " + requestId + ": " - + structure); + Slog.v( + TAG, + "New structure for PCC Detection: requestId " + + requestId + + ": " + + structure); } synchronized (mLock) { @@ -1037,13 +1061,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { structure.ensureDataForAutofill(); } catch (RuntimeException e) { - wtf(e, "Exception lazy loading assist structure for %s: %s", - structure.getActivityComponent(), e); + wtf( + e, + "Exception lazy loading assist structure for %s: %s", + structure.getActivityComponent(), + e); return; } - final ArrayList ids = Helper.getAutofillIds(structure, - /* autofillableOnly= */false); + final ArrayList ids = + Helper.getAutofillIds(structure, /* autofillableOnly= */ false); for (int i = 0; i < ids.size(); i++) { ids.get(i).setSessionId(Session.this.id); } @@ -1066,13 +1093,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState PendingIntent pendingIntent; final long identity = Binder.clearCallingIdentity(); try { - Intent intent = new Intent(ACTION_DELAYED_FILL).setPackage("android") - .putExtra(EXTRA_REQUEST_ID, requestId); - pendingIntent = PendingIntent.getBroadcast( - mContext, this.id, intent, - PendingIntent.FLAG_MUTABLE - | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_CANCEL_CURRENT); + Intent intent = + new Intent(ACTION_DELAYED_FILL) + .setPackage("android") + .putExtra(EXTRA_REQUEST_ID, requestId); + pendingIntent = + PendingIntent.getBroadcast( + mContext, + this.id, + intent, + PendingIntent.FLAG_MUTABLE + | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_CANCEL_CURRENT); } finally { Binder.restoreCallingIdentity(identity); } @@ -1113,9 +1145,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - /** - * Returns the ids of all entries in {@link #mViewStates} in the same order. - */ + /** Returns the ids of all entries in {@link #mViewStates} in the same order. */ @GuardedBy("mLock") private AutofillId[] getIdsOfAllViewStatesLocked() { final int numViewState = mViewStates.size(); @@ -1164,8 +1194,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - *

Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, - * or {@code null} when not found on either of them. + * Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, or + * {@code null} when not found on either of them. */ @GuardedBy("mLock") @Nullable @@ -1180,16 +1210,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList previousSessions = mService.getPreviousSessionsLocked(this); if (previousSessions != null) { if (sDebug) { - Slog.d(TAG, "findValueLocked(): looking on " + previousSessions.size() - + " previous sessions for autofillId " + autofillId); + Slog.d( + TAG, + "findValueLocked(): looking on " + + previousSessions.size() + + " previous sessions for autofillId " + + autofillId); } for (int i = 0; i < previousSessions.size(); i++) { final Session previousSession = previousSessions.get(i); - final AutofillValue previousValue = previousSession - .findValueFromThisSessionOnlyLocked(autofillId); + final AutofillValue previousValue = + previousSession.findValueFromThisSessionOnlyLocked(autofillId); if (previousValue != null) { - return getSanitizedValue(createSanitizers(previousSession.getSaveInfoLocked()), - autofillId, previousValue); + return getSanitizedValue( + createSanitizers(previousSession.getSaveInfoLocked()), + autofillId, + previousValue); } } } @@ -1212,16 +1248,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState AutofillValue candidateSaveValue = state.getCandidateSaveValue(); if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { if (sDebug) { - Slog.d(TAG, "findValueLocked(): current value for " + autofillId - + " is empty, using candidateSaveValue instead."); + Slog.d( + TAG, + "findValueLocked(): current value for " + + autofillId + + " is empty, using candidateSaveValue instead."); } return candidateSaveValue; } } if (value == null) { if (sDebug) { - Slog.d(TAG, "findValueLocked(): no current value for " + autofillId - + ", checking value from previous fill contexts"); + Slog.d( + TAG, + "findValueLocked(): no current value for " + + autofillId + + ", checking value from previous fill contexts"); value = getValueFromContextsLocked(autofillId); } } @@ -1231,17 +1273,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Updates values of the nodes in the context's structure so that: * - * - proper node is focused - * - autofillValue is sent back to service when it was previously autofilled - * - autofillValue is sent in the view used to force a request + *

- proper node is focused - autofillValue is sent back to service when it was previously + * autofilled - autofillValue is sent in the view used to force a request * * @param fillContext The context to be filled * @param flags The flags that started the session */ @GuardedBy("mLock") private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) { - final ViewNode[] nodes = fillContext - .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); + final ViewNode[] nodes = + fillContext.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); final int numViewState = mViewStates.size(); for (int i = 0; i < numViewState; i++) { @@ -1250,7 +1291,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ViewNode node = nodes[i]; if (node == null) { if (sVerbose) { - Slog.v(TAG, + Slog.v( + TAG, "fillContextWithAllowedValuesLocked(): no node for " + viewState.id); } continue; @@ -1277,14 +1319,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - /** - * Cancels the last request sent to the {@link #mRemoteFillService}. - */ + /** Cancels the last request sent to the {@link #mRemoteFillService}. */ @GuardedBy("mLock") private void cancelCurrentRequestLocked() { if (mRemoteFillService == null) { - wtf(null, "cancelCurrentRequestLocked() called without a remote service. " - + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); + wtf( + null, + "cancelCurrentRequestLocked() called without a remote service. " + + "mForAugmentedAutofillOnly: %s", + mSessionFlags.mAugmentedAutofillOnly); return; } final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); @@ -1318,21 +1361,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private Optional requestNewFillResponseLocked( @NonNull ViewState viewState, int newState, int flags) { boolean isSecondary = shouldRequestSecondaryProvider(flags); - final FillResponse existingResponse = isSecondary - ? viewState.getSecondaryResponse() : viewState.getResponse(); + final FillResponse existingResponse = + isSecondary ? viewState.getSecondaryResponse() : viewState.getResponse(); mFillRequestEventLogger.startLogForNewRequest(); mRequestCount++; mFillRequestEventLogger.maybeSetAppPackageUid(uid); mFillRequestEventLogger.maybeSetFlags(mFlags); - if(mPreviouslyFillDialogPotentiallyStarted) { + if (mPreviouslyFillDialogPotentiallyStarted) { mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_PRE_TRIGGER); } else { if ((flags & FLAG_MANUAL_REQUEST) != 0) { mFillRequestEventLogger.maybeSetRequestTriggerReason( TRIGGER_REASON_EXPLICITLY_REQUESTED); } else { - mFillRequestEventLogger.maybeSetRequestTriggerReason( - TRIGGER_REASON_NORMAL_TRIGGER); + mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_NORMAL_TRIGGER); } } if (existingResponse != null) { @@ -1348,9 +1390,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionState = STATE_ACTIVE; if (mSessionFlags.mAugmentedAutofillOnly || mRemoteFillService == null) { if (sVerbose) { - Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead " - + "(mForAugmentedAutofillOnly=" + mSessionFlags.mAugmentedAutofillOnly - + ", flags=" + flags + ")"); + Slog.v( + TAG, + "requestNewFillResponse(): triggering augmented autofill instead " + + "(mForAugmentedAutofillOnly=" + + mSessionFlags.mAugmentedAutofillOnly + + ", flags=" + + flags + + ")"); } mSessionFlags.mAugmentedAutofillOnly = true; mFillRequestEventLogger.maybeSetRequestId(AUGMENTED_AUTOFILL_REQUEST_ID); @@ -1365,16 +1412,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Create a metrics log for the request final int ordinal = mRequestLogs.size() + 1; - final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal); + final LogMaker log = + newLogMaker(MetricsEvent.AUTOFILL_REQUEST) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal); if (flags != 0) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags); } mRequestLogs.put(requestId, log); if (sVerbose) { - Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId=" + requestId - + ", flags=" + flags); + Slog.v( + TAG, + "Requesting structure for request #" + + ordinal + + " ,requestId=" + + requestId + + ", flags=" + + flags); } boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0; mFillRequestEventLogger.maybeSetRequestId(requestId); @@ -1406,11 +1460,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // is also not focused. final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); - if (mSessionFlags.mInlineSupportedByService && remoteRenderService != null - && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { + if (mSessionFlags.mInlineSupportedByService + && remoteRenderService != null + && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { Consumer inlineSuggestionsRequestConsumer = - mAssistReceiver.newAutofillRequestLocked(viewState, - /* isInlineRequest= */ true); + mAssistReceiver.newAutofillRequestLocked( + viewState, /* isInlineRequest= */ true); if (inlineSuggestionsRequestConsumer != null) { final int requestIdCopy = requestId; final AutofillId focusedId = mCurrentViewId; @@ -1423,8 +1478,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState requestIdCopy, inlineSuggestionsRequestConsumer, focusedId); - RemoteCallback inlineSuggestionRendorInfoCallback = new RemoteCallback( - inlineSuggestionRendorInfoCallbackOnResultListener, mHandler); + RemoteCallback inlineSuggestionRendorInfoCallback = + new RemoteCallback( + inlineSuggestionRendorInfoCallbackOnResultListener, mHandler); remoteRenderService.getInlineSuggestionsRendererInfo( inlineSuggestionRendorInfoCallback); @@ -1465,8 +1521,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); final long identity = Binder.clearCallingIdentity(); try { - if (!ActivityTaskManager.getService().requestAutofillData(mPccAssistReceiver, - receiverExtras, mActivityToken, flags)) { + if (!ActivityTaskManager.getService() + .requestAutofillData( + mPccAssistReceiver, receiverExtras, mActivityToken, flags)) { Slog.w(TAG, "failed to request autofill data for " + mActivityToken); } } finally { @@ -1483,8 +1540,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); final long identity = Binder.clearCallingIdentity(); try { - if (!ActivityTaskManager.getService().requestAutofillData(mAssistReceiver, - receiverExtras, mActivityToken, flags)) { + if (!ActivityTaskManager.getService() + .requestAutofillData( + mAssistReceiver, receiverExtras, mActivityToken, flags)) { Slog.w(TAG, "failed to request autofill data for " + mActivityToken); } } finally { @@ -1495,13 +1553,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui, - @NonNull Context context, @NonNull Handler handler, int userId, @NonNull Object lock, - int sessionId, int taskId, int uid, @NonNull IBinder activityToken, - @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory, - @NonNull LocalLog wtfHistory, @Nullable ComponentName serviceComponentName, - @NonNull ComponentName componentName, boolean compatMode, - boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags, + Session( + @NonNull AutofillManagerServiceImpl service, + @NonNull AutoFillUI ui, + @NonNull Context context, + @NonNull Handler handler, + int userId, + @NonNull Object lock, + int sessionId, + int taskId, + int uid, + @NonNull IBinder activityToken, + @NonNull IBinder client, + boolean hasCallback, + @NonNull LocalLog uiLatencyHistory, + @NonNull LocalLog wtfHistory, + @Nullable ComponentName serviceComponentName, + @NonNull ComponentName componentName, + boolean compatMode, + boolean bindInstantServiceAllowed, + boolean forAugmentedAutofillOnly, + int flags, @NonNull InputMethodManagerInternal inputMethodManagerInternal, boolean isPrimaryCredential) { if (sessionId < 0) { @@ -1533,22 +1605,40 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState primaryServiceComponentName = serviceComponentName; secondaryServiceComponentName = mCredentialAutofillService; } - Slog.v(TAG, "Primary service component name: " + primaryServiceComponentName - + ", secondary service component name: " + secondaryServiceComponentName); - - mRemoteFillService = primaryServiceComponentName == null ? null - : new RemoteFillService(context, primaryServiceComponentName, userId, this, - bindInstantServiceAllowed, mCredentialAutofillService); - mSecondaryProviderHandler = secondaryServiceComponentName == null ? null - : new SecondaryProviderHandler(context, userId, bindInstantServiceAllowed, - this::onSecondaryFillResponse, secondaryServiceComponentName, - mCredentialAutofillService); + Slog.v( + TAG, + "Primary service component name: " + + primaryServiceComponentName + + ", secondary service component name: " + + secondaryServiceComponentName); + + mRemoteFillService = + primaryServiceComponentName == null + ? null + : new RemoteFillService( + context, + primaryServiceComponentName, + userId, + this, + bindInstantServiceAllowed, + mCredentialAutofillService); + mSecondaryProviderHandler = + secondaryServiceComponentName == null + ? null + : new SecondaryProviderHandler( + context, + userId, + bindInstantServiceAllowed, + this::onSecondaryFillResponse, + secondaryServiceComponentName, + mCredentialAutofillService); mActivityToken = activityToken; mHasCallback = hasCallback; mUiLatencyHistory = uiLatencyHistory; mWtfHistory = wtfHistory; - int displayId = LocalServices.getService(ActivityTaskManagerInternal.class) - .getDisplayId(activityToken); + int displayId = + LocalServices.getService(ActivityTaskManagerInternal.class) + .getDisplayId(activityToken); mContext = Helper.getDisplayContext(context, displayId); mComponentName = componentName; mCompatMode = compatMode; @@ -1557,8 +1647,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mStartTime = SystemClock.elapsedRealtime(); mLatencyBaseTime = mStartTime; mRequestCount = 0; - mPresentationStatsEventLogger = PresentationStatsEventLogger.createPresentationLog( - sessionId, uid, mLatencyBaseTime); + mPresentationStatsEventLogger = + PresentationStatsEventLogger.createPresentationLog( + sessionId, uid, mLatencyBaseTime); mFillRequestEventLogger = FillRequestEventLogger.forSessionId(sessionId); mFillResponseEventLogger = FillResponseEventLogger.forSessionId(sessionId); mSessionCommittedEventLogger = SessionCommittedEventLogger.forSessionId(sessionId); @@ -1574,33 +1665,39 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState setClientLocked(client); } - mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal, - userId, componentName, handler, mLock, - new InlineFillUi.InlineUiEventCallback() { - @Override - public void notifyInlineUiShown(AutofillId autofillId) { - notifyFillUiShown(autofillId); - } + mInlineSessionController = + new AutofillInlineSessionController( + inputMethodManagerInternal, + userId, + componentName, + handler, + mLock, + new InlineFillUi.InlineUiEventCallback() { + @Override + public void notifyInlineUiShown(AutofillId autofillId) { + notifyFillUiShown(autofillId); + } - @Override - public void notifyInlineUiHidden(AutofillId autofillId) { - notifyFillUiHidden(autofillId); - } - }); + @Override + public void notifyInlineUiHidden(AutofillId autofillId) { + notifyFillUiHidden(autofillId); + } + }); - mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); + mMetricsLogger.write( + newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); mLogViewEntered = false; } private ComponentName getCredentialAutofillService(Context context) { ComponentName componentName = null; - String credentialManagerAutofillCompName = context.getResources().getString( - R.string.config_defaultCredentialManagerAutofillService); + String credentialManagerAutofillCompName = + context.getResources() + .getString(R.string.config_defaultCredentialManagerAutofillService); if (credentialManagerAutofillCompName != null && !credentialManagerAutofillCompName.isEmpty()) { - componentName = ComponentName.unflattenFromString( - credentialManagerAutofillCompName); + componentName = ComponentName.unflattenFromString(credentialManagerAutofillCompName); } if (componentName == null) { Slog.w(TAG, "Invalid CredentialAutofillService"); @@ -1614,7 +1711,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @return The activity token */ @GuardedBy("mLock") - @NonNull IBinder getActivityTokenLocked() { + @NonNull + IBinder getActivityTokenLocked() { return mActivityToken; } @@ -1627,8 +1725,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#switchActivity() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#switchActivity() rejected - session: " + + id + + " destroyed"); return; } mActivityToken = newActivity; @@ -1643,17 +1744,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void setClientLocked(@NonNull IBinder client) { unlinkClientVultureLocked(); mClient = IAutoFillManagerClient.Stub.asInterface(client); - mClientVulture = () -> { - synchronized (mLock) { - Slog.d(TAG, "handling death of " + mActivityToken + " when saving=" - + mSessionFlags.mShowingSaveUi); - if (mSessionFlags.mShowingSaveUi) { - mUi.hideFillUi(this); - } else { - mUi.destroyAll(mPendingSaveUi, this, false); - } - } - }; + mClientVulture = + () -> { + synchronized (mLock) { + Slog.d( + TAG, + "handling death of " + + mActivityToken + + " when saving=" + + mSessionFlags.mShowingSaveUi); + if (mSessionFlags.mShowingSaveUi) { + mUi.hideFillUi(this); + } else { + mUi.destroyAll(mPendingSaveUi, this, false); + } + } + }; try { mClient.asBinder().linkToDeath(mClientVulture, 0); } catch (RemoteException e) { @@ -1676,8 +1782,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // FillServiceCallbacks @Override @SuppressWarnings("GuardedBy") - public void onFillRequestSuccess(int requestId, @Nullable FillResponse response, - @NonNull String servicePackageName, int requestFlags) { + public void onFillRequestSuccess( + int requestId, + @Nullable FillResponse response, + @NonNull String servicePackageName, + int requestFlags) { final AutofillId[] fieldClassificationIds; final LogMaker requestLog; @@ -1701,8 +1810,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getDetectionPreferenceForLogging()); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onFillRequestSuccess() rejected - session: " + + id + + " destroyed"); mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); mFillResponseEventLogger.logAndEndEvent(); return; @@ -1713,8 +1825,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // saveUi gets closed, the session will be destroyed and AutofillManager will reset // its state. Processing the fill request will result in a great chance of corrupt // state in Autofill. - Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: " - + id + " is showing saveUi"); + Slog.w( + TAG, + "Call to Session#onFillRequestSuccess() rejected - session: " + + id + + " is showing saveUi"); mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); mFillResponseEventLogger.logAndEndEvent(); return; @@ -1760,22 +1875,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - final long disableDuration = response.getDisableDuration(); final boolean autofillDisabled = disableDuration > 0; if (autofillDisabled) { final int flags = response.getFlags(); final boolean disableActivityOnly = (flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0; - notifyDisableAutofillToClient(disableDuration, - disableActivityOnly ? mComponentName : null); + notifyDisableAutofillToClient( + disableDuration, disableActivityOnly ? mComponentName : null); if (disableActivityOnly) { - mService.disableAutofillForActivity(mComponentName, disableDuration, - id, mCompatMode); + mService.disableAutofillForActivity( + mComponentName, disableDuration, id, mCompatMode); } else { - mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration, - id, mCompatMode); + mService.disableAutofillForApp( + mComponentName.getPackageName(), disableDuration, id, mCompatMode); } synchronized (mLock) { @@ -1786,17 +1900,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (triggerAugmentedAutofillLocked(requestFlags) != null) { mSessionFlags.mAugmentedAutofillOnly = true; if (sDebug) { - Slog.d(TAG, "Service disabled autofill for " + mComponentName - + ", but session is kept for augmented autofill only"); + Slog.d( + TAG, + "Service disabled autofill for " + + mComponentName + + ", but session is kept for augmented autofill only"); } return; } } if (sDebug) { - final StringBuilder message = new StringBuilder("Service disabled autofill for ") + final StringBuilder message = + new StringBuilder("Service disabled autofill for ") .append(mComponentName) - .append(": flags=").append(flags) + .append(": flags=") + .append(flags) .append(", duration="); TimeUtils.formatDuration(disableDuration, message); Slog.d(TAG, message.toString()); @@ -1816,8 +1935,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (requestLog != null) { - requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, - response.getDatasets() == null ? 0 : response.getDatasets().size()); + requestLog.addTaggedData( + MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, + response.getDatasets() == null ? 0 : response.getDatasets().size()); if (fieldClassificationIds != null) { requestLog.addTaggedData( MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS, @@ -1840,10 +1960,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - @GuardedBy("mLock") - private void processResponseLockedForPcc(@NonNull FillResponse response, - @Nullable Bundle newClientState, int flags) { + private void processResponseLockedForPcc( + @NonNull FillResponse response, @Nullable Bundle newClientState, int flags) { if (DBG) { Slog.d(TAG, "DBG: Initial response: " + response); } @@ -1851,9 +1970,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState response = getEffectiveFillResponse(response); if (isEmptyResponse(response)) { // Treat it as a null response. - processNullResponseLocked( - response != null ? response.getRequestId() : 0, - flags); + processNullResponseLocked(response != null ? response.getRequestId() : 0, flags); return; } if (DBG) { @@ -1870,9 +1987,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return ((response.getDatasets() == null || response.getDatasets().isEmpty()) && response.getAuthentication() == null && (saveInfo == null - || (ArrayUtils.isEmpty(saveInfo.getOptionalIds()) - && ArrayUtils.isEmpty(saveInfo.getRequiredIds()) - && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0))) + || (ArrayUtils.isEmpty(saveInfo.getOptionalIds()) + && ArrayUtils.isEmpty(saveInfo.getRequiredIds()) + && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0))) && (ArrayUtils.isEmpty(response.getFieldClassificationIds()))); } } @@ -1884,10 +2001,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState computeDatasetsForProviderAndUpdateContainer(response, autofillProviderContainer); if (DBG) { - Slog.d(TAG, "DBG: computeDatasetsForProviderAndUpdateContainer: " - + autofillProviderContainer); + Slog.d( + TAG, + "DBG: computeDatasetsForProviderAndUpdateContainer: " + + autofillProviderContainer); } - if (!mService.isPccClassificationEnabled()) { + if (!mService.isPccClassificationEnabled()) { if (sVerbose) { Slog.v(TAG, "PCC classification is disabled"); } @@ -1897,10 +2016,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mClassificationState.mState != ClassificationState.STATE_RESPONSE || mClassificationState.mLastFieldClassificationResponse == null) { if (sVerbose) { - Slog.v(TAG, "PCC classification no last response:" - + (mClassificationState.mLastFieldClassificationResponse == null) - + " ,ineligible state=" - + (mClassificationState.mState != ClassificationState.STATE_RESPONSE)); + Slog.v( + TAG, + "PCC classification no last response:" + + (mClassificationState.mLastFieldClassificationResponse + == null) + + " ,ineligible state=" + + (mClassificationState.mState + != ClassificationState.STATE_RESPONSE)); } return createShallowCopy(response, autofillProviderContainer); } @@ -1966,8 +2089,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis( (int) (fillRequestReceivedRelativeTimestamp)); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onSecondaryFillResponse() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onSecondaryFillResponse() rejected - session: " + + id + + " destroyed"); mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); mFillResponseEventLogger.logAndEndEvent(); return; @@ -1981,7 +2107,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSecondaryResponses = new SparseArray<>(2); } mSecondaryResponses.put(fillResponse.getRequestId(), fillResponse); - setViewStatesLocked(fillResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false, + setViewStatesLocked( + fillResponse, + ViewState.STATE_FILLABLE, + /* clearResponse= */ false, /* isPrimary= */ false); // Updates the UI, if necessary. @@ -1997,16 +2126,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private FillResponse createShallowCopy( FillResponse response, DatasetComputationContainer container) { return FillResponse.shallowCopy( - response, - new ArrayList<>(container.mDatasets), - getEligibleSaveInfo(response)); + response, new ArrayList<>(container.mDatasets), getEligibleSaveInfo(response)); } private SaveInfo getEligibleSaveInfo(FillResponse response) { SaveInfo saveInfo = response.getSaveInfo(); - if (saveInfo == null || (!ArrayUtils.isEmpty(saveInfo.getOptionalIds()) - || !ArrayUtils.isEmpty(saveInfo.getRequiredIds()) - || (saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) != 0)) { + if (saveInfo == null + || (!ArrayUtils.isEmpty(saveInfo.getOptionalIds()) + || !ArrayUtils.isEmpty(saveInfo.getRequiredIds()) + || (saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) != 0)) { return saveInfo; } synchronized (mLock) { @@ -2019,12 +2147,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState ArraySet ids = new ArraySet<>(); int saveType = saveInfo.getType(); if (saveType == SaveInfo.SAVE_DATA_TYPE_GENERIC) { - for (Set autofillIds: hintsToAutofillIdMap.values()) { + for (Set autofillIds : hintsToAutofillIdMap.values()) { ids.addAll(autofillIds); } } else { Set hints = HintsHelper.getHintsForSaveType(saveType); - for (Map.Entry> entry: hintsToAutofillIdMap.entrySet()) { + for (Map.Entry> entry : hintsToAutofillIdMap.entrySet()) { String hint = entry.getKey(); if (hints.contains(hint)) { ids.addAll(entry.getValue()); @@ -2039,9 +2167,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - /** - * A private class to hold & compute datasets to be shown - */ + /** A private class to hold & compute datasets to be shown */ private static class DatasetComputationContainer { // List of all autofill ids that have a corresponding datasets Set mAutofillIds = new LinkedHashSet<>(); @@ -2103,9 +2229,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Computes datasets that are eligible to be shown based on provider detections. - * Datasets are populated in the provided container for them to be later merged with the - * PCC eligible datasets based on preference strategy. + * Computes datasets that are eligible to be shown based on provider detections. Datasets are + * populated in the provided container for them to be later merged with the PCC eligible + * datasets based on preference strategy. + * * @param response * @param container */ @@ -2210,9 +2337,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Computes datasets that are eligible to be shown based on PCC detections. - * Datasets are populated in the provided container for them to be later merged with the - * provider eligible datasets based on preference strategy. + * Computes datasets that are eligible to be shown based on PCC detections. Datasets are + * populated in the provided container for them to be later merged with the provider eligible + * datasets based on preference strategy. + * * @param response * @param container */ @@ -2267,14 +2395,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // For that, there has to be a datatype detected by PCC, and the dataset // for that datatype provided by the provider. AutofillId autofillId = dataset.getFieldIds().get(j); - if (!mClassificationState.mClassificationCombinedHintsMap - .containsKey(autofillId)) { + if (!mClassificationState.mClassificationCombinedHintsMap.containsKey( + autofillId)) { additionalEligibleAutofillIds.add(autofillId); additionalDatasetAutofillIds.add(autofillId); // For each of the field, copy over values. - copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues, - fieldPresentations, fieldDialogPresentations, - fieldInlinePresentations, fieldInlineTooltipPresentations, + copyFieldsFromDataset( + dataset, + j, + autofillId, + fieldIds, + fieldValues, + fieldPresentations, + fieldDialogPresentations, + fieldInlinePresentations, + fieldInlineTooltipPresentations, fieldFilters); } continue; @@ -2292,9 +2427,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState eligibleAutofillIds.add(autofillId); datasetAutofillIds.add(autofillId); // For each of the field, copy over values. - copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues, - fieldPresentations, fieldDialogPresentations, - fieldInlinePresentations, fieldInlineTooltipPresentations, + copyFieldsFromDataset( + dataset, + j, + autofillId, + fieldIds, + fieldValues, + fieldPresentations, + fieldDialogPresentations, + fieldInlinePresentations, + fieldInlineTooltipPresentations, fieldFilters); } } @@ -2364,8 +2506,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState fieldPresentations.add(dataset.getFieldPresentation(index)); fieldDialogPresentations.add(dataset.getFieldDialogPresentation(index)); fieldInlinePresentations.add(dataset.getFieldInlinePresentation(index)); - fieldInlineTooltipPresentations.add( - dataset.getFieldInlineTooltipPresentation(index)); + fieldInlineTooltipPresentations.add(dataset.getFieldInlineTooltipPresentation(index)); fieldFilters.add(dataset.getFilter(index)); } @@ -2395,16 +2536,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState unregisterDelayedFillBroadcastLocked(); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId - + ") rejected - session: " + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onFillRequestFailureOrTimeout(req=" + + requestId + + ") rejected - session: " + + id + + " destroyed"); mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); mFillResponseEventLogger.logAndEndEvent(); return; } if (sDebug) { - Slog.d(TAG, "finishing session due to service " - + (timedOut ? "timeout" : "failure")); + Slog.d( + TAG, + "finishing session due to service " + (timedOut ? "timeout" : "failure")); } mService.resetLastResponse(); mLastFillDialogTriggerIds = null; @@ -2418,12 +2565,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int targetSdk = mService.getTargedSdkLocked(); if (targetSdk >= Build.VERSION_CODES.Q) { showMessage = false; - Slog.w(TAG, "onFillRequestFailureOrTimeout(): not showing '" + message - + "' because service's targetting API " + targetSdk); + Slog.w( + TAG, + "onFillRequestFailureOrTimeout(): not showing '" + + message + + "' because service's targeting API " + + targetSdk); } if (message != null) { - requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, - message.length()); + requestLog.addTaggedData( + MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, message.length()); } } @@ -2445,8 +2596,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis(); mFillResponseEventLogger.logAndEndEvent(); } - notifyUnavailableToClient(AutofillManager.STATE_UNKNOWN_FAILED, - /* autofillableIds= */ null); + notifyUnavailableToClient( + AutofillManager.STATE_UNKNOWN_FAILED, /* autofillableIds= */ null); if (showMessage) { getUiForShowing().showError(message, this); } @@ -2455,8 +2606,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // FillServiceCallbacks @Override - public void onSaveRequestSuccess(@NonNull String servicePackageName, - @Nullable IntentSender intentSender) { + public void onSaveRequestSuccess( + @NonNull String servicePackageName, @Nullable IntentSender intentSender) { synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; // Log onSaveRequest result. @@ -2464,16 +2615,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSaveEventLogger.maybeSetLatencySaveFinishMillis(); mSaveEventLogger.logAndEndEvent(); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onSaveRequestSuccess() rejected - session: " + + id + + " destroyed"); return; } } - LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) - .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN); + LogMaker log = + newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) + .setType( + intentSender == null + ? MetricsEvent.TYPE_SUCCESS + : MetricsEvent.TYPE_OPEN); mMetricsLogger.write(log); - if (intentSender != null) { if (sDebug) Slog.d(TAG, "Starting intent sender on save()"); startIntentSenderAndFinishSession(intentSender); @@ -2485,8 +2642,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // FillServiceCallbacks @Override - public void onSaveRequestFailure(@Nullable CharSequence message, - @NonNull String servicePackageName) { + public void onSaveRequestFailure( + @Nullable CharSequence message, @NonNull String servicePackageName) { boolean showMessage = !TextUtils.isEmpty(message); synchronized (mLock) { @@ -2495,28 +2652,34 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSaveEventLogger.maybeSetLatencySaveFinishMillis(); mSaveEventLogger.logAndEndEvent(); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onSaveRequestFailure() rejected - session: " + + id + + " destroyed"); return; } if (showMessage) { final int targetSdk = mService.getTargedSdkLocked(); if (targetSdk >= Build.VERSION_CODES.Q) { showMessage = false; - Slog.w(TAG, "onSaveRequestFailure(): not showing '" + message - + "' because service's targetting API " + targetSdk); + Slog.w( + TAG, + "onSaveRequestFailure(): not showing '" + + message + + "' because service's targeting API " + + targetSdk); } } } final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) - .setType(MetricsEvent.TYPE_FAILURE); + .setType(MetricsEvent.TYPE_FAILURE); if (message != null) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, message.length()); } mMetricsLogger.write(log); - if (showMessage) { getUiForShowing().showError(message, this); } @@ -2525,8 +2688,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // FillServiceCallbacks @Override - public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse - convertCredentialResponse) { + public void onConvertCredentialRequestSuccess( + @NonNull ConvertCredentialResponse convertCredentialResponse) { Dataset dataset = convertCredentialResponse.getDataset(); Bundle clientState = convertCredentialResponse.getClientState(); if (dataset != null) { @@ -2534,15 +2697,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (clientState != null) { requestId = clientState.getInt(EXTRA_AUTOFILL_REQUEST_ID); } else { - Slog.e(TAG, "onConvertCredentialRequestSuccess(): client state is null, this " - + "would cause loss in logging."); + Slog.e( + TAG, + "onConvertCredentialRequestSuccess(): client state is null, this " + + "would cause loss in logging."); } // TODO: Add autofill related logging; consider whether to log the index - fill(requestId, /* datasetIndex=*/ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET); + fill(requestId, /* datasetIndex= */ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET); } else { // TODO: Add logging to log this error case - Slog.e(TAG, "onConvertCredentialRequestSuccess(): dataset inside response is " - + "null"); + Slog.e( + TAG, + "onConvertCredentialRequestSuccess(): dataset inside response is " + "null"); } } @@ -2550,11 +2716,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Gets the {@link FillContext} for a request. * * @param requestId The id of the request - * * @return The context or {@code null} if there is no context */ @GuardedBy("mLock") - @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) { + @Nullable + private FillContext getFillContextByRequestIdLocked(int requestId) { if (mContexts == null) { return null; } @@ -2582,19 +2748,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override - public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras, - int uiType) { + public void authenticate( + int requestId, int datasetIndex, IntentSender intent, Bundle extras, int uiType) { if (sDebug) { - Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex - + "; intentSender=" + intent); + Slog.d( + TAG, + "authenticate(): requestId=" + + requestId + + "; datasetIdx=" + + datasetIndex + + "; intentSender=" + + intent); } final Intent fillInIntent; synchronized (mLock) { mPresentationStatsEventLogger.maybeSetAuthenticationType( - AUTHENTICATION_TYPE_FULL_AUTHENTICATION); + AUTHENTICATION_TYPE_FULL_AUTHENTICATION); if (mDestroyed) { - Slog.w(TAG, "Call to Session#authenticate() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#authenticate() rejected - session: " + id + " destroyed"); return; } fillInIntent = createAuthFillInIntentLocked(requestId, extras); @@ -2607,10 +2780,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.setAuthenticationSelected(id, mClientState, uiType); final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex); - mHandler.sendMessage(obtainMessage( - Session::startAuthentication, - this, authenticationId, intent, fillInIntent, - /* authenticateInline= */ uiType == UI_TYPE_INLINE)); + mHandler.sendMessage( + obtainMessage( + Session::startAuthentication, + this, + authenticationId, + intent, + fillInIntent, + /* authenticateInline= */ uiType == UI_TYPE_INLINE)); } // AutoFillUiCallback @@ -2618,14 +2795,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void fill(int requestId, int datasetIndex, Dataset dataset, int uiType) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#fill() rejected - session: " - + id + " destroyed"); + Slog.w(TAG, "Call to Session#fill() rejected - session: " + id + " destroyed"); return; } } - mHandler.sendMessage(obtainMessage( - Session::autoFill, - this, requestId, datasetIndex, dataset, true, uiType)); + mHandler.sendMessage( + obtainMessage( + Session::autoFill, this, requestId, datasetIndex, dataset, true, uiType)); } // AutoFillUiCallback @@ -2633,15 +2809,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void save() { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#save() rejected - session: " - + id + " destroyed"); + Slog.w(TAG, "Call to Session#save() rejected - session: " + id + " destroyed"); return; } mSaveEventLogger.maybeSetLatencySaveRequestMillis(); } - mHandler.sendMessage(obtainMessage( - AutofillManagerServiceImpl::handleSessionSave, - mService, this)); + mHandler.sendMessage( + obtainMessage(AutofillManagerServiceImpl::handleSessionSave, mService, this)); } // AutoFillUiCallback @@ -2650,13 +2824,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; if (mDestroyed) { - Slog.w(TAG, "Call to Session#cancelSave() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#cancelSave() rejected - session: " + id + " destroyed"); return; } } - mHandler.sendMessage(obtainMessage( - Session::removeFromService, this)); + mHandler.sendMessage(obtainMessage(Session::removeFromService, this)); } // AutofillUiCallback @@ -2692,26 +2866,34 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override - public void requestShowFillUi(AutofillId id, int width, int height, - IAutofillWindowPresenter presenter) { + public void requestShowFillUi( + AutofillId id, int width, int height, IAutofillWindowPresenter presenter) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#requestShowFillUi() rejected - session: " + + id + + " destroyed"); return; } if (id.equals(mCurrentViewId)) { try { final ViewState view = mViewStates.get(id); - mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(), - presenter); + mClient.requestShowFillUi( + this.id, id, width, height, view.getVirtualBounds(), presenter); } catch (RemoteException e) { Slog.e(TAG, "Error requesting to show fill UI", e); } } else { if (sDebug) { - Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view (" - + mCurrentViewId + ") anymore"); + Slog.d( + TAG, + "Do not show full UI on " + + id + + " as it is not the current view (" + + mCurrentViewId + + ") anymore"); } } } @@ -2722,8 +2904,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#dispatchUnhandledKey() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#dispatchUnhandledKey() rejected - session: " + + id + + " destroyed"); return; } if (id.equals(mCurrentViewId)) { @@ -2733,8 +2918,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.e(TAG, "Error requesting to dispatch unhandled key", e); } } else { - Slog.w(TAG, "Do not dispatch unhandled key on " + id - + " as it is not the current view (" + mCurrentViewId + ") anymore"); + Slog.w( + TAG, + "Do not dispatch unhandled key on " + + id + + " as it is not the current view (" + + mCurrentViewId + + ") anymore"); } } } @@ -2790,17 +2980,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void startIntentSender(IntentSender intentSender, Intent intent) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#startIntentSender() rejected - session: " + + id + + " destroyed"); return; } if (intent == null) { removeFromServiceLocked(); } } - mHandler.sendMessage(obtainMessage( - Session::doStartIntentSender, - this, intentSender, intent)); + mHandler.sendMessage( + obtainMessage(Session::doStartIntentSender, this, intentSender, intent)); } // AutoFillUiCallback @@ -2865,13 +3057,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void setAuthenticationResultLocked(Bundle data, int authenticationId) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#setAuthenticationResultLocked() rejected - session: " + + id + + " destroyed"); return; } if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): id= " + authenticationId - + ", data=" + data); + Slog.d( + TAG, + "setAuthenticationResultLocked(): id= " + authenticationId + ", data=" + data); } final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId); if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) { @@ -2890,9 +3086,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState removeFromService(); return; } - final FillResponse authenticatedResponse = mRequestId.isSecondaryProvider(requestId) - ? mSecondaryResponses.get(requestId) - : mResponses.get(requestId); + final FillResponse authenticatedResponse = + mRequestId.isSecondaryProvider(requestId) + ? mSecondaryResponses.get(requestId) + : mResponses.get(requestId); if (authenticatedResponse == null || data == null) { Slog.w(TAG, "no authenticated response"); mPresentationStatsEventLogger.maybeSetAuthenticationResult( @@ -2902,8 +3099,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId( - authenticationId); + final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId(authenticationId); Dataset dataset = null; // Authenticated a dataset - reset view state regardless if we got a response or a dataset if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { @@ -2922,33 +3118,45 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionFlags.mExpiredResponse = false; final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT); - final GetCredentialException exception = data.getSerializable( - CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, - GetCredentialException.class); + final GetCredentialException exception = + data.getSerializable( + CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, + GetCredentialException.class); final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE); if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result - + ", clientState=" + newClientState + ", authenticationId=" + authenticationId); - } - if (Flags.autofillCredmanDevIntegration() && exception != null + Slog.d( + TAG, + "setAuthenticationResultLocked(): result=" + + result + + ", clientState=" + + newClientState + + ", authenticationId=" + + authenticationId); + } + if (Flags.autofillCredmanDevIntegration() + && exception != null && !exception.getType().equals(GetCredentialException.TYPE_USER_CANCELED)) { if (dataset != null && dataset.getFieldIds().size() == 1) { if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): result returns with" - + "Credential Manager Exception"); + Slog.d( + TAG, + "setAuthenticationResultLocked(): result returns with" + + "Credential Manager Exception"); } AutofillId autofillId = dataset.getFieldIds().get(0); - sendCredentialManagerResponseToApp(/*response=*/ null, - (GetCredentialException) exception, autofillId); + sendCredentialManagerResponseToApp( + /* response= */ null, (GetCredentialException) exception, autofillId); } return; } if (result instanceof FillResponse) { if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): received FillResponse from" - + " authentication flow"); + Slog.d( + TAG, + "setAuthenticationResultLocked(): received FillResponse from" + + " authentication flow"); } logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_AUTHENTICATED); mPresentationStatsEventLogger.maybeSetAuthenticationResult( @@ -2963,33 +3171,40 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (dataset != null && dataset.getFieldIds().size() == 1) { AutofillId autofillId = dataset.getFieldIds().get(0); if (sDebug) { - Slog.d(TAG, "Received GetCredentialResponse from authentication flow," - + "for autofillId: " + autofillId); + Slog.d( + TAG, + "Received GetCredentialResponse from authentication flow," + + "for autofillId: " + + autofillId); } - sendCredentialManagerResponseToApp(response, - /*exception=*/ null, autofillId); + sendCredentialManagerResponseToApp(response, /* exception= */ null, autofillId); } } else if (Flags.autofillCredmanIntegration()) { - Dataset datasetFromCredentialResponse = getDatasetFromCredentialResponse( - (GetCredentialResponse) result); + Dataset datasetFromCredentialResponse = + getDatasetFromCredentialResponse((GetCredentialResponse) result); if (datasetFromCredentialResponse != null) { - autoFill(requestId, datasetIdx, datasetFromCredentialResponse, - false, UI_TYPE_UNKNOWN); + autoFill( + requestId, + datasetIdx, + datasetFromCredentialResponse, + false, + UI_TYPE_UNKNOWN); } } } else if (result instanceof Dataset) { if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): received Dataset from" - + " authentication flow"); + Slog.d( + TAG, + "setAuthenticationResultLocked(): received Dataset from" + + " authentication flow"); } if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { - logAuthenticationStatusLocked(requestId, - MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED); + logAuthenticationStatusLocked( + requestId, MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_SUCCESS); if (newClientState != null) { - if (sDebug) - Slog.d(TAG, "Updating client state from auth dataset"); + if (sDebug) Slog.d(TAG, "Updating client state from auth dataset"); mClientState = newClientState; } Dataset datasetFromResult = getEffectiveDatasetForAuthentication((Dataset) result); @@ -2999,10 +3214,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } autoFill(requestId, datasetIdx, datasetFromResult, false, UI_TYPE_UNKNOWN); } else { - Slog.w(TAG, "invalid index (" + datasetIdx + ") for authentication id " - + authenticationId); - logAuthenticationStatusLocked(requestId, - MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION); + Slog.w( + TAG, + "invalid index (" + + datasetIdx + + ") for authentication id " + + authenticationId); + logAuthenticationStatusLocked( + requestId, MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_FAILURE); } @@ -3010,8 +3229,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (result != null) { Slog.w(TAG, "service returned invalid auth type: " + result); } - logAuthenticationStatusLocked(requestId, - MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION); + logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_FAILURE); processNullResponseLocked(requestId, 0); @@ -3036,8 +3254,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "DBG: authenticated effective response: " + response); } if (response == null || response.getDatasets().size() == 0) { - Log.wtf(TAG, "No datasets in fill response on authentication. response = " - + (response == null ? "null" : response.toString())); + Log.wtf( + TAG, + "No datasets in fill response on authentication. response = " + + (response == null ? "null" : response.toString())); return authenticatedDataset; } List datasets = response.getDatasets(); @@ -3047,8 +3267,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState for (Dataset dataset : datasets) { if (!dataset.getFieldIds().isEmpty()) { for (int i = 0; i < dataset.getFieldIds().size(); i++) { - builder.setField(dataset.getFieldIds().get(i), - new Field.Builder().setValue(dataset.getFieldValues().get(i)) + builder.setField( + dataset.getFieldIds().get(i), + new Field.Builder() + .setValue(dataset.getFieldValues().get(i)) .build()); } } @@ -3063,12 +3285,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Returns whether the dataset returned from the authentication result is ephemeral or not. - * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more - * information. + * Returns whether the dataset returned from the authentication result is ephemeral or not. See + * {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more information. */ - private static boolean isAuthResultDatasetEphemeral(@Nullable Dataset oldDataset, - @NonNull Bundle authResultData) { + private static boolean isAuthResultDatasetEphemeral( + @Nullable Dataset oldDataset, @NonNull Bundle authResultData) { if (authResultData.containsKey( AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) { return authResultData.getBoolean( @@ -3079,11 +3300,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * A dataset can potentially have multiple fields, and it's possible that some of the fields' - * has inline presentation and some don't. It's also possible that some of the fields' - * inline presentation is pinned and some isn't. So the concept of whether a dataset is - * pinned or not is ill-defined. Here we say a dataset is pinned if any of the field has a - * pinned inline presentation in the dataset. It's not ideal but hopefully it is sufficient - * for most of the cases. + * has inline presentation and some don't. It's also possible that some of the fields' inline + * presentation is pinned and some isn't. So the concept of whether a dataset is pinned or not + * is ill-defined. Here we say a dataset is pinned if any of the field has a pinned inline + * presentation in the dataset. It's not ideal but hopefully it is sufficient for most of the + * cases. */ private static boolean isPinnedDataset(@Nullable Dataset dataset) { if (dataset != null && dataset.getFieldIds() != null) { @@ -3100,16 +3321,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void setAuthenticationResultForAugmentedAutofillLocked(Bundle data, int authId) { - final Dataset dataset = (data == null) ? null : - data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT, android.service.autofill.Dataset.class); + final Dataset dataset = + (data == null) + ? null + : data.getParcelable( + AutofillManager.EXTRA_AUTHENTICATION_RESULT, + android.service.autofill.Dataset.class); if (sDebug) { - Slog.d(TAG, "Auth result for augmented autofill: sessionId=" + id - + ", authId=" + authId + ", dataset=" + dataset); - } - final AutofillId fieldId = (dataset != null && dataset.getFieldIds().size() == 1) - ? dataset.getFieldIds().get(0) : null; - final AutofillValue value = (dataset != null && dataset.getFieldValues().size() == 1) - ? dataset.getFieldValues().get(0) : null; + Slog.d( + TAG, + "Auth result for augmented autofill: sessionId=" + + id + + ", authId=" + + authId + + ", dataset=" + + dataset); + } + final AutofillId fieldId = + (dataset != null && dataset.getFieldIds().size() == 1) + ? dataset.getFieldIds().get(0) + : null; + final AutofillValue value = + (dataset != null && dataset.getFieldValues().size() == 1) + ? dataset.getFieldValues().get(0) + : null; final ClipData content = (dataset != null) ? dataset.getFieldContent() : null; if (fieldId == null || (value == null && content == null)) { if (sDebug) { @@ -3154,8 +3389,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Fill the value into the field. if (sDebug) { - Slog.d(TAG, "Filling after auth: fieldId=" + fieldId + ", value=" + value - + ", content=" + content); + Slog.d( + TAG, + "Filling after auth: fieldId=" + + fieldId + + ", value=" + + value + + ", content=" + + content); } try { if (content != null) { @@ -3164,8 +3405,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mClient.autofill(id, dataset.getFieldIds(), dataset.getFieldValues(), true); } } catch (RemoteException e) { - Slog.w(TAG, "Error filling after auth: fieldId=" + fieldId + ", value=" + value - + ", content=" + content, e); + Slog.w( + TAG, + "Error filling after auth: fieldId=" + + fieldId + + ", value=" + + value + + ", content=" + + content, + e); } // Clear the suggestions since the user already accepted one of them. @@ -3175,8 +3423,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void setHasCallbackLocked(boolean hasIt) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#setHasCallbackLocked() rejected - session: " + + id + + " destroyed"); return; } mHasCallback = hasIt; @@ -3185,9 +3436,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") @Nullable private FillResponse getLastResponseLocked(@Nullable String logPrefixFmt) { - final String logPrefix = sDebug && logPrefixFmt != null - ? String.format(logPrefixFmt, this.id) - : null; + final String logPrefix = + sDebug && logPrefixFmt != null ? String.format(logPrefixFmt, this.id) : null; if (mContexts == null) { if (logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts"); return null; @@ -3204,16 +3454,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int lastResponseIdx = getLastResponseIndexLocked(); if (lastResponseIdx < 0) { if (logPrefix != null) { - Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses - + ", mViewStates=" + mViewStates); + Slog.w( + TAG, + logPrefix + + ": did not get last response. mResponses=" + + mResponses + + ", mViewStates=" + + mViewStates); } return null; } final FillResponse response = mResponses.valueAt(lastResponseIdx); if (sVerbose && logPrefix != null) { - Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts - + ", mViewStates=" + mViewStates); + Slog.v( + TAG, + logPrefix + + ": mResponses=" + + mResponses + + ", mContexts=" + + mContexts + + ", mViewStates=" + + mViewStates); } return response; } @@ -3232,9 +3494,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Get statistic information of save info in current session. Specifically - * 1. how many save info the current session has. - * 2. How many distinct save data types current session has. + * Get statistic information of save info in current session. Specifically 1. how many save info + * the current session has. 2. How many distinct save data types current session has. * * @return SaveInfoStats returns the above two number in a SaveInfoStats object */ @@ -3255,12 +3516,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ public void logContextCommitted() { if (sVerbose) { - Slog.v(TAG, "logContextCommitted (" + id + "): commit_reason:" + COMMIT_REASON_UNKNOWN - + " no_save_reason:" + Event.NO_SAVE_UI_REASON_NONE); - } - mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this, - Event.NO_SAVE_UI_REASON_NONE, - COMMIT_REASON_UNKNOWN)); + Slog.v( + TAG, + "logContextCommitted (" + + id + + "): commit_reason:" + + COMMIT_REASON_UNKNOWN + + " no_save_reason:" + + Event.NO_SAVE_UI_REASON_NONE); + } + mHandler.sendMessage( + obtainMessage( + Session::handleLogContextCommitted, + this, + Event.NO_SAVE_UI_REASON_NONE, + COMMIT_REASON_UNKNOWN)); synchronized (mLock) { logAllEventsLocked(COMMIT_REASON_UNKNOWN); } @@ -3274,16 +3544,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @param saveDialogNotShowReason The reason why a save dialog was not shown. * @param commitReason The reason why context is committed. */ - @GuardedBy("mLock") - public void logContextCommittedLocked(@NoSaveReason int saveDialogNotShowReason, - @AutofillCommitReason int commitReason) { + public void logContextCommittedLocked( + @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { if (sVerbose) { - Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason - + " no_save_reason:" + saveDialogNotShowReason); - } - mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this, - saveDialogNotShowReason, commitReason)); + Slog.v( + TAG, + "logContextCommittedLocked (" + + id + + "): commit_reason:" + + commitReason + + " no_save_reason:" + + saveDialogNotShowReason); + } + mHandler.sendMessage( + obtainMessage( + Session::handleLogContextCommitted, + this, + saveDialogNotShowReason, + commitReason)); mSessionCommittedEventLogger.maybeSetCommitReason(commitReason); mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); @@ -3295,8 +3574,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE); } - private void handleLogContextCommitted(@NoSaveReason int saveDialogNotShowReason, - @AutofillCommitReason int commitReason) { + private void handleLogContextCommitted( + @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { final FillResponse lastResponse; synchronized (mLock) { lastResponse = getLastResponseLocked("logContextCommited(%s)"); @@ -3326,31 +3605,42 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Sets field classification scores if (userData != null && fcStrategy != null) { - logFieldClassificationScore(fcStrategy, userData, saveDialogNotShowReason, - commitReason); + logFieldClassificationScore( + fcStrategy, userData, saveDialogNotShowReason, commitReason); } else { logContextCommitted(null, null, saveDialogNotShowReason, commitReason); } } - private void logContextCommitted(@Nullable ArrayList detectedFieldIds, + private void logContextCommitted( + @Nullable ArrayList detectedFieldIds, @Nullable ArrayList detectedFieldClassifications, @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { synchronized (mLock) { - logContextCommittedLocked(detectedFieldIds, detectedFieldClassifications, - saveDialogNotShowReason, commitReason); + logContextCommittedLocked( + detectedFieldIds, + detectedFieldClassifications, + saveDialogNotShowReason, + commitReason); } } @GuardedBy("mLock") - private void logContextCommittedLocked(@Nullable ArrayList detectedFieldIds, + private void logContextCommittedLocked( + @Nullable ArrayList detectedFieldIds, @Nullable ArrayList detectedFieldClassifications, @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { if (sVerbose) { - Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason - + " no_save_reason:" + saveDialogNotShowReason); + Slog.v( + TAG, + "logContextCommittedLocked (" + + id + + "): commit_reason:" + + commitReason + + " no_save_reason:" + + saveDialogNotShowReason); } final FillResponse lastResponse = getLastResponseLocked("logContextCommited(%s)"); if (lastResponse == null) return; @@ -3423,8 +3713,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue currentValue = viewState.getCurrentValue(); if (autofilledValue != null && autofilledValue.equals(currentValue)) { if (sDebug) { - Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState - + " because it has same value that was autofilled"); + Slog.d( + TAG, + "logContextCommitted(): ignoring changed " + + viewState + + " because it has same value that was autofilled"); } continue; } @@ -3442,8 +3735,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue currentValue = viewState.getCurrentValue(); if (currentValue == null) { if (sDebug) { - Slog.d(TAG, "logContextCommitted(): skipping view without current " - + "value ( " + viewState + ")"); + Slog.d( + TAG, + "logContextCommitted(): skipping view without current " + + "value ( " + + viewState + + ")"); } continue; } @@ -3455,7 +3752,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final List datasets = response.getDatasets(); if (datasets == null || datasets.isEmpty()) { if (sVerbose) { - Slog.v(TAG, "logContextCommitted() no datasets at " + j); + Slog.v(TAG, "logContextCommitted() no datasets at " + j); } } else { for (int k = 0; k < datasets.size(); k++) { @@ -3463,8 +3760,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final String datasetId = dataset.getId(); if (datasetId == null) { if (sVerbose) { - Slog.v(TAG, "logContextCommitted() skipping idless " - + "dataset " + dataset); + Slog.v( + TAG, + "logContextCommitted() skipping idless " + + "dataset " + + dataset); } } else { final ArrayList values = @@ -3473,9 +3773,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue candidate = values.get(l); if (currentValue.equals(candidate)) { if (sDebug) { - Slog.d(TAG, "field " + viewState.id + " was " - + "manually filled with value set by " - + "dataset " + datasetId); + Slog.d( + TAG, + "field " + + viewState.id + + " was manually filled with" + + " value set by dataset " + + datasetId); } if (manuallyFilledIds == null) { manuallyFilledIds = new ArrayMap<>(); @@ -3524,10 +3828,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, ignoredDatasets, - changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, - manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications, - mComponentName, mCompatMode, saveDialogNotShowReason); + mService.logContextCommittedLocked( + id, + mClientState, + mSelectedDatasetIds, + ignoredDatasets, + changedFieldIds, + changedDatasetIds, + manuallyFilledFieldIds, + manuallyFilledDatasetIds, + detectedFieldIds, + detectedFieldClassifications, + mComponentName, + mCompatMode, + saveDialogNotShowReason); mSessionCommittedEventLogger.maybeSetCommitReason(commitReason); mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); mSaveEventLogger.maybeSetSaveUiNotShownReason(saveDialogNotShowReason); @@ -3537,7 +3851,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for * {@code fieldId} based on its {@code currentValue} and {@code userData}. */ - private void logFieldClassificationScore(@NonNull FieldClassificationStrategy fcStrategy, + private void logFieldClassificationScore( + @NonNull FieldClassificationStrategy fcStrategy, @NonNull FieldClassificationUserData userData, @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { @@ -3555,16 +3870,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) { final int valuesLength = userValues == null ? -1 : userValues.length; final int idsLength = categoryIds == null ? -1 : categoryIds.length; - Slog.w(TAG, "setScores(): user data mismatch: values.length = " - + valuesLength + ", ids.length = " + idsLength); + Slog.w( + TAG, + "setScores(): user data mismatch: values.length = " + + valuesLength + + ", ids.length = " + + idsLength); return; } final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize(); final ArrayList detectedFieldIds = new ArrayList<>(maxFieldsSize); - final ArrayList detectedFieldClassifications = new ArrayList<>( - maxFieldsSize); + final ArrayList detectedFieldClassifications = + new ArrayList<>(maxFieldsSize); final Collection viewStates; synchronized (mLock) { @@ -3583,33 +3902,49 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // Then use the results, asynchronously - final RemoteCallback callback = new RemoteCallback( - new LogFieldClassificationScoreOnResultListener( - this, - saveDialogNotShowReason, - commitReason, - viewsSize, - autofillIds, - userValues, - categoryIds, - detectedFieldIds, - detectedFieldClassifications)); - - fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds, - defaultAlgorithm, defaultArgs, algorithms, args); - } - - void handleLogFieldClassificationScore(@Nullable Bundle result, int saveDialogNotShowReason, - int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues, - String[] categoryIds, ArrayList detectedFieldIds, + final RemoteCallback callback = + new RemoteCallback( + new LogFieldClassificationScoreOnResultListener( + this, + saveDialogNotShowReason, + commitReason, + viewsSize, + autofillIds, + userValues, + categoryIds, + detectedFieldIds, + detectedFieldClassifications)); + + fcStrategy.calculateScores( + callback, + currentValues, + userValues, + categoryIds, + defaultAlgorithm, + defaultArgs, + algorithms, + args); + } + + void handleLogFieldClassificationScore( + @Nullable Bundle result, + int saveDialogNotShowReason, + int commitReason, + int viewsSize, + AutofillId[] autofillIds, + String[] userValues, + String[] categoryIds, + ArrayList detectedFieldIds, ArrayList detectedFieldClassifications) { if (result == null) { if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results"); logContextCommitted(null, null, saveDialogNotShowReason, commitReason); return; } - final Scores scores = result.getParcelable(EXTRA_SCORES, - android.service.autofill.AutofillFieldClassificationService.Scores.class); + final Scores scores = + result.getParcelable( + EXTRA_SCORES, + android.service.autofill.AutofillFieldClassificationService.Scores.class); if (scores == null) { Slog.w(TAG, "No field classification score on " + result); return; @@ -3633,14 +3968,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final Float currentScore = scoresByField.get(categoryId); if (currentScore != null && currentScore > score) { if (sVerbose) { - Slog.v(TAG, "skipping score " + score - + " because it's less than " + currentScore); + Slog.v( + TAG, + "skipping score " + + score + + " because it's less than " + + currentScore); } continue; } if (sVerbose) { - Slog.v(TAG, "adding score " + score + " at index " + j + " and id " - + autofillId); + Slog.v( + TAG, + "adding score " + + score + + " at index " + + j + + " and id " + + autofillId); } scoresByField.put(categoryId, score); } else if (sVerbose) { @@ -3666,13 +4011,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e); return; } - logContextCommitted(detectedFieldIds, detectedFieldClassifications, - saveDialogNotShowReason, commitReason); + logContextCommitted( + detectedFieldIds, + detectedFieldClassifications, + saveDialogNotShowReason, + commitReason); } /** - * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} - * when necessary. + * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} when + * necessary. * *

Note: It is necessary to call logContextCommitted() first before calling this method. */ @@ -3689,11 +4037,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @NonNull public SaveResult showSaveLocked() { if (mDestroyed) { - Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#showSaveLocked() rejected - session: " + id + " destroyed"); mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SESSION_DESTROYED); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ false, Event.NO_SAVE_UI_REASON_NONE); } mSessionState = STATE_FINISHED; @@ -3705,12 +4056,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ if (mSessionFlags.mScreenHasCredmanField) { if (sVerbose) { - Slog.v(TAG, "Call to Session#showSaveLocked() rejected - " - + "there is credman field in screen"); + Slog.v( + TAG, + "Call to Session#showSaveLocked() rejected - " + + "there is credman field in screen"); } mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SCREEN_HAS_CREDMAN_FIELD); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_NONE); } @@ -3728,7 +4083,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose) Slog.v(TAG, "showSaveLocked(" + this.id + "): no saveInfo from service"); mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NO_SAVE_INFO); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_NO_SAVE_INFO); } @@ -3737,7 +4094,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) Slog.v(TAG, "showSaveLocked(" + this.id + "): service asked to delay save"); mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ false, Event.NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG); } @@ -3774,12 +4133,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Some apps clear the form before navigating to other activities. // If current value is empty, consider fall back to last cached // non-empty result first. - final AutofillValue candidateSaveValue = - viewState.getCandidateSaveValue(); + final AutofillValue candidateSaveValue = viewState.getCandidateSaveValue(); if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { if (sVerbose) { - Slog.v(TAG, "current value is empty, using cached last non-empty " - + "value instead"); + Slog.v( + TAG, + "current value is empty, using cached last non-empty " + + "value instead"); } value = candidateSaveValue; } else { @@ -3788,8 +4148,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue initialValue = getValueFromContextsLocked(id); if (initialValue != null) { if (sDebug) { - Slog.d(TAG, "Value of required field " + id + " didn't change; " - + "using initial value (" + initialValue + ") instead"); + Slog.d( + TAG, + "Value of required field " + + id + + " didn't change; " + + "using initial value (" + + initialValue + + ") instead"); } value = initialValue; } else { @@ -3821,8 +4187,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue initialValue = getValueFromContextsLocked(id); if (initialValue != null && initialValue.equals(value)) { if (sDebug) { - Slog.d(TAG, "id " + id + " is part of dataset but initial value " - + "didn't change: " + value); + Slog.d( + TAG, + "id " + + id + + " is part of dataset but initial value " + + "didn't change: " + + value); } changed = false; } else { @@ -3833,8 +4204,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (changed) { if (sDebug) { - Slog.d(TAG, "found a change on required " + id + ": " + filledValue - + " => " + value); + Slog.d( + TAG, + "found a change on required " + + id + + ": " + + filledValue + + " => " + + value); } atLeastOneChanged = true; } @@ -3844,8 +4221,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId[] optionalIds = saveInfo.getOptionalIds(); if (sVerbose) { - Slog.v(TAG, "allRequiredAreNotEmpty: " + allRequiredAreNotEmpty + " hasOptional: " - + (optionalIds != null)); + Slog.v( + TAG, + "allRequiredAreNotEmpty: " + + allRequiredAreNotEmpty + + " hasOptional: " + + (optionalIds != null)); } int saveDialogNotShowReason; if (!allRequiredAreNotEmpty) { @@ -3859,7 +4240,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // - if at least one required id changed but it was not part of a filled dataset, we // need to check if an optional id is part of a filled datased (in which case we show // Update instead of Save) - if (optionalIds!= null && (!atLeastOneChanged || !isUpdate)) { + if (optionalIds != null && (!atLeastOneChanged || !isUpdate)) { // No change on required ids yet, look for changes on optional ids. for (int i = 0; i < optionalIds.length; i++) { final AutofillId id = optionalIds[i]; @@ -3879,8 +4260,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState viewState.getCandidateSaveValue(); if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { if (sVerbose) { - Slog.v(TAG, "current value is empty, using cached last " - + "non-empty value instead"); + Slog.v( + TAG, + "current value is empty, using cached last " + + "non-empty value instead"); } currentValue = candidateSaveValue; } @@ -3897,8 +4280,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue filledValue = viewState.getAutofilledValue(); if (value != null && !value.equals(filledValue)) { if (sDebug) { - Slog.d(TAG, "found a change on optional " + id + ": " + filledValue - + " => " + value); + Slog.d( + TAG, + "found a change on optional " + + id + + ": " + + filledValue + + " => " + + value); } if (filledValue != null) { isUpdate = true; @@ -3907,12 +4296,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } atLeastOneChanged = true; } - } else { + } else { // Update current values cache based on initial value final AutofillValue initialValue = getValueFromContextsLocked(id); if (sDebug) { - Slog.d(TAG, "no current value for " + id + "; initial value is " - + initialValue); + Slog.d( + TAG, + "no current value for " + + id + + "; initial value is " + + initialValue); } if (initialValue != null) { currentValues.put(id, initialValue); @@ -3935,17 +4328,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { isValid = validator.isValid(this); if (sDebug) Slog.d(TAG, validator + " returned " + isValid); - log.setType(isValid - ? MetricsEvent.TYPE_SUCCESS - : MetricsEvent.TYPE_DISMISS); + log.setType( + isValid ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_DISMISS); } catch (Exception e) { Slog.e(TAG, "Not showing save UI because validation failed:", e); log.setType(MetricsEvent.TYPE_FAILURE); mMetricsLogger.write(log); mSaveEventLogger.maybeSetSaveUiNotShownReason( - NO_SAVE_REASON_FIELD_VALIDATION_FAILED); + NO_SAVE_REASON_FIELD_VALIDATION_FAILED); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED); } @@ -3953,9 +4347,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (!isValid) { Slog.i(TAG, "not showing save UI because fields failed validation"); mSaveEventLogger.maybeSetSaveUiNotShownReason( - NO_SAVE_REASON_FIELD_VALIDATION_FAILED); + NO_SAVE_REASON_FIELD_VALIDATION_FAILED); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED); } } @@ -3964,15 +4360,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // content. final List datasets = response.getDatasets(); if (datasets != null) { - datasets_loop: for (int i = 0; i < datasets.size(); i++) { + datasets_loop: + for (int i = 0; i < datasets.size(); i++) { final Dataset dataset = datasets.get(i); final ArrayMap datasetValues = Helper.getFields(dataset); if (sVerbose) { - Slog.v(TAG, "Checking if saved fields match contents of dataset #" + i - + ": " + dataset + "; savableIds=" + savableIds); + Slog.v( + TAG, + "Checking if saved fields match contents of dataset #" + + i + + ": " + + dataset + + "; savableIds=" + + savableIds); } - savable_ids_loop: for (int j = 0; j < savableIds.size(); j++) { + savable_ids_loop: + for (int j = 0; j < savableIds.size(); j++) { final AutofillId id = savableIds.valueAt(j); final AutofillValue currentValue = currentValues.get(id); if (currentValue == null) { @@ -3984,20 +4388,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue datasetValue = datasetValues.get(id); if (!currentValue.equals(datasetValue)) { if (sDebug) { - Slog.d(TAG, "found a dataset change on id " + id + ": from " - + datasetValue + " to " + currentValue); + Slog.d( + TAG, + "found a dataset change on id " + + id + + ": from " + + datasetValue + + " to " + + currentValue); } continue datasets_loop; } if (sVerbose) Slog.v(TAG, "no dataset changes for id " + id); } if (sDebug) { - Slog.d(TAG, "ignoring Save UI because all fields match contents of " - + "dataset #" + i + ": " + dataset); + Slog.d( + TAG, + "ignoring Save UI because all fields match contents of " + + "dataset #" + + i + + ": " + + dataset); } mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_DATASET_MATCH); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_DATASET_MATCH); } } @@ -4015,13 +4432,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState wtf(null, "showSaveLocked(): no service label or icon"); mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_NONE); } - getUiForShowing().showSaveUi(serviceLabel, serviceIcon, - mService.getServicePackageName(), saveInfo, this, - mComponentName, this, mContext, mPendingSaveUi, isUpdate, mCompatMode, - response.getShowSaveDialogIcon(), mSaveEventLogger); + getUiForShowing() + .showSaveUi( + serviceLabel, + serviceIcon, + mService.getServicePackageName(), + saveInfo, + this, + mComponentName, + this, + mContext, + mPendingSaveUi, + isUpdate, + mCompatMode, + response.getShowSaveDialogIcon(), + mSaveEventLogger); if (client != null) { try { client.setSaveUiState(id, true); @@ -4031,21 +4461,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mSessionFlags.mShowingSaveUi = true; if (sDebug) { - Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for " - + id + "!"); + Slog.d( + TAG, + "Good news, everyone! All checks passed, show save UI for " + id + "!"); } - return new SaveResult(/* logSaveShown= */ true, /* removeSession= */ false, + return new SaveResult( + /* logSaveShown= */ true, + /* removeSession= */ false, Event.NO_SAVE_UI_REASON_NONE); } } // Nothing changed... if (sDebug) { - Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities." - + "allRequiredAreNotNull=" + allRequiredAreNotEmpty - + ", atLeastOneChanged=" + atLeastOneChanged); + Slog.d( + TAG, + "showSaveLocked(" + + id + + "): with no changes, comes no responsibilities." + + "allRequiredAreNotNull=" + + allRequiredAreNotEmpty + + ", atLeastOneChanged=" + + atLeastOneChanged); } - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, - saveDialogNotShowReason); + return new SaveResult( + /* logSaveShown= */ false, /* removeSession= */ true, saveDialogNotShowReason); } private void logSaveShown() { @@ -4078,25 +4517,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return sanitized; } - /** - * Returns whether the session is currently showing the save UI - */ + /** Returns whether the session is currently showing the save UI */ @GuardedBy("mLock") boolean isSaveUiShowingLocked() { return mSessionFlags.mShowingSaveUi; } - /** - * Gets the latest non-empty value for the given id in the autofill contexts. - */ + /** Gets the latest non-empty value for the given id in the autofill contexts. */ @GuardedBy("mLock") @Nullable private ViewNode getViewNodeFromContextsLocked(@NonNull AutofillId autofillId) { final int numContexts = mContexts.size(); for (int i = numContexts - 1; i >= 0; i--) { final FillContext context = mContexts.get(i); - final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), - autofillId); + final ViewNode node = + Helper.findViewNodeByAutofillId(context.getStructure(), autofillId); if (node != null) { return node; } @@ -4104,22 +4539,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } - /** - * Gets the latest non-empty value for the given id in the autofill contexts. - */ + /** Gets the latest non-empty value for the given id in the autofill contexts. */ @GuardedBy("mLock") @Nullable private AutofillValue getValueFromContextsLocked(@NonNull AutofillId autofillId) { final int numContexts = mContexts.size(); for (int i = numContexts - 1; i >= 0; i--) { final FillContext context = mContexts.get(i); - final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), - autofillId); + final ViewNode node = + Helper.findViewNodeByAutofillId(context.getStructure(), autofillId); if (node != null) { final AutofillValue value = node.getAutofillValue(); if (sDebug) { - Slog.d(TAG, "getValueFromContexts(" + this.id + "/" + autofillId + ") at " - + i + ": " + value); + Slog.d( + TAG, + "getValueFromContexts(" + + this.id + + "/" + + autofillId + + ") at " + + i + + ": " + + value); } if (value != null && !value.isEmpty()) { return value; @@ -4129,17 +4570,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } - /** - * Gets the latest autofill options for the given id in the autofill contexts. - */ + /** Gets the latest autofill options for the given id in the autofill contexts. */ @GuardedBy("mLock") @Nullable private CharSequence[] getAutofillOptionsFromContextsLocked(@NonNull AutofillId autofillId) { final int numContexts = mContexts.size(); for (int i = numContexts - 1; i >= 0; i--) { final FillContext context = mContexts.get(i); - final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), - autofillId); + final ViewNode node = + Helper.findViewNodeByAutofillId(context.getStructure(), autofillId); if (node != null && node.getAutofillOptions() != null) { return node.getAutofillOptions(); } @@ -4160,7 +4599,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final FillContext context = mContexts.get(contextNum); final ViewNode[] nodes = - context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); + context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); if (sVerbose) Slog.v(TAG, "updateValuesForSaveLocked(): updating " + context); @@ -4191,8 +4630,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sanitizedValue != null) { node.updateAutofillValue(sanitizedValue); } else if (sDebug) { - Slog.d(TAG, "updateValuesForSaveLocked(): not updating field " + id - + " because it failed sanitization"); + Slog.d( + TAG, + "updateValuesForSaveLocked(): not updating field " + + id + + " because it failed sanitization"); } } @@ -4200,28 +4642,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState context.getStructure().sanitizeForParceling(false); if (sVerbose) { - Slog.v(TAG, "updateValuesForSaveLocked(): dumping structure of " + context - + " before calling service.save()"); + Slog.v( + TAG, + "updateValuesForSaveLocked(): dumping structure of " + + context + + " before calling service.save()"); context.getStructure().dump(false); } } } - /** - * Calls service when user requested save. - */ + /** Calls service when user requested save. */ @GuardedBy("mLock") void callSaveLocked() { if (mDestroyed) { - Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#callSaveLocked() rejected - session: " + id + " destroyed"); mSaveEventLogger.maybeSetIsSaved(false); mSaveEventLogger.logAndEndEvent(); return; } if (mRemoteFillService == null) { - wtf(null, "callSaveLocked() called without a remote service. " - + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); + wtf( + null, + "callSaveLocked() called without a remote service. " + + "mForAugmentedAutofillOnly: %s", + mSessionFlags.mAugmentedAutofillOnly); mSaveEventLogger.maybeSetIsSaved(false); mSaveEventLogger.logAndEndEvent(); return; @@ -4241,7 +4688,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Remove pending fill requests as the session is finished. cancelCurrentRequestLocked(); - final ArrayList contexts = mergePreviousSessionLocked( /* forSave= */ true); + final ArrayList contexts = mergePreviousSessionLocked(/* forSave= */ true); FieldClassificationResponse fieldClassificationResponse = mClassificationState.mLastFieldClassificationResponse; @@ -4251,8 +4698,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mClientState == null) { mClientState = new Bundle(); } - mClientState.putParcelableArrayList(EXTRA_KEY_DETECTIONS, new ArrayList<>( - fieldClassificationResponse.getClassifications())); + mClientState.putParcelableArrayList( + EXTRA_KEY_DETECTIONS, + new ArrayList<>(fieldClassificationResponse.getClassifications())); } final SaveRequest saveRequest = new SaveRequest(contexts, mClientState, mSelectedDatasetIds); @@ -4270,11 +4718,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * from previous sessions that were asked by the service to be delayed (if any). * *

As a side-effect: + * *

    - *
  • If the current {@link #mClientState} is {@code null}, sets it with the last non- - * {@code null} client state from previous sessions. + *
  • If the current {@link #mClientState} is {@code null}, sets it with the last non- {@code + * null} client state from previous sessions. *
  • When {@code forSave} is {@code true}, calls {@link #updateValuesForSaveLocked()} in the - * previous sessions. + * previous sessions. *
*/ @NonNull @@ -4283,30 +4732,51 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList contexts; if (previousSessions != null) { if (sDebug) { - Slog.d(TAG, "mergeSessions(" + this.id + "): Merging the content of " - + previousSessions.size() + " sessions for task " + taskId); + Slog.d( + TAG, + "mergeSessions(" + + this.id + + "): Merging the content of " + + previousSessions.size() + + " sessions for task " + + taskId); } contexts = new ArrayList<>(); for (int i = 0; i < previousSessions.size(); i++) { final Session previousSession = previousSessions.get(i); final ArrayList previousContexts = previousSession.mContexts; if (previousContexts == null) { - Slog.w(TAG, "mergeSessions(" + this.id + "): Not merging null contexts from " - + previousSession.id); + Slog.w( + TAG, + "mergeSessions(" + + this.id + + "): Not merging null contexts from " + + previousSession.id); continue; } if (forSave) { previousSession.updateValuesForSaveLocked(); } if (sDebug) { - Slog.d(TAG, "mergeSessions(" + this.id + "): adding " + previousContexts.size() - + " context from previous session #" + previousSession.id); + Slog.d( + TAG, + "mergeSessions(" + + this.id + + "): adding " + + previousContexts.size() + + " context from previous session #" + + previousSession.id); } contexts.addAll(previousContexts); if (mClientState == null && previousSession.mClientState != null) { if (sDebug) { - Slog.d(TAG, "mergeSessions(" + this.id + "): setting client state from " - + "previous session" + previousSession.id); + Slog.d( + TAG, + "mergeSessions(" + + this.id + + "): setting client state from " + + "previous session" + + previousSession.id); } mClientState = previousSession.mClientState; } @@ -4351,8 +4821,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // If it's not, then check if it should start a partition. if (shouldStartNewPartitionLocked(id, flags)) { if (sDebug) { - Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": " - + viewState.getStateAsString()); + Slog.d( + TAG, + "Starting partition or augmented request for view id " + + id + + ": " + + viewState.getStateAsString()); } // Fix to always let standard autofill start. // Sometimes activity contain IMPORTANT_FOR_AUTOFILL_NO fields which marks session as @@ -4363,8 +4837,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (sVerbose) { - Slog.v(TAG, "Not starting new partition for view " + id + ": " - + viewState.getStateAsString()); + Slog.v( + TAG, + "Not starting new partition for view " + + id + + ": " + + viewState.getStateAsString()); } return Optional.empty(); } @@ -4373,17 +4851,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Determines if a new partition should be started for an id. * * @param id The id of the view that is entered - * * @return {@code true} if a new partition should be started */ @GuardedBy("mLock") private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id, int flags) { final ViewState currentView = mViewStates.get(id); - SparseArray responses = shouldRequestSecondaryProvider(flags) - ? mSecondaryResponses : mResponses; + SparseArray responses = + shouldRequestSecondaryProvider(flags) ? mSecondaryResponses : mResponses; if (responses == null) { - return currentView != null && (currentView.getState() - & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) == 0; + return currentView != null + && (currentView.getState() & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) + == 0; } if (mSessionFlags.mExpiredResponse) { @@ -4395,8 +4873,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int numResponses = responses.size(); if (numResponses >= AutofillManagerService.getPartitionMaxCount()) { - Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id - + " reached maximum of " + AutofillManagerService.getPartitionMaxCount()); + Slog.e( + TAG, + "Not starting a new partition on " + + id + + " because session " + + this.id + + " reached maximum of " + + AutofillManagerService.getPartitionMaxCount()); return false; } @@ -4437,8 +4921,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } boolean shouldRequestSecondaryProvider(int flags) { - if (!mService.isAutofillCredmanIntegrationEnabled() - || mSecondaryProviderHandler == null) { + if (!mService.isAutofillCredmanIntegrationEnabled() || mSecondaryProviderHandler == null) { return false; } if (mIsPrimaryCredential) { @@ -4452,8 +4935,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // 'Session.this.mLock', which is the same as mLock. @SuppressWarnings("GuardedBy") @GuardedBy("mLock") - void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action, - int flags) { + void updateLocked( + AutofillId id, Rect virtualBounds, AutofillValue value, int action, int flags) { if (mDestroyed) { Slog.w(TAG, "updateLocked(" + id + "): rejected - session: destroyed"); return; @@ -4464,7 +4947,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "updateLocked(" + id + "): Set the response has expired."); } mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists( - NOT_SHOWN_REASON_VIEW_CHANGED); + NOT_SHOWN_REASON_VIEW_CHANGED); mPresentationStatsEventLogger.logAndEndEvent("ACTION_RESPONSE_EXPIRED"); return; } @@ -4474,23 +4957,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose) { Slog.v( TAG, - "updateLocked(" + id + "): " - + "id=" + this.id - + ", action=" + actionAsString(action) - + ", flags=" + flags - + ", mCurrentViewId=" + mCurrentViewId - + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse - + ", viewState=" + viewState); + "updateLocked(" + + id + + "): " + + "id=" + + this.id + + ", action=" + + actionAsString(action) + + ", flags=" + + flags + + ", mCurrentViewId=" + + mCurrentViewId + + ", mExpiredResponse=" + + mSessionFlags.mExpiredResponse + + ", viewState=" + + viewState); } if (viewState == null) { - if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED + if (action == ACTION_START_SESSION + || action == ACTION_VALUE_CHANGED || action == ACTION_VIEW_ENTERED) { if (sVerbose) Slog.v(TAG, "Creating viewState for " + id); boolean isIgnored = isIgnoredLocked(id); - viewState = new ViewState(id, this, - isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL, - mIsPrimaryCredential); + viewState = + new ViewState( + id, + this, + isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL, + mIsPrimaryCredential); mViewStates.put(id, viewState); // TODO(b/73648631): for optimization purposes, should also ignore if change is @@ -4521,7 +5016,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionFlags.mScreenHasCredmanField = true; } - switch(action) { + switch (action) { case ACTION_START_SESSION: // View is triggering autofill. mCurrentViewId = viewState.id; @@ -4545,14 +5040,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState case ACTION_VALUE_CHANGED: if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { // Must cancel the session if the value of the URL bar changed - final String currentUrl = mUrlBar == null ? null - : mUrlBar.getText().toString().trim(); + final String currentUrl = + mUrlBar == null ? null : mUrlBar.getText().toString().trim(); if (currentUrl == null) { // Validation check - shouldn't happen. wtf(null, "URL bar value changed, but current value is null"); return; } - if (value == null || ! value.isText()) { + if (value == null || !value.isText()) { // Validation check - shouldn't happen. wtf(null, "URL bar value changed to null or non-text: %s", value); return; @@ -4567,8 +5062,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // are finished, as the URL bar changed callback is usually called before // the virtual views become invisible. if (sDebug) { - Slog.d(TAG, "Ignoring change on URL because session will finish when " - + "views are gone"); + Slog.d( + TAG, + "Ignoring change on URL because session will finish when " + + "views are gone"); } return; } @@ -4598,8 +5095,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // isSameViewEntered has some limitations, where it isn't considered same view when // autofill suggestions pop up, user selects, and the focus lands back on the view. // isSameViewAgain tries to overcome that situation. - final boolean isSameViewAgain = isSameViewEntered - || Objects.equals(mCurrentViewId, mPreviousNonNullEnteredViewId); + final boolean isSameViewAgain = + isSameViewEntered + || Objects.equals(mCurrentViewId, mPreviousNonNullEnteredViewId); if (mCurrentViewId != null) { mPreviousNonNullEnteredViewId = mCurrentViewId; } @@ -4655,8 +5153,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Trigger augmented autofill if applicable if ((flags & FLAG_MANUAL_REQUEST) == 0) { // Not a manual request - if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains( - id)) { + if (mAugmentedAutofillableIds != null + && mAugmentedAutofillableIds.contains(id)) { // Regular autofill handled the view and returned null response, but it // triggered augmented autofill if (!isSameViewEntered) { @@ -4664,16 +5162,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState triggerAugmentedAutofillLocked(flags); } else { if (sDebug) { - Slog.d(TAG, "skip augmented autofill for same view: " - + "same view entered"); + Slog.d( + TAG, + "skip augmented autofill for same view: " + + "same view entered"); } } return; } else if (mSessionFlags.mAugmentedAutofillOnly && isSameViewEntered) { // Regular autofill is disabled. if (sDebug) { - Slog.d(TAG, "skip augmented autofill for same view: " - + "standard autofill disabled."); + Slog.d( + TAG, + "skip augmented autofill for same view: " + + "standard autofill disabled."); } return; } @@ -4717,8 +5219,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // on the IME side if it arrives before the input view is finished on the IME. mInlineSessionController.resetInlineFillUiLocked(); - if ((viewState.getState() & - ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) != 0) { + if ((viewState.getState() & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) + != 0) { // View was exited before Inline Request sent back, do not set it to // null yet to let onHandleAssistData finish processing } else { @@ -4728,7 +5230,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // It's not necessary that there's no more presentation for this view. It could // be that the user chose some suggestion, in which case, view exits. mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( - NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); + NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); } break; default: @@ -4737,8 +5239,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private void logPresentationStatsOnViewEnteredLocked(FillResponse response, - boolean isCredmanRequested) { + private void logPresentationStatsOnViewEnteredLocked( + FillResponse response, boolean isCredmanRequested) { mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested); mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( mFieldClassificationIdSnapshot); @@ -4754,16 +5256,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void hideAugmentedAutofillLocked(@NonNull ViewState viewState) { - if ((viewState.getState() - & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) { + if ((viewState.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) { viewState.resetState(ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL); cancelAugmentedAutofillLocked(); } } - /** - * Checks whether a view should be ignored. - */ + /** Checks whether a view should be ignored. */ @GuardedBy("mLock") private boolean isIgnoredLocked(AutofillId id) { // Always check the latest response only @@ -4782,22 +5281,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState && getSaveInfoLocked() != null) { final int length = viewState.getCurrentValue().getTextValue().length(); if (sDebug) { - Slog.d(TAG, "updateLocked(" + id + "): resetting value that was " - + length + " chars long"); - } - final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length); + Slog.d( + TAG, + "updateLocked(" + + id + + "): resetting value that was " + + length + + " chars long"); + } + final LogMaker log = + newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length); mMetricsLogger.write(log); } } @GuardedBy("mLock") - private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value, - ViewState viewState, int flags) { + private void updateViewStateAndUiOnValueChangedLocked( + AutofillId id, AutofillValue value, ViewState viewState, int flags) { // Cache the last non-empty value for save purpose. Some apps clear the form before // navigating to other activities. - if (mIgnoreViewStateResetToEmpty && (value == null || value.isEmpty()) - && viewState.getCurrentValue() != null && viewState.getCurrentValue().isText() + if (mIgnoreViewStateResetToEmpty + && (value == null || value.isEmpty()) + && viewState.getCurrentValue() != null + && viewState.getCurrentValue().isText() && viewState.getCurrentValue().getTextValue() != null && viewState.getCurrentValue().getTextValue().length() > 1) { if (sVerbose) { @@ -4875,8 +5382,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * indicate the IME attempting to probe the potentially sensitive content of inline suggestions. */ @GuardedBy("mLock") - private void updateFilteringStateOnValueChangedLocked(@Nullable String newTextValue, - ViewState viewState) { + private void updateFilteringStateOnValueChangedLocked( + @Nullable String newTextValue, ViewState viewState) { if (newTextValue == null) { // Don't just return here, otherwise the IME can circumvent this logic using non-text // values. @@ -4901,18 +5408,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @Override - public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId, - @Nullable AutofillValue value, int flags) { + public void onFillReady( + @NonNull FillResponse response, + @NonNull AutofillId filledId, + @Nullable AutofillValue value, + int flags) { synchronized (mLock) { mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( mFieldClassificationIdSnapshot); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onFillReady() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onFillReady() rejected - session: " + id + " destroyed"); mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SESSION_DESTROYED); mSaveEventLogger.logAndEndEvent(); mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( - NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY); + NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY); mPresentationStatsEventLogger.logAndEndEvent("on fill ready"); return; } @@ -4954,7 +5465,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } else { setFillDialogDisabled(); } - } if (response.supportsInlineSuggestions()) { @@ -4971,10 +5481,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - getUiForShowing().showFillUi(filledId, response, filterText, - mService.getServicePackageName(), mComponentName, - serviceLabel, serviceIcon, this, mContext, id, mCompatMode, - mService.getMaster().getMaxInputLengthForAutofill()); + getUiForShowing() + .showFillUi( + filledId, + response, + filterText, + mService.getServicePackageName(), + mComponentName, + serviceLabel, + serviceIcon, + this, + mContext, + id, + mCompatMode, + mService.getMaster().getMaxInputLengthForAutofill()); synchronized (mLock) { if (mUiShownTime == 0) { @@ -4983,21 +5503,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final long duration = mUiShownTime - mStartTime; if (sDebug) { - final StringBuilder msg = new StringBuilder("1st UI for ") - .append(mActivityToken) - .append(" shown in "); + final StringBuilder msg = + new StringBuilder("1st UI for ") + .append(mActivityToken) + .append(" shown in "); TimeUtils.formatDuration(duration, msg); Slog.d(TAG, msg.toString()); } - final StringBuilder historyLog = new StringBuilder("id=").append(id) - .append(" app=").append(mActivityToken) - .append(" svc=").append(mService.getServicePackageName()) - .append(" latency="); + final StringBuilder historyLog = + new StringBuilder("id=") + .append(id) + .append(" app=") + .append(mActivityToken) + .append(" svc=") + .append(mService.getServicePackageName()) + .append(" latency="); TimeUtils.formatDuration(duration, historyLog); mUiLatencyHistory.log(historyLog.toString()); - addTaggedDataToRequestLogLocked(response.getRequestId(), - MetricsEvent.FIELD_AUTOFILL_DURATION, duration); + addTaggedDataToRequestLogLocked( + response.getRequestId(), MetricsEvent.FIELD_AUTOFILL_DURATION, duration); } } } @@ -5052,8 +5577,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - private boolean requestShowFillDialog(FillResponse response, - AutofillId filledId, String filterText, int flags) { + private boolean requestShowFillDialog( + FillResponse response, AutofillId filledId, String filterText, int flags) { if (!isFillDialogUiEnabled()) { // Unsupported fill dialog UI if (sDebug) Log.w(TAG, "requestShowFillDialog: fill dialog is disabled"); @@ -5079,7 +5604,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed."); return false; } - } Drawable serviceIcon = null; @@ -5087,21 +5611,31 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState serviceIcon = getServiceIcon(response); } - getUiForShowing().showFillDialog(filledId, response, filterText, - mService.getServicePackageName(), mComponentName, serviceIcon, this, - id, mCompatMode, mPresentationStatsEventLogger, mLock); + getUiForShowing() + .showFillDialog( + filledId, + response, + filterText, + mService.getServicePackageName(), + mComponentName, + serviceIcon, + this, + id, + mCompatMode, + mPresentationStatsEventLogger, + mLock); return true; } /** - * Get the custom icon that was passed through FillResponse. If the custom icon wasn't able - * to be fetched, use the default provider icon instead + * Get the custom icon that was passed through FillResponse. If the custom icon wasn't able to + * be fetched, use the default provider icon instead * * @return Drawable of the provider icon, if it was able to be fetched. Null otherwise */ @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's - // actually the same object as mLock. - // TODO: Expose mService.mLock or redesign instead. + // actually the same object as mLock. + // TODO: Expose mService.mLock or redesign instead. @GuardedBy("mLock") private Drawable getServiceIcon(FillResponse response) { Drawable serviceIcon = null; @@ -5130,14 +5664,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Get the custom label that was passed through FillResponse. If the custom label - * wasn't able to be fetched, use the default provider icon instead + * Get the custom label that was passed through FillResponse. If the custom label wasn't able to + * be fetched, use the default provider icon instead * * @return Drawable of the provider icon, if it was able to be fetched. Null otherwise */ @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's - // actually the same object as mLock. - // TODO: Expose mService.mLock or redesign instead. + // actually the same object as mLock. + // TODO: Expose mService.mLock or redesign instead. @GuardedBy("mLock") private CharSequence getServiceLabel(FillResponse response) { CharSequence serviceLabel = null; @@ -5167,11 +5701,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return serviceLabel; } - /** - * Returns whether we made a request to show inline suggestions. - */ - private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response, - @Nullable String filterText) { + /** Returns whether we made a request to show inline suggestions. */ + private boolean requestShowInlineSuggestionsLocked( + @NonNull FillResponse response, @Nullable String filterText) { if (mCurrentViewId == null) { Log.w(TAG, "requestShowInlineSuggestionsLocked(): no view currently focused"); return false; @@ -5199,89 +5731,122 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } final InlineFillUi.InlineFillUiInfo inlineFillUiInfo = - new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId, - filterText, remoteRenderService, userId, id); - InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response, - new InlineFillUi.InlineSuggestionUiCallback() { - @Override - public void autofill(@NonNull Dataset dataset, int datasetIndex) { - fill(response.getRequestId(), datasetIndex, dataset, UI_TYPE_INLINE); - } + new InlineFillUi.InlineFillUiInfo( + inlineSuggestionsRequest.get(), + focusedId, + filterText, + remoteRenderService, + userId, + id); + InlineFillUi inlineFillUi = + InlineFillUi.forAutofill( + inlineFillUiInfo, + response, + new InlineFillUi.InlineSuggestionUiCallback() { + @Override + public void autofill(@NonNull Dataset dataset, int datasetIndex) { + fill( + response.getRequestId(), + datasetIndex, + dataset, + UI_TYPE_INLINE); + } - @Override - public void authenticate(int requestId, int datasetIndex) { - Session.this.authenticate(response.getRequestId(), datasetIndex, - response.getAuthentication(), response.getClientState(), - UI_TYPE_INLINE); - } + @Override + public void authenticate(int requestId, int datasetIndex) { + Session.this.authenticate( + response.getRequestId(), + datasetIndex, + response.getAuthentication(), + response.getClientState(), + UI_TYPE_INLINE); + } - @Override - public void startIntentSender(@NonNull IntentSender intentSender) { - Session.this.startIntentSender(intentSender, new Intent()); - } + @Override + public void startIntentSender(@NonNull IntentSender intentSender) { + Session.this.startIntentSender(intentSender, new Intent()); + } - @Override - public void onError() { - synchronized (mLock) { - mInlineSessionController.setInlineFillUiLocked( - InlineFillUi.emptyUi(focusedId)); - } - } + @Override + public void onError() { + synchronized (mLock) { + mInlineSessionController.setInlineFillUiLocked( + InlineFillUi.emptyUi(focusedId)); + } + } - @Override - public void onInflate() { - Session.this.onShown(UI_TYPE_INLINE, 1); - } - }, mService.getMaster().getMaxInputLengthForAutofill()); + @Override + public void onInflate() { + Session.this.onShown(UI_TYPE_INLINE, 1); + } + }, + mService.getMaster().getMaxInputLengthForAutofill()); return mInlineSessionController.setInlineFillUiLocked(inlineFillUi); } private ResultReceiver constructCredentialManagerCallback(int requestId) { - final ResultReceiver resultReceiver = new ResultReceiver(mHandler) { - final AutofillId mAutofillId = mCurrentViewId; - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - if (resultCode == SUCCESS_CREDMAN_SELECTOR) { - Slog.d(TAG, "onReceiveResult from Credential Manager " - + "bottom sheet with mCurrentViewId: " + mAutofillId); - GetCredentialResponse getCredentialResponse = - resultData.getParcelable( - CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, - GetCredentialResponse.class); - - if (Flags.autofillCredmanDevIntegration()) { - sendCredentialManagerResponseToApp(getCredentialResponse, - /*exception=*/ null, mAutofillId); - } else { - Dataset datasetFromCredential = getDatasetFromCredentialResponse( - getCredentialResponse); - if (datasetFromCredential != null) { - autoFill(requestId, /*datasetIndex=*/-1, - datasetFromCredential, false, - UI_TYPE_CREDMAN_BOTTOM_SHEET); + final ResultReceiver resultReceiver = + new ResultReceiver(mHandler) { + final AutofillId mAutofillId = mCurrentViewId; + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == SUCCESS_CREDMAN_SELECTOR) { + Slog.d( + TAG, + "onReceiveResult from Credential Manager " + + "bottom sheet with mCurrentViewId: " + + mAutofillId); + GetCredentialResponse getCredentialResponse = + resultData.getParcelable( + CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, + GetCredentialResponse.class); + + if (Flags.autofillCredmanDevIntegration()) { + sendCredentialManagerResponseToApp( + getCredentialResponse, /* exception= */ null, mAutofillId); + } else { + Dataset datasetFromCredential = + getDatasetFromCredentialResponse(getCredentialResponse); + if (datasetFromCredential != null) { + autoFill( + requestId, + /* datasetIndex= */ -1, + datasetFromCredential, + false, + UI_TYPE_CREDMAN_BOTTOM_SHEET); + } + } + } else if (resultCode == FAILURE_CREDMAN_SELECTOR) { + String[] exception = + resultData.getStringArray( + CredentialProviderService + .EXTRA_GET_CREDENTIAL_EXCEPTION); + if (exception != null && exception.length >= 2) { + String errType = exception[0]; + String errMsg = exception[1]; + Slog.w( + TAG, + "Credman bottom sheet from pinned " + + "entry failed with: + " + + errType + + " , " + + errMsg); + sendCredentialManagerResponseToApp( + /* response= */ null, + new GetCredentialException(errType, errMsg), + mAutofillId); + } + } else { + Slog.d( + TAG, + "Unknown resultCode from credential " + + "manager bottom sheet: " + + resultCode); } } - } else if (resultCode == FAILURE_CREDMAN_SELECTOR) { - String[] exception = resultData.getStringArray( - CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION); - if (exception != null && exception.length >= 2) { - String errType = exception[0]; - String errMsg = exception[1]; - Slog.w(TAG, "Credman bottom sheet from pinned " - + "entry failed with: + " + errType + " , " - + errMsg); - sendCredentialManagerResponseToApp(/*response=*/ null, - new GetCredentialException(errType, errMsg), - mAutofillId); - } - } else { - Slog.d(TAG, "Unknown resultCode from credential " - + "manager bottom sheet: " + resultCode); - } - } - }; - ResultReceiver ipcFriendlyResultReceiver = - toIpcFriendlyResultReceiver(resultReceiver); + }; + ResultReceiver ipcFriendlyResultReceiver = toIpcFriendlyResultReceiver(resultReceiver); return ipcFriendlyResultReceiver; } @@ -5309,8 +5874,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - private void notifyUnavailableToClient(int sessionFinishedState, - @Nullable ArrayList autofillableIds) { + private void notifyUnavailableToClient( + int sessionFinishedState, @Nullable ArrayList autofillableIds) { synchronized (mLock) { if (mCurrentViewId == null) return; try { @@ -5374,27 +5939,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (saveInfo.getRequiredIds() != null) { Collections.addAll(trackedViews, saveInfo.getRequiredIds()); mSaveEventLogger.maybeSetSaveUiShownReason( - SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE); + SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE); } if (saveInfo.getOptionalIds() != null) { Collections.addAll(trackedViews, saveInfo.getOptionalIds()); mSaveEventLogger.maybeSetSaveUiShownReason( - SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE); + SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE); } } if ((flags & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) { - mSaveEventLogger.maybeSetSaveUiShownReason( - SAVE_UI_SHOWN_REASON_UNKNOWN); + mSaveEventLogger.maybeSetSaveUiShownReason(SAVE_UI_SHOWN_REASON_UNKNOWN); mSaveEventLogger.maybeSetSaveUiNotShownReason( - NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG); + NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG); saveOnFinish = false; } } else { flags = 0; - mSaveEventLogger.maybeSetSaveUiNotShownReason( - NO_SAVE_REASON_NO_SAVE_INFO); + mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NO_SAVE_INFO); saveTriggerId = null; } @@ -5427,21 +5990,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { if (sVerbose) { - Slog.v(TAG, "updateTrackedIdsLocked(): trackedViews: " + trackedViews - + " fillableIds: " + fillableIds + " triggerId: " + saveTriggerId - + " saveOnFinish:" + saveOnFinish + " flags: " + flags - + " hasSaveInfo: " + (saveInfo != null)); - } - mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible, - saveOnFinish, toArray(fillableIds), saveTriggerId, hasAuthentication); + Slog.v( + TAG, + "updateTrackedIdsLocked(): trackedViews: " + + trackedViews + + " fillableIds: " + + fillableIds + + " triggerId: " + + saveTriggerId + + " saveOnFinish:" + + saveOnFinish + + " flags: " + + flags + + " hasSaveInfo: " + + (saveInfo != null)); + } + mClient.setTrackedViews( + id, + toArray(trackedViews), + mSaveOnAllViewsInvisible, + saveOnFinish, + toArray(fillableIds), + saveTriggerId, + hasAuthentication); } catch (RemoteException e) { Slog.w(TAG, "Cannot set tracked ids", e); } } - /** - * Sets the state of views that failed to autofill. - */ + /** Sets the state of views that failed to autofill. */ @GuardedBy("mLock") void setAutofillFailureLocked(@NonNull List ids, boolean isRefill) { if (sVerbose && !ids.isEmpty()) { @@ -5464,9 +6041,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids, isRefill); } - /** - * Sets the state of views that failed to autofill. - */ + /** Sets the state of views that failed to autofill. */ @GuardedBy("mLock") void setViewAutofilledLocked(@NonNull AutofillId id) { if (sVerbose) { @@ -5478,17 +6053,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPresentationStatsEventLogger.maybeAddSuccessId(id); } - /** - * Sets the state of views that failed to autofill. - */ + /** Sets the state of views that failed to autofill. */ void setNotifyNotExpiringResponseDuringAuth() { synchronized (mLock) { mPresentationStatsEventLogger.maybeSetNotifyNotExpiringResponseDuringAuth(); } } - /** - * Sets the state of views that failed to autofill. - */ + + /** Sets the state of views that failed to autofill. */ void setLogViewEnteredIgnoredDuringAuth() { synchronized (mLock) { mPresentationStatsEventLogger.notifyViewEnteredIgnoredDuringAuthCount(); @@ -5496,10 +6068,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private void replaceResponseLocked(@NonNull FillResponse oldResponse, - @NonNull FillResponse newResponse, @Nullable Bundle newClientState) { + private void replaceResponseLocked( + @NonNull FillResponse oldResponse, + @NonNull FillResponse newResponse, + @Nullable Bundle newClientState) { // Disassociate view states with the old response - setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, /* clearResponse= */ true, + setViewStatesLocked( + oldResponse, + ViewState.STATE_INITIAL, + /* clearResponse= */ true, /* isPrimary= */ true); // Move over the id newResponse.setRequestId(oldResponse.getRequestId()); @@ -5519,7 +6096,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList autofillableIds; if (context != null) { final AssistStructure structure = context.getStructure(); - autofillableIds = Helper.getAutofillIds(structure, /* autofillableOnly= */true); + autofillableIds = Helper.getAutofillIds(structure, /* autofillableOnly= */ true); } else { Slog.w(TAG, "processNullResponseLocked(): no context for req " + requestId); autofillableIds = null; @@ -5535,8 +6112,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked(flags); if (mAugmentedAutofillDestroyer == null && ((flags & FLAG_PASSWORD_INPUT_TYPE) == 0)) { if (sVerbose) { - Slog.v(TAG, "canceling session " + id + " when service returned null and it cannot " - + "be augmented. AutofillableIds: " + autofillableIds); + Slog.v( + TAG, + "canceling session " + + id + + " when service returned null and it cannot " + + "be augmented. AutofillableIds: " + + autofillableIds); } // Nothing to be done, but need to notify client. notifyUnavailableToClient(AutofillManager.STATE_FINISHED, autofillableIds); @@ -5544,15 +6126,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } else { if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) { if (sVerbose) { - Slog.v(TAG, "keeping session " + id + " when service returned null and " - + "augmented service is disabled for password fields. " - + "AutofillableIds: " + autofillableIds); + Slog.v( + TAG, + "keeping session " + + id + + " when service returned null and " + + "augmented service is disabled for password fields. " + + "AutofillableIds: " + + autofillableIds); } mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); } else { if (sVerbose) { - Slog.v(TAG, "keeping session " + id + " when service returned null but " - + "it can be augmented. AutofillableIds: " + autofillableIds); + Slog.v( + TAG, + "keeping session " + + id + + " when service returned null but " + + "it can be augmented. AutofillableIds: " + + autofillableIds); } } mAugmentedAutofillableIds = autofillableIds; @@ -5567,8 +6159,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Tries to trigger Augmented Autofill when the standard service could not fulfill a request. * - *

The request may not have been sent when this method returns as it may be waiting for - * the inline suggestion request asynchronously. + *

The request may not have been sent when this method returns as it may be waiting for the + * inline suggestion request asynchronously. * * @return callback to destroy the autofill UI, or {@code null} if not supported. */ @@ -5582,8 +6174,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // Check if Smart Suggestions is supported... - final @SmartSuggestionMode int supportedModes = mService - .getSupportedSmartSuggestionModesLocked(); + final @SmartSuggestionMode int supportedModes = + mService.getSupportedSmartSuggestionModesLocked(); if (supportedModes == 0) { if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no supported modes"); return null; @@ -5591,8 +6183,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // ...then if the service is set for the user - final RemoteAugmentedAutofillService remoteService = mService - .getRemoteAugmentedAutofillServiceLocked(); + final RemoteAugmentedAutofillService remoteService = + mService.getRemoteAugmentedAutofillServiceLocked(); if (remoteService == null) { if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no service for user"); return null; @@ -5612,25 +6204,37 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } - final boolean isAllowlisted = mService - .isWhitelistedForAugmentedAutofillLocked(mComponentName); + final boolean isAllowlisted = + mService.isWhitelistedForAugmentedAutofillLocked(mComponentName); if (!isAllowlisted) { if (sVerbose) { - Slog.v(TAG, "triggerAugmentedAutofillLocked(): " - + ComponentName.flattenToShortString(mComponentName) + " not whitelisted "); - } - logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), - mCurrentViewId, isAllowlisted, /* isInline= */ null); + Slog.v( + TAG, + "triggerAugmentedAutofillLocked(): " + + ComponentName.flattenToShortString(mComponentName) + + " not whitelisted "); + } + logAugmentedAutofillRequestLocked( + mode, + remoteService.getComponentName(), + mCurrentViewId, + isAllowlisted, + /* isInline= */ null); return null; } if (sVerbose) { - Slog.v(TAG, "calling Augmented Autofill Service (" - + ComponentName.flattenToShortString(remoteService.getComponentName()) - + ") on view " + mCurrentViewId + " using suggestion mode " - + getSmartSuggestionModeToString(mode) - + " when server returned null for session " + this.id); + Slog.v( + TAG, + "calling Augmented Autofill Service (" + + ComponentName.flattenToShortString(remoteService.getComponentName()) + + ") on view " + + mCurrentViewId + + " using suggestion mode " + + getSmartSuggestionModeToString(mode) + + " when server returned null for session " + + this.id); } // Log FillRequest for Augmented Autofill. mFillRequestEventLogger.startLogForNewRequest(); @@ -5648,8 +6252,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mAugmentedRequestsLogs == null) { mAugmentedRequestsLogs = new ArrayList<>(); } - final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_AUGMENTED_REQUEST, - remoteService.getComponentName().getPackageName()); + final LogMaker log = + newLogMaker( + MetricsEvent.AUTOFILL_AUGMENTED_REQUEST, + remoteService.getComponentName().getPackageName()); mAugmentedRequestsLogs.add(log); final AutofillId focusedId = mCurrentViewId; @@ -5715,8 +6321,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } synchronized (session.mLock) { session.mInlineSessionController.onCreateInlineSuggestionsRequestLocked( - mFocusedId, /*requestConsumer=*/ mRequestAugmentedAutofill, - result); + mFocusedId, /* requestConsumer= */ mRequestAugmentedAutofill, result); } } } @@ -5741,19 +6346,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mIsAllowlisted = isAllowlisted; mMode = mode; mCurrentValue = currentValue; - } + @Override public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) { Session session = mSessionWeakRef.get(); - if (logIfSessionNull( - session, "AugmentedAutofillInlineSuggestionRequestConsumer:")) { + if (logIfSessionNull(session, "AugmentedAutofillInlineSuggestionRequestConsumer:")) { return; } session.onAugmentedAutofillInlineSuggestionAccept( inlineSuggestionsRequest, mFocusedId, mIsAllowlisted, mMode, mCurrentValue); - } } @@ -5770,8 +6373,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public Boolean apply(InlineFillUi inlineFillUi) { Session session = mSessionWeakRef.get(); - if (logIfSessionNull( - session, "AugmentedAutofillInlineSuggestionsResponseCallback:")) { + if (logIfSessionNull(session, "AugmentedAutofillInlineSuggestionsResponseCallback:")) { return false; } @@ -5801,8 +6403,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * If the session is null or has been destroyed, log the error msg, and return true. - * This is a helper function intended to be called when de-referencing from a weak reference. + * If the session is null or has been destroyed, log the error msg, and return true. This is a + * helper function intended to be called when de-referencing from a weak reference. + * * @param session * @param logPrefix * @return true if the session is null, false otherwise. @@ -5830,15 +6433,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { final RemoteAugmentedAutofillService remoteService = mService.getRemoteAugmentedAutofillServiceLocked(); - logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), - focussedId, isAllowlisted, inlineSuggestionsRequest != null); - remoteService.onRequestAutofillLocked(id, mClient, - taskId, mComponentName, mActivityToken, - AutofillId.withoutSession(focussedId), currentValue, + logAugmentedAutofillRequestLocked( + mode, + remoteService.getComponentName(), + focussedId, + isAllowlisted, + inlineSuggestionsRequest != null); + remoteService.onRequestAutofillLocked( + id, + mClient, + taskId, + mComponentName, + mActivityToken, + AutofillId.withoutSession(focussedId), + currentValue, inlineSuggestionsRequest, new AugmentedAutofillInlineSuggestionsResponseCallback(this), new AugmentedAutofillErrorCallback(this), - mService.getRemoteInlineSuggestionRenderServiceLocked(), userId); + mService.getRemoteInlineSuggestionRenderServiceLocked(), + userId); } } @@ -5847,15 +6460,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState cancelAugmentedAutofillLocked(); // Also cancel augmented in IME - mInlineSessionController.setInlineFillUiLocked( - InlineFillUi.emptyUi(mCurrentViewId)); + mInlineSessionController.setInlineFillUiLocked(InlineFillUi.emptyUi(mCurrentViewId)); } } @GuardedBy("mLock") private void cancelAugmentedAutofillLocked() { - final RemoteAugmentedAutofillService remoteService = mService - .getRemoteAugmentedAutofillServiceLocked(); + final RemoteAugmentedAutofillService remoteService = + mService.getRemoteAugmentedAutofillServiceLocked(); if (remoteService == null) { Slog.w(TAG, "cancelAugmentedAutofillLocked(): no service for user"); return; @@ -5865,8 +6477,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private void processResponseLocked(@NonNull FillResponse newResponse, - @Nullable Bundle newClientState, int flags) { + private void processResponseLocked( + @NonNull FillResponse newResponse, @Nullable Bundle newClientState, int flags) { // Make sure we are hiding the UI which will be shown // only if handling the current response requires it. mUi.hideAll(this); @@ -5878,9 +6490,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int requestId = newResponse.getRequestId(); if (sVerbose) { - Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId - + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse - + ",newClientState=" + newClientState); + Slog.v( + TAG, + "processResponseLocked(): mCurrentViewId=" + + mCurrentViewId + + ",flags=" + + flags + + ", reqId=" + + requestId + + ", resp=" + + newResponse + + ",newClientState=" + + newClientState); } if (mResponses == null) { @@ -5892,8 +6513,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mResponses.put(requestId, newResponse); mClientState = newClientState != null ? newClientState : newResponse.getClientState(); - boolean webviewRequestedCredman = newClientState != null && newClientState.getBoolean( - WEBVIEW_REQUESTED_CREDENTIAL_KEY, false); + boolean webviewRequestedCredman = + newClientState != null + && newClientState.getBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false); List datasetList = newResponse.getDatasets(); mPresentationStatsEventLogger.maybeSetWebviewRequestedCredential(webviewRequestedCredman); @@ -5901,7 +6523,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPresentationStatsEventLogger.maybeSetAvailableCount(datasetList, mCurrentViewId); mFillResponseEventLogger.maybeSetDatasetsCountAfterPotentialPccFiltering(datasetList); - setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false, + setViewStatesLocked( + newResponse, + ViewState.STATE_FILLABLE, + /* clearResponse= */ false, /* isPrimary= */ true); updateFillDialogTriggerIdsLocked(); updateTrackedIdsLocked(); @@ -5914,12 +6539,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState currentView.maybeCallOnFillReady(flags); } - /** - * Sets the state of all views in the given response. - */ + /** Sets the state of all views in the given response. */ @GuardedBy("mLock") - private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse, - boolean isPrimary) { + private void setViewStatesLocked( + FillResponse response, int state, boolean clearResponse, boolean isPrimary) { final List datasets = response.getDatasets(); if (datasets != null && !datasets.isEmpty()) { for (int i = 0; i < datasets.size(); i++) { @@ -5964,12 +6587,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - /** - * Sets the state and response of all views in the given dataset. - */ + /** Sets the state and response of all views in the given dataset. */ @GuardedBy("mLock") - private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset, - int state, boolean clearResponse, boolean isPrimary) { + private void setViewStatesLocked( + @Nullable FillResponse response, + @NonNull Dataset dataset, + int state, + boolean clearResponse, + boolean isPrimary) { final ArrayList ids = dataset.getFieldIds(); final ArrayList values = dataset.getFieldValues(); for (int j = 0; j < ids.size(); j++) { @@ -5989,10 +6614,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state, - @Nullable AutofillValue value) { + private ViewState createOrUpdateViewStateLocked( + @NonNull AutofillId id, int state, @Nullable AutofillValue value) { ViewState viewState = mViewStates.get(id); - if (viewState != null) { + if (viewState != null) { viewState.setState(state); } else { viewState = new ViewState(id, this, state, mIsPrimaryCredential); @@ -6008,22 +6633,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return viewState; } - void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent, - int uiType) { + void autoFill( + int requestId, int datasetIndex, Dataset dataset, boolean generateEvent, int uiType) { if (sDebug) { - Slog.d(TAG, "autoFill(): requestId=" + requestId + "; datasetIdx=" + datasetIndex - + "; dataset=" + dataset); + Slog.d( + TAG, + "autoFill(): requestId=" + + requestId + + "; datasetIdx=" + + datasetIndex + + "; dataset=" + + dataset); } synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#autoFill() rejected - session: " - + id + " destroyed"); + Slog.w(TAG, "Call to Session#autoFill() rejected - session: " + id + " destroyed"); return; } // Selected dataset id is logged regardless of authentication result. mPresentationStatsEventLogger.maybeSetSelectedDatasetId(datasetIndex); mPresentationStatsEventLogger.maybeSetSelectedDatasetPickReason( - dataset.getEligibleReason()); + dataset.getEligibleReason()); // Autofill it directly... if (dataset.getAuthentication() == null) { if (generateEvent) { @@ -6039,10 +6669,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // ...or handle authentication. mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType); mPresentationStatsEventLogger.maybeSetAuthenticationType( - AUTHENTICATION_TYPE_DATASET_AUTHENTICATION); + AUTHENTICATION_TYPE_DATASET_AUTHENTICATION); // does not matter the value of isPrimary because null response won't be overridden. - setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, - /* clearResponse= */ false, /* isPrimary= */ true); + setViewStatesLocked( + null, + dataset, + ViewState.STATE_WAITING_DATASET_AUTH, + /* clearResponse= */ false, + /* isPrimary= */ true); final Intent fillInIntent; if (dataset.getCredentialFillInIntent() != null && Flags.autofillCredmanIntegration()) { Slog.d(TAG, "Setting credential fill intent"); @@ -6055,11 +6689,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState forceRemoveFromServiceLocked(); return; } - final int authenticationId = AutofillManager.makeAuthenticationId(requestId, - datasetIndex); - startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent, - /* authenticateInline= */false); - + final int authenticationId = + AutofillManager.makeAuthenticationId(requestId, datasetIndex); + startAuthentication( + authenticationId, + dataset.getAuthentication(), + fillInIntent, + /* authenticateInline= */ false); } } @@ -6072,13 +6708,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final FillContext context = getFillContextByRequestIdLocked(requestId); if (context == null) { - wtf(null, "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s", - requestId, mContexts); + wtf( + null, + "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s", + requestId, + mContexts); return null; } if (mLastInlineSuggestionsRequest != null && mLastInlineSuggestionsRequest.first == requestId) { - fillInIntent.putExtra(AutofillManager.EXTRA_INLINE_SUGGESTIONS_REQUEST, + fillInIntent.putExtra( + AutofillManager.EXTRA_INLINE_SUGGESTIONS_REQUEST, mLastInlineSuggestionsRequest.second); } fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure()); @@ -6121,12 +6761,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - private void startAuthentication(int authenticationId, IntentSender intent, - Intent fillInIntent, boolean authenticateInline) { + private void startAuthentication( + int authenticationId, + IntentSender intent, + Intent fillInIntent, + boolean authenticateInline) { try { synchronized (mLock) { - mClient.authenticate(id, authenticationId, intent, fillInIntent, - authenticateInline); + mClient.authenticate( + id, authenticationId, intent, fillInIntent, authenticateInline); } } catch (RemoteException e) { Slog.e(TAG, "Error launching auth intent", e); @@ -6139,22 +6782,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @hide */ static final class SaveResult { - /** - * Whether to record the save dialog has been shown. - */ + /** Whether to record the save dialog has been shown. */ private boolean mLogSaveShown; - /** - * Whether to remove the session. - */ + /** Whether to remove the session. */ private boolean mRemoveSession; - /** - * The reason why a save dialog was not shown. - */ + /** The reason why a save dialog was not shown. */ @NoSaveReason private int mSaveDialogNotShowReason; - SaveResult(boolean logSaveShown, boolean removeSession, + SaveResult( + boolean logSaveShown, + boolean removeSession, @NoSaveReason int saveDialogNotShowReason) { mLogSaveShown = logSaveShown; mRemoveSession = removeSession; @@ -6218,15 +6857,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public String toString() { - return "SaveResult: [logSaveShown=" + mLogSaveShown - + ", removeSession=" + mRemoveSession - + ", saveDialogNotShowReason=" + mSaveDialogNotShowReason + "]"; + return "SaveResult: [logSaveShown=" + + mLogSaveShown + + ", removeSession=" + + mRemoveSession + + ", saveDialogNotShowReason=" + + mSaveDialogNotShowReason + + "]"; } } /** - * Class maintaining the state of the requests to - * {@link android.service.assist.classification.FieldClassificationService}. + * Class maintaining the state of the requests to {@link + * android.service.assist.classification.FieldClassificationService}. */ private static final class ClassificationState { @@ -6234,18 +6877,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Initial state indicating that the request for classification hasn't been triggered yet. */ private static final int STATE_INITIAL = 1; - /** - * Assist request has been triggered, but awaiting response. - */ + + /** Assist request has been triggered, but awaiting response. */ private static final int STATE_PENDING_ASSIST_REQUEST = 2; - /** - * Classification request has been triggered, but awaiting response. - */ + + /** Classification request has been triggered, but awaiting response. */ private static final int STATE_PENDING_REQUEST = 3; - /** - * Classification response has been received. - */ + + /** Classification response has been received. */ private static final int STATE_RESPONSE = 4; + /** * Classification state has been invalidated, and the last response may no longer be valid. * This could occur due to various reasons like views changing their layouts, becoming @@ -6254,15 +6895,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ private static final int STATE_INVALIDATED = 5; - @IntDef(prefix = { "STATE_" }, value = { - STATE_INITIAL, - STATE_PENDING_ASSIST_REQUEST, - STATE_PENDING_REQUEST, - STATE_RESPONSE, - STATE_INVALIDATED - }) + @IntDef( + prefix = {"STATE_"}, + value = { + STATE_INITIAL, + STATE_PENDING_ASSIST_REQUEST, + STATE_PENDING_REQUEST, + STATE_RESPONSE, + STATE_INVALIDATED + }) @Retention(RetentionPolicy.SOURCE) - @interface ClassificationRequestState{} + @interface ClassificationRequestState {} @GuardedBy("mLock") private @ClassificationRequestState int mState = STATE_INITIAL; @@ -6284,8 +6927,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Typically, there would be a 1:1 mapping. However, in certain cases, we may have a hint - * being applicable to many types. An example of this being new/change password forms, - * where you need to confirm the passward twice. + * being applicable to many types. An example of this being new/change password forms, where + * you need to confirm the passward twice. */ @GuardedBy("mLock") private ArrayMap> mHintsToAutofillIdMap; @@ -6317,8 +6960,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Process the response received. + * * @return true if the response was processed, false otherwise. If there wasn't any - * response, yet this function was called, it would return false. + * response, yet this function was called, it would return false. */ @GuardedBy("mLock") private boolean processResponse() { @@ -6358,7 +7002,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private static void processDetections(Set detections, AutofillId id, + private static void processDetections( + Set detections, + AutofillId id, ArrayMap> currentMap) { for (String detection : detections) { Set autofillIds; @@ -6416,37 +7062,67 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public String toString() { return "ClassificationState: [" - + "state=" + stateToString() - + ", mPendingFieldClassificationRequest=" + mPendingFieldClassificationRequest - + ", mLastFieldClassificationResponse=" + mLastFieldClassificationResponse - + ", mClassificationHintsMap=" + mClassificationHintsMap - + ", mClassificationGroupHintsMap=" + mClassificationGroupHintsMap - + ", mHintsToAutofillIdMap=" + mHintsToAutofillIdMap - + ", mGroupHintsToAutofillIdMap=" + mGroupHintsToAutofillIdMap + + "state=" + + stateToString() + + ", mPendingFieldClassificationRequest=" + + mPendingFieldClassificationRequest + + ", mLastFieldClassificationResponse=" + + mLastFieldClassificationResponse + + ", mClassificationHintsMap=" + + mClassificationHintsMap + + ", mClassificationGroupHintsMap=" + + mClassificationGroupHintsMap + + ", mHintsToAutofillIdMap=" + + mHintsToAutofillIdMap + + ", mGroupHintsToAutofillIdMap=" + + mGroupHintsToAutofillIdMap + "]"; } - } @Override public String toString() { - return "Session: [id=" + id + ", component=" + mComponentName - + ", state=" + sessionStateAsString(mSessionState) + "]"; + return "Session: [id=" + + id + + ", component=" + + mComponentName + + ", state=" + + sessionStateAsString(mSessionState) + + "]"; } @GuardedBy("mLock") void dumpLocked(String prefix, PrintWriter pw) { final String prefix2 = prefix + " "; - pw.print(prefix); pw.print("id: "); pw.println(id); - pw.print(prefix); pw.print("uid: "); pw.println(uid); - pw.print(prefix); pw.print("taskId: "); pw.println(taskId); - pw.print(prefix); pw.print("flags: "); pw.println(mFlags); - pw.print(prefix); pw.print("displayId: "); pw.println(mContext.getDisplayId()); - pw.print(prefix); pw.print("state: "); pw.println(sessionStateAsString(mSessionState)); - pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName); - pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); - pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime); - pw.print(prefix); pw.print("Time to show UI: "); + pw.print(prefix); + pw.print("id: "); + pw.println(id); + pw.print(prefix); + pw.print("uid: "); + pw.println(uid); + pw.print(prefix); + pw.print("taskId: "); + pw.println(taskId); + pw.print(prefix); + pw.print("flags: "); + pw.println(mFlags); + pw.print(prefix); + pw.print("displayId: "); + pw.println(mContext.getDisplayId()); + pw.print(prefix); + pw.print("state: "); + pw.println(sessionStateAsString(mSessionState)); + pw.print(prefix); + pw.print("mComponentName: "); + pw.println(mComponentName); + pw.print(prefix); + pw.print("mActivityToken: "); + pw.println(mActivityToken); + pw.print(prefix); + pw.print("mStartTime: "); + pw.println(mStartTime); + pw.print(prefix); + pw.print("Time to show UI: "); if (mUiShownTime == 0) { pw.println("N/A"); } else { @@ -6454,41 +7130,67 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState pw.println(); } final int requestLogsSizes = mRequestLogs.size(); - pw.print(prefix); pw.print("mSessionLogs: "); pw.println(requestLogsSizes); + pw.print(prefix); + pw.print("mSessionLogs: "); + pw.println(requestLogsSizes); for (int i = 0; i < requestLogsSizes; i++) { final int requestId = mRequestLogs.keyAt(i); final LogMaker log = mRequestLogs.valueAt(i); - pw.print(prefix2); pw.print('#'); pw.print(i); pw.print(": req="); - pw.print(requestId); pw.print(", log=" ); dumpRequestLog(pw, log); pw.println(); + pw.print(prefix2); + pw.print('#'); + pw.print(i); + pw.print(": req="); + pw.print(requestId); + pw.print(", log="); + dumpRequestLog(pw, log); + pw.println(); } - pw.print(prefix); pw.print("mResponses: "); + pw.print(prefix); + pw.print("mResponses: "); if (mResponses == null) { pw.println("null"); } else { pw.println(mResponses.size()); for (int i = 0; i < mResponses.size(); i++) { - pw.print(prefix2); pw.print('#'); pw.print(i); - pw.print(' '); pw.println(mResponses.valueAt(i)); - } - } - pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId); - pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed); - pw.print(prefix); pw.print("mShowingSaveUi: "); pw.println(mSessionFlags.mShowingSaveUi); - pw.print(prefix); pw.print("mPendingSaveUi: "); pw.println(mPendingSaveUi); + pw.print(prefix2); + pw.print('#'); + pw.print(i); + pw.print(' '); + pw.println(mResponses.valueAt(i)); + } + } + pw.print(prefix); + pw.print("mCurrentViewId: "); + pw.println(mCurrentViewId); + pw.print(prefix); + pw.print("mDestroyed: "); + pw.println(mDestroyed); + pw.print(prefix); + pw.print("mShowingSaveUi: "); + pw.println(mSessionFlags.mShowingSaveUi); + pw.print(prefix); + pw.print("mPendingSaveUi: "); + pw.println(mPendingSaveUi); final int numberViews = mViewStates.size(); - pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size()); + pw.print(prefix); + pw.print("mViewStates size: "); + pw.println(mViewStates.size()); for (int i = 0; i < numberViews; i++) { - pw.print(prefix); pw.print("ViewState at #"); pw.println(i); + pw.print(prefix); + pw.print("ViewState at #"); + pw.println(i); mViewStates.valueAt(i).dump(prefix2, pw); } - pw.print(prefix); pw.print("mContexts: " ); + pw.print(prefix); + pw.print("mContexts: "); if (mContexts != null) { int numContexts = mContexts.size(); for (int i = 0; i < numContexts; i++) { FillContext context = mContexts.get(i); - pw.print(prefix2); pw.print(context); + pw.print(prefix2); + pw.print(context); if (sVerbose) { pw.println("AssistStructure dumped at logcat)"); @@ -6500,43 +7202,62 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState pw.println("null"); } - pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback); + pw.print(prefix); + pw.print("mHasCallback: "); + pw.println(mHasCallback); if (mClientState != null) { - pw.print(prefix); pw.print("mClientState: "); pw.print(mClientState.getSize()); pw - .println(" bytes"); - } - pw.print(prefix); pw.print("mCompatMode: "); pw.println(mCompatMode); - pw.print(prefix); pw.print("mUrlBar: "); + pw.print(prefix); + pw.print("mClientState: "); + pw.print(mClientState.getSize()); + pw.println(" bytes"); + } + pw.print(prefix); + pw.print("mCompatMode: "); + pw.println(mCompatMode); + pw.print(prefix); + pw.print("mUrlBar: "); if (mUrlBar == null) { pw.println("N/A"); } else { - pw.print("id="); pw.print(mUrlBar.getAutofillId()); - pw.print(" domain="); pw.print(mUrlBar.getWebDomain()); - pw.print(" text="); Helper.printlnRedactedText(pw, mUrlBar.getText()); - } - pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println( - mSaveOnAllViewsInvisible); - pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds); + pw.print("id="); + pw.print(mUrlBar.getAutofillId()); + pw.print(" domain="); + pw.print(mUrlBar.getWebDomain()); + pw.print(" text="); + Helper.printlnRedactedText(pw, mUrlBar.getText()); + } + pw.print(prefix); + pw.print("mSaveOnAllViewsInvisible: "); + pw.println(mSaveOnAllViewsInvisible); + pw.print(prefix); + pw.print("mSelectedDatasetIds: "); + pw.println(mSelectedDatasetIds); if (mSessionFlags.mAugmentedAutofillOnly) { - pw.print(prefix); pw.println("For Augmented Autofill Only"); + pw.print(prefix); + pw.println("For Augmented Autofill Only"); } if (mSessionFlags.mFillDialogDisabled) { - pw.print(prefix); pw.println("Fill Dialog disabled"); + pw.print(prefix); + pw.println("Fill Dialog disabled"); } if (mLastFillDialogTriggerIds != null) { - pw.print(prefix); pw.println("Last Fill Dialog trigger ids: "); + pw.print(prefix); + pw.println("Last Fill Dialog trigger ids: "); pw.println(mSelectedDatasetIds); } if (mAugmentedAutofillDestroyer != null) { - pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer"); + pw.print(prefix); + pw.println("has mAugmentedAutofillDestroyer"); } if (mAugmentedRequestsLogs != null) { - pw.print(prefix); pw.print("number augmented requests: "); + pw.print(prefix); + pw.print("number augmented requests: "); pw.println(mAugmentedRequestsLogs.size()); } if (mAugmentedAutofillableIds != null) { - pw.print(prefix); pw.print("mAugmentedAutofillableIds: "); + pw.print(prefix); + pw.print("mAugmentedAutofillableIds: "); pw.println(mAugmentedAutofillableIds); } if (mRemoteFillService != null) { @@ -6545,21 +7266,32 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } private static void dumpRequestLog(@NonNull PrintWriter pw, @NonNull LogMaker log) { - pw.print("CAT="); pw.print(log.getCategory()); + pw.print("CAT="); + pw.print(log.getCategory()); pw.print(", TYPE="); final int type = log.getType(); switch (type) { - case MetricsEvent.TYPE_SUCCESS: pw.print("SUCCESS"); break; - case MetricsEvent.TYPE_FAILURE: pw.print("FAILURE"); break; - case MetricsEvent.TYPE_CLOSE: pw.print("CLOSE"); break; - default: pw.print("UNSUPPORTED"); - } - pw.print('('); pw.print(type); pw.print(')'); - pw.print(", PKG="); pw.print(log.getPackageName()); - pw.print(", SERVICE="); pw.print(log - .getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE)); - pw.print(", ORDINAL="); pw.print(log - .getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL)); + case MetricsEvent.TYPE_SUCCESS: + pw.print("SUCCESS"); + break; + case MetricsEvent.TYPE_FAILURE: + pw.print("FAILURE"); + break; + case MetricsEvent.TYPE_CLOSE: + pw.print("CLOSE"); + break; + default: + pw.print("UNSUPPORTED"); + } + pw.print('('); + pw.print(type); + pw.print(')'); + pw.print(", PKG="); + pw.print(log.getPackageName()); + pw.print(", SERVICE="); + pw.print(log.getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE)); + pw.print(", ORDINAL="); + pw.print(log.getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL)); dumpNumericValue(pw, log, "FLAGS", MetricsEvent.FIELD_AUTOFILL_FLAGS); dumpNumericValue(pw, log, "NUM_DATASETS", MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS); dumpNumericValue(pw, log, "UI_LATENCY", MetricsEvent.FIELD_AUTOFILL_DURATION); @@ -6569,64 +7301,86 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState pw.print(", AUTH_STATUS="); switch (authStatus) { case MetricsEvent.AUTOFILL_AUTHENTICATED: - pw.print("AUTHENTICATED"); break; + pw.print("AUTHENTICATED"); + break; case MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED: - pw.print("DATASET_AUTHENTICATED"); break; + pw.print("DATASET_AUTHENTICATED"); + break; case MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION: - pw.print("INVALID_AUTHENTICATION"); break; + pw.print("INVALID_AUTHENTICATION"); + break; case MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION: - pw.print("INVALID_DATASET_AUTHENTICATION"); break; - default: pw.print("UNSUPPORTED"); + pw.print("INVALID_DATASET_AUTHENTICATION"); + break; + default: + pw.print("UNSUPPORTED"); } - pw.print('('); pw.print(authStatus); pw.print(')'); + pw.print('('); + pw.print(authStatus); + pw.print(')'); } - dumpNumericValue(pw, log, "FC_IDS", - MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS); - dumpNumericValue(pw, log, "COMPAT_MODE", - MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE); + dumpNumericValue( + pw, log, "FC_IDS", MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS); + dumpNumericValue(pw, log, "COMPAT_MODE", MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE); } - private static void dumpNumericValue(@NonNull PrintWriter pw, @NonNull LogMaker log, - @NonNull String field, int tag) { + private static void dumpNumericValue( + @NonNull PrintWriter pw, @NonNull LogMaker log, @NonNull String field, int tag) { final int value = getNumericValue(log, tag); if (value != 0) { - pw.print(", "); pw.print(field); pw.print('='); pw.print(value); + pw.print(", "); + pw.print(field); + pw.print('='); + pw.print(value); } } - void sendCredentialManagerResponseToApp(@Nullable GetCredentialResponse response, - @Nullable GetCredentialException exception, @NonNull AutofillId viewId) { + void sendCredentialManagerResponseToApp( + @Nullable GetCredentialResponse response, + @Nullable GetCredentialException exception, + @NonNull AutofillId viewId) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#sendCredentialManagerResponseToApp() rejected " - + "- session: " + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#sendCredentialManagerResponseToApp() rejected " + + "- session: " + + id + + " destroyed"); return; } try { final ViewState viewState = mViewStates.get(viewId); if (mService.getMaster().getIsFillFieldsFromCurrentSessionOnly() - && viewState != null && viewState.id.getSessionId() != id) { + && viewState != null + && viewState.id.getSessionId() != id) { if (sVerbose) { - Slog.v(TAG, "Skipping sending credential response to view: " - + viewId + " as it isn't part of the current session: " + id); + Slog.v( + TAG, + "Skipping sending credential response to view: " + + viewId + + " as it isn't part of the current session: " + + id); } } if (exception != null) { if (viewId.isVirtualInt()) { - sendResponseToViewNode(viewId, /*response=*/ null, exception); + sendResponseToViewNode(viewId, /* response= */ null, exception); } else { - mClient.onGetCredentialException(id, viewId, exception.getType(), - exception.getMessage()); + mClient.onGetCredentialException( + id, viewId, exception.getType(), exception.getMessage()); } } else if (response != null) { if (viewId.isVirtualInt()) { - sendResponseToViewNode(viewId, response, /*exception=*/ null); + sendResponseToViewNode(viewId, response, /* exception= */ null); } else { mClient.onGetCredentialResponse(id, viewId, response); } } else { - Slog.w(TAG, "sendCredentialManagerResponseToApp called with null response" - + "and exception"); + Slog.w( + TAG, + "sendCredentialManagerResponseToApp called with null response" + + "and exception"); } } catch (RemoteException e) { Slog.w(TAG, "Error sending credential response to activity: " + e); @@ -6635,23 +7389,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private void sendResponseToViewNode(AutofillId viewId, GetCredentialResponse response, - GetCredentialException exception) { + private void sendResponseToViewNode( + AutofillId viewId, GetCredentialResponse response, GetCredentialException exception) { ViewNode viewNode = getViewNodeFromContextsLocked(viewId); if (viewNode != null && viewNode.getPendingCredentialCallback() != null) { Bundle resultData = new Bundle(); if (response != null) { resultData.putParcelable( - CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, - response); - viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR, - resultData); + CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response); + viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR, resultData); } else if (exception != null) { resultData.putStringArray( CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, new String[] {exception.getType(), exception.getMessage()}); - viewNode.getPendingCredentialCallback().send(FAILURE_CREDMAN_SELECTOR, - resultData); + viewNode.getPendingCredentialCallback().send(FAILURE_CREDMAN_SELECTOR, resultData); } } else { Slog.w(TAG, "View node not found after GetCredentialResponse"); @@ -6661,8 +7412,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void autoFillApp(Dataset dataset) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#autoFillApp() rejected - session: " + id + " destroyed"); return; } try { @@ -6671,8 +7423,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final List ids = new ArrayList<>(entryCount); final List values = new ArrayList<>(entryCount); boolean waitingDatasetAuth = false; - boolean hideHighlight = (entryCount == 1 - && dataset.getFieldIds().get(0).equals(mCurrentViewId)); + boolean hideHighlight = + highlightAutofillSingleField() + ? false + : (entryCount == 1 + && dataset.getFieldIds().get(0).equals(mCurrentViewId)); // Count how many views are filtered because they are not in current session int numOfViewsFiltered = 0; for (int i = 0; i < entryCount; i++) { @@ -6682,10 +7437,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId viewId = dataset.getFieldIds().get(i); final ViewState viewState = mViewStates.get(viewId); if (mService.getMaster().getIsFillFieldsFromCurrentSessionOnly() - && viewState != null && viewState.id.getSessionId() != id) { + && viewState != null + && viewState.id.getSessionId() != id) { if (sVerbose) { - Slog.v(TAG, "Skipping filling view: " + - viewId + " as it isn't part of the current session: " + id); + Slog.v( + TAG, + "Skipping filling view: " + + viewId + + " as it isn't part of the current session: " + + id); } numOfViewsFiltered += 1; continue; @@ -6721,8 +7481,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // does not matter the value of isPrimary because null response won't be // overridden. - setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED, - /* clearResponse= */ false, /* isPrimary= */ true); + setViewStatesLocked( + null, + dataset, + ViewState.STATE_AUTOFILLED, + /* clearResponse= */ false, + /* isPrimary= */ true); } } catch (RemoteException e) { Slog.w(TAG, "Error autofilling activity: " + e); @@ -6750,7 +7514,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionCommittedEventLogger.maybeSetCommitReason(val); mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); mSessionCommittedEventLogger.maybeSetSessionDurationMillis( - SystemClock.elapsedRealtime() - mStartTime); + SystemClock.elapsedRealtime() - mStartTime); mFillRequestEventLogger.logAndEndEvent(); mFillResponseEventLogger.logAndEndEvent(); mPresentationStatsEventLogger.logAndEndEvent("log all events"); @@ -6808,8 +7572,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - final int totalAugmentedRequests = mAugmentedRequestsLogs == null ? 0 - : mAugmentedRequestsLogs.size(); + final int totalAugmentedRequests = + mAugmentedRequestsLogs == null ? 0 : mAugmentedRequestsLogs.size(); if (totalAugmentedRequests > 0) { if (sVerbose) { Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " augmented requests"); @@ -6820,11 +7584,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests); + final LogMaker log = + newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests); if (totalAugmentedRequests > 0) { - log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS, - totalAugmentedRequests); + log.addTaggedData( + MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS, totalAugmentedRequests); } if (mSessionFlags.mAugmentedAutofillOnly) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_AUGMENTED_ONLY, 1); @@ -6846,8 +7611,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void forceRemoveFromServiceIfForAugmentedOnlyLocked() { if (sVerbose) { - Slog.v(TAG, "forceRemoveFromServiceIfForAugmentedOnlyLocked(" + this.id + "): " - + mSessionFlags.mAugmentedAutofillOnly); + Slog.v( + TAG, + "forceRemoveFromServiceIfForAugmentedOnlyLocked(" + + this.id + + "): " + + mSessionFlags.mAugmentedAutofillOnly); } if (!mSessionFlags.mAugmentedAutofillOnly) return; @@ -6880,9 +7649,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - /** - * Thread-safe version of {@link #removeFromServiceLocked()}. - */ + /** Thread-safe version of {@link #removeFromServiceLocked()}. */ private void removeFromService() { synchronized (mLock) { removeFromServiceLocked(); @@ -6897,8 +7664,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void removeFromServiceLocked() { if (sVerbose) Slog.v(TAG, "removeFromServiceLocked(" + this.id + "): " + mPendingSaveUi); if (mDestroyed) { - Slog.w(TAG, "Call to Session#removeFromServiceLocked() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#removeFromServiceLocked() rejected - session: " + + id + + " destroyed"); return; } if (isSaveUiPendingLocked()) { @@ -6922,18 +7692,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Checks whether this session is hiding the Save UI to handle a custom description link for - * a specific {@code token} created by - * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}. + * Checks whether this session is hiding the Save UI to handle a custom description link for a + * specific {@code token} created by {@link PendingUi#PendingUi(IBinder, int, + * IAutoFillManagerClient)}. */ @GuardedBy("mLock") boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) { return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken()); } - /** - * Checks whether this session is hiding the Save UI to handle a custom description link. - */ + /** Checks whether this session is hiding the Save UI to handle a custom description link. */ @GuardedBy("mLock") private boolean isSaveUiPendingLocked() { return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING; @@ -6942,8 +7710,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Return latest response index in mResponses SparseArray. @GuardedBy("mLock") private int getLastResponseIndexLocked() { - if (mResponses == null || mResponses.size() == 0) { - return -1; + if (mResponses == null || mResponses.size() == 0) { + return -1; } List requestIdList = new ArrayList<>(); final int responseCount = mResponses.size(); @@ -6967,15 +7735,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void logAuthenticationStatusLocked(int requestId, int status) { - addTaggedDataToRequestLogLocked(requestId, - MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status); + addTaggedDataToRequestLogLocked( + requestId, MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status); } @GuardedBy("mLock") private void addTaggedDataToRequestLogLocked(int requestId, int tag, @Nullable Object value) { final LogMaker requestLog = mRequestLogs.get(requestId); if (requestLog == null) { - Slog.w(TAG, + Slog.w( + TAG, "addTaggedDataToRequestLogLocked(tag=" + tag + "): no log for id " + requestId); return; } @@ -6983,20 +7752,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private void logAugmentedAutofillRequestLocked(int mode, - ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted, + private void logAugmentedAutofillRequestLocked( + int mode, + ComponentName augmentedRemoteServiceName, + AutofillId focusedId, + boolean isWhitelisted, Boolean isInline) { final String historyItem = - "aug:id=" + id + " u=" + uid + " m=" + mode - + " a=" + ComponentName.flattenToShortString(mComponentName) - + " f=" + focusedId - + " s=" + augmentedRemoteServiceName - + " w=" + isWhitelisted - + " i=" + isInline; + "aug:id=" + + id + + " u=" + + uid + + " m=" + + mode + + " a=" + + ComponentName.flattenToShortString(mComponentName) + + " f=" + + focusedId + + " s=" + + augmentedRemoteServiceName + + " w=" + + isWhitelisted + + " i=" + + isInline; mService.getMaster().logRequestLocked(historyItem); } - private void wtf(@Nullable Exception e, String fmt, Object...args) { + private void wtf(@Nullable Exception e, String fmt, Object... args) { final String message = String.format(fmt, args); synchronized (mLock) { mWtfHistory.log(message); @@ -7051,13 +7833,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mClassificationState.updateResponseReceived(response); } - public void onClassificationRequestFailure(int requestId, @Nullable CharSequence message) { + public void onClassificationRequestFailure(int requestId, @Nullable CharSequence message) {} - } - - public void onClassificationRequestTimeout(int requestId) { - - } + public void onClassificationRequestTimeout(int requestId) {} @Override public void onServiceDied(@NonNull RemoteFieldClassificationService service) { @@ -7070,12 +7848,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void logFieldClassificationEvent( - long startTime, FieldClassificationResponse response, + long startTime, + FieldClassificationResponse response, @FieldClassificationEventLogger.FieldClassificationStatus int status) { final FieldClassificationEventLogger logger = FieldClassificationEventLogger.createLogger(); logger.startNewLogForRequest(); - logger.maybeSetLatencyMillis( - SystemClock.elapsedRealtime() - startTime); + logger.maybeSetLatencyMillis(SystemClock.elapsedRealtime() - startTime); logger.maybeSetAppPackageUid(uid); logger.maybeSetNextFillRequestId(mFillRequestIdSnapshot + 1); logger.maybeSetRequestId(sIdCounterForPcc.get()); -- GitLab From 15f16ed6b7dc6fd0690eb997d2f94bf995ebb8e7 Mon Sep 17 00:00:00 2001 From: Kevin Jeon Date: Thu, 26 Sep 2024 18:09:17 -0400 Subject: [PATCH 113/441] Introduce oomadjuster_cached_app_tiers flag This change adds a flag for cached app tiers in OomAdjuster. The default-disabled constant for this functionality has been replaced with a flag check. Test: Build successfully, check that the flag exists. Bug: 369893532 Flag: com.android.server.am.oomadjuster_cached_app_tiers Change-Id: I71445a19328724f77ad489d45433ce82be4be529 --- .../com/android/server/am/ActivityManagerConstants.java | 2 +- services/core/java/com/android/server/am/flags.aconfig | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index f5a297bfd4f5..65a2c187f1c8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -246,7 +246,7 @@ final class ActivityManagerConstants extends ContentObserver { static final int DEFAULT_MAX_SERVICE_CONNECTIONS_PER_PROCESS = 3000; - private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false; + private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = Flags.oomadjusterCachedAppTiers(); private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000; /** diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index adf0e640f6bf..d67fea09f945 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -225,3 +225,11 @@ flag { description: "Migrate OomAdjuster pulled device state to a push model" bug: "302575389" } + +flag { + name: "oomadjuster_cached_app_tiers" + namespace: "system_performance" + is_fixed_read_only: true + description: "Assign cached oom_score_adj in tiers." + bug: "369893532" +} -- GitLab From c15d6de9b0fad72961fc7f075b75514b6728fe2b Mon Sep 17 00:00:00 2001 From: Brad Ebinger Date: Wed, 9 Oct 2024 17:20:51 -0700 Subject: [PATCH 114/441] Remove Recursion in Session Management/Traversal In some cases when Telecom is generating many sessions, deeply nested sessions have a risk of creating a stack overflow when trying to end a session and clean up intermediate nodes. Instead of using recursion, this change moves all traversal to iterative while loops, which do not require the creation of a new stack frame for each node. - Iteration is also faster, for 5 incoming calls before and after the change, I was seeing anywhere from 2%-20% speedups during incoming call setup and disconnect phases. Bug: 370349160 Flag: com.android.server.telecom.flags.end_session_improvements Test: atest TelecomUnitTests; manual testing Change-Id: Ib21af6c1dc303ea0c50544bb09423df72ac52a41 --- .../java/android/telecom/Logging/Session.java | 240 ++++++++++++------ .../telecom/Logging/SessionManager.java | 126 ++++++--- 2 files changed, 262 insertions(+), 104 deletions(-) diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java index e2fb6019f30a..ad0d4f4de3ae 100644 --- a/telecomm/java/android/telecom/Logging/Session.java +++ b/telecomm/java/android/telecom/Logging/Session.java @@ -16,7 +16,6 @@ package android.telecom.Logging; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -24,8 +23,13 @@ import android.telecom.Log; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.telecom.flags.Flags; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** * Stores information about a thread's point of entry into that should persist until that thread @@ -55,7 +59,7 @@ public class Session { * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()} * if the Session is canceled. */ - public static final int UNDEFINED = -1; + public static final long UNDEFINED = -1; public static class Info implements Parcelable { public final String sessionId; @@ -129,46 +133,39 @@ public class Session { } } - private String mSessionId; - private String mShortMethodName; + private final String mSessionId; + private volatile String mShortMethodName; private long mExecutionStartTimeMs; private long mExecutionEndTimeMs = UNDEFINED; - private Session mParentSession; - private ArrayList mChildSessions; + private volatile Session mParentSession; + private final ArrayList mChildSessions = new ArrayList<>(5); private boolean mIsCompleted = false; - private boolean mIsExternal = false; - private int mChildCounter = 0; + private final boolean mIsExternal; + private final AtomicInteger mChildCounter = new AtomicInteger(0); // True if this is a subsession that has been started from the same thread as the parent // session. This can happen if Log.startSession(...) is called multiple times on the same // thread in the case of one Telecom entry point method calling another entry point method. // In this case, we can just make this subsession "invisible," but still keep track of it so // that the Log.endSession() calls match up. - private boolean mIsStartedFromActiveSession = false; + private final boolean mIsStartedFromActiveSession; // Optionally provided info about the method/class/component that started the session in order // to make Logging easier. This info will be provided in parentheses along with the session. - private String mOwnerInfo; + private final String mOwnerInfo; // Cache Full Method path so that recursive population of the full method path only needs to // be calculated once. - private String mFullMethodPathCache; + private volatile String mFullMethodPathCache; public Session(String sessionId, String shortMethodName, long startTimeMs, - boolean isStartedFromActiveSession, String ownerInfo) { - setSessionId(sessionId); + boolean isStartedFromActiveSession, boolean isExternal, String ownerInfo) { + mSessionId = (sessionId != null) ? sessionId : "???"; setShortMethodName(shortMethodName); mExecutionStartTimeMs = startTimeMs; mParentSession = null; - mChildSessions = new ArrayList<>(5); mIsStartedFromActiveSession = isStartedFromActiveSession; + mIsExternal = isExternal; mOwnerInfo = ownerInfo; } - public void setSessionId(@NonNull String sessionId) { - if (sessionId == null) { - mSessionId = "?"; - } - mSessionId = sessionId; - } - public String getShortMethodName() { return mShortMethodName; } @@ -180,10 +177,6 @@ public class Session { mShortMethodName = shortMethodName; } - public void setIsExternal(boolean isExternal) { - mIsExternal = isExternal; - } - public boolean isExternal() { return mIsExternal; } @@ -193,13 +186,15 @@ public class Session { } public void addChild(Session childSession) { - if (childSession != null) { + if (childSession == null) return; + synchronized (mChildSessions) { mChildSessions.add(childSession); } } public void removeChild(Session child) { - if (child != null) { + if (child == null) return; + synchronized (mChildSessions) { mChildSessions.remove(child); } } @@ -217,7 +212,9 @@ public class Session { } public ArrayList getChildSessions() { - return mChildSessions; + synchronized (mChildSessions) { + return new ArrayList<>(mChildSessions); + } } public boolean isSessionCompleted() { @@ -259,17 +256,41 @@ public class Session { return mExecutionEndTimeMs - mExecutionStartTimeMs; } - public synchronized String getNextChildId() { - return String.valueOf(mChildCounter++); + public String getNextChildId() { + return String.valueOf(mChildCounter.getAndIncrement()); } - // Builds full session id recursively + // Builds full session ID, which incliudes the optional external indicators (E), + // base session ID, and the optional sub-session IDs (_X): @[E-]...[ID][_X][_Y]... private String getFullSessionId() { - return getFullSessionId(0); + if (!Flags.endSessionImprovements()) return getFullSessionIdRecursive(0); + int currParentCount = 0; + StringBuilder id = new StringBuilder(); + Session currSession = this; + while (currSession != null) { + Session parentSession = currSession.getParentSession(); + if (parentSession != null) { + if (currParentCount >= SESSION_RECURSION_LIMIT) { + id.insert(0, getSessionId()); + id.insert(0, TRUNCATE_STRING); + android.util.Slog.w(LOG_TAG, "getFullSessionId: Hit iteration limit!"); + return id.toString(); + } + if (Log.VERBOSE) { + id.insert(0, currSession.getSessionId()); + id.insert(0, SESSION_SEPARATION_CHAR_CHILD); + } + } else { + id.insert(0, currSession.getSessionId()); + } + currSession = parentSession; + currParentCount++; + } + return id.toString(); } // keep track of calls and bail if we hit the recursion limit - private String getFullSessionId(int parentCount) { + private String getFullSessionIdRecursive(int parentCount) { if (parentCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it will // try to add session information to this logging statement, which will cause it to hit @@ -286,12 +307,12 @@ public class Session { return mSessionId; } else { if (Log.VERBOSE) { - return parentSession.getFullSessionId(parentCount + 1) + return parentSession.getFullSessionIdRecursive(parentCount + 1) // Append "_X" to subsession to show subsession designation. + SESSION_SEPARATION_CHAR_CHILD + mSessionId; } else { // Only worry about the base ID at the top of the tree. - return parentSession.getFullSessionId(parentCount + 1); + return parentSession.getFullSessionIdRecursive(parentCount + 1); } } @@ -300,16 +321,18 @@ public class Session { private Session getRootSession(String callingMethod) { int currParentCount = 0; Session topNode = this; - while (topNode.getParentSession() != null) { + Session parentNode = topNode.getParentSession(); + while (parentNode != null) { if (currParentCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it // will try to add session information to this logging statement, which will cause // it to hit this condition again and so on... - android.util.Slog.w(LOG_TAG, "getRootSession: Hit recursion limit from " + android.util.Slog.w(LOG_TAG, "getRootSession: Hit iteration limit from " + callingMethod); break; } - topNode = topNode.getParentSession(); + topNode = parentNode; + parentNode = topNode.getParentSession(); currParentCount++; } return topNode; @@ -320,14 +343,40 @@ public class Session { return getRootSession("printFullSessionTree").printSessionTree(); } - // Recursively move down session tree using DFS, but print out each node when it is reached. private String printSessionTree() { StringBuilder sb = new StringBuilder(); - printSessionTree(0, sb, 0); + if (!Flags.endSessionImprovements()) { + printSessionTreeRecursive(0, sb, 0); + return sb.toString(); + } + int depth = 0; + ArrayDeque deque = new ArrayDeque<>(); + deque.add(this); + while (!deque.isEmpty()) { + Session node = deque.pollFirst(); + sb.append("\t".repeat(depth)); + sb.append(node.toString()); + sb.append("\n"); + if (depth >= SESSION_RECURSION_LIMIT) { + sb.append(TRUNCATE_STRING); + depth -= 1; + continue; + } + List childSessions = node.getChildSessions().reversed(); + if (!childSessions.isEmpty()) { + depth += 1; + for (Session child : childSessions) { + deque.addFirst(child); + } + } else { + depth -= 1; + } + } return sb.toString(); } - private void printSessionTree(int tabI, StringBuilder sb, int currChildCount) { + // Recursively move down session tree using DFS, but print out each node when it is reached. + private void printSessionTreeRecursive(int tabI, StringBuilder sb, int currChildCount) { // Prevent infinite recursion. if (currChildCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it will @@ -343,26 +392,85 @@ public class Session { for (int i = 0; i <= tabI; i++) { sb.append("\t"); } - child.printSessionTree(tabI + 1, sb, currChildCount + 1); + child.printSessionTreeRecursive(tabI + 1, sb, currChildCount + 1); } } - // Recursively concatenate mShortMethodName with the parent Sessions to create full method - // path. if truncatePath is set to true, all other external sessions (except for the most - // recent) will be truncated to "..." + // + + /** + * Concatenate the short method name with the parent Sessions to create full method path. + * @param truncatePath if truncatePath is set to true, all other external sessions (except for + * the most recent) will be truncated to "..." + * @return The full method path associated with this Session. + */ + @VisibleForTesting public String getFullMethodPath(boolean truncatePath) { StringBuilder sb = new StringBuilder(); - getFullMethodPath(sb, truncatePath, 0); + if (!Flags.endSessionImprovements()) { + getFullMethodPathRecursive(sb, truncatePath, 0); + return sb.toString(); + } + // Check to see if the session has been renamed yet. If it has not, then the session + // has not been continued. + Session parentSession = getParentSession(); + boolean isSessionStarted = parentSession == null + || !getShortMethodName().equals(parentSession.getShortMethodName()); + int depth = 0; + Session currSession = this; + while (currSession != null) { + String cache = currSession.mFullMethodPathCache; + // Return cached value for method path. When returning the truncated path, recalculate + // the full path without using the cached value. + if (!TextUtils.isEmpty(cache) && !truncatePath) { + sb.insert(0, cache); + return sb.toString(); + } + + parentSession = currSession.getParentSession(); + // Encapsulate the external session's method name so it is obvious what part of the + // session is external or truncate it if we do not want the entire history. + if (currSession.isExternal()) { + if (truncatePath) { + sb.insert(0, TRUNCATE_STRING); + } else { + sb.insert(0, ")"); + sb.insert(0, currSession.getShortMethodName()); + sb.insert(0, "("); + } + } else { + sb.insert(0, currSession.getShortMethodName()); + } + if (parentSession != null) { + sb.insert(0, SUBSESSION_SEPARATION_CHAR); + } + + if (depth >= SESSION_RECURSION_LIMIT) { + // Don't use Telecom's Log.w here or it will cause infinite recursion because it + // will try to add session information to this logging statement, which will cause + // it to hit this condition again and so on... + android.util.Slog.w(LOG_TAG, "getFullMethodPath: Hit iteration limit!"); + sb.insert(0, TRUNCATE_STRING); + return sb.toString(); + } + currSession = parentSession; + depth++; + } + if (isSessionStarted && !truncatePath) { + // Cache the full method path for this node so that we do not need to calculate it + // again in the future. + mFullMethodPathCache = sb.toString(); + } return sb.toString(); } - private synchronized void getFullMethodPath(StringBuilder sb, boolean truncatePath, + private synchronized void getFullMethodPathRecursive(StringBuilder sb, boolean truncatePath, int parentCount) { if (parentCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it will // try to add session information to this logging statement, which will cause it to hit // this condition again and so on... - android.util.Slog.w(LOG_TAG, "getFullMethodPath: Hit recursion limit!"); + android.util.Slog.w(LOG_TAG, "getFullMethodPathRecursive: Hit recursion limit!"); sb.append(TRUNCATE_STRING); return; } @@ -378,7 +486,7 @@ public class Session { // Check to see if the session has been renamed yet. If it has not, then the session // has not been continued. isSessionStarted = !mShortMethodName.equals(parentSession.mShortMethodName); - parentSession.getFullMethodPath(sb, truncatePath, parentCount + 1); + parentSession.getFullMethodPathRecursive(sb, truncatePath, parentCount + 1); sb.append(SUBSESSION_SEPARATION_CHAR); } // Encapsulate the external session's method name so it is obvious what part of the session @@ -409,14 +517,14 @@ public class Session { @Override public int hashCode() { - int result = mSessionId != null ? mSessionId.hashCode() : 0; - result = 31 * result + (mShortMethodName != null ? mShortMethodName.hashCode() : 0); - result = 31 * result + (int) (mExecutionStartTimeMs ^ (mExecutionStartTimeMs >>> 32)); - result = 31 * result + (int) (mExecutionEndTimeMs ^ (mExecutionEndTimeMs >>> 32)); + int result = mSessionId.hashCode(); + result = 31 * result + mShortMethodName.hashCode(); + result = 31 * result + Long.hashCode(mExecutionStartTimeMs); + result = 31 * result + Long.hashCode(mExecutionEndTimeMs); result = 31 * result + (mParentSession != null ? mParentSession.hashCode() : 0); - result = 31 * result + (mChildSessions != null ? mChildSessions.hashCode() : 0); + result = 31 * result + mChildSessions.hashCode(); result = 31 * result + (mIsCompleted ? 1 : 0); - result = 31 * result + mChildCounter; + result = 31 * result + mChildCounter.hashCode(); result = 31 * result + (mIsStartedFromActiveSession ? 1 : 0); result = 31 * result + (mOwnerInfo != null ? mOwnerInfo.hashCode() : 0); return result; @@ -432,23 +540,13 @@ public class Session { if (mExecutionStartTimeMs != session.mExecutionStartTimeMs) return false; if (mExecutionEndTimeMs != session.mExecutionEndTimeMs) return false; if (mIsCompleted != session.mIsCompleted) return false; - if (mChildCounter != session.mChildCounter) return false; + if (!(mChildCounter.get() == session.mChildCounter.get())) return false; if (mIsStartedFromActiveSession != session.mIsStartedFromActiveSession) return false; - if (mSessionId != null ? - !mSessionId.equals(session.mSessionId) : session.mSessionId != null) - return false; - if (mShortMethodName != null ? !mShortMethodName.equals(session.mShortMethodName) - : session.mShortMethodName != null) - return false; - if (mParentSession != null ? !mParentSession.equals(session.mParentSession) - : session.mParentSession != null) - return false; - if (mChildSessions != null ? !mChildSessions.equals(session.mChildSessions) - : session.mChildSessions != null) - return false; - return mOwnerInfo != null ? mOwnerInfo.equals(session.mOwnerInfo) - : session.mOwnerInfo == null; - + if (!Objects.equals(mSessionId, session.mSessionId)) return false; + if (!Objects.equals(mShortMethodName, session.mShortMethodName)) return false; + if (!Objects.equals(mParentSession, session.mParentSession)) return false; + if (!Objects.equals(mChildSessions, session.mChildSessions)) return false; + return Objects.equals(mOwnerInfo, session.mOwnerInfo); } @Override diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java index 9d17219c1ae4..00e344c67cc5 100644 --- a/telecomm/java/android/telecom/Logging/SessionManager.java +++ b/telecomm/java/android/telecom/Logging/SessionManager.java @@ -27,6 +27,7 @@ import android.telecom.Log; import android.util.Base64; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.telecom.flags.Flags; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -36,10 +37,16 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** - * TODO: Create better Sessions Documentation + * SessionManager manages the active sessions in a HashMap, which maps the active thread(s) to the + * associated {@link Session}s. + *

+ * Note: Sessions assume that session structure modification is synchronized on this object - only + * one thread can modify the structure of any Session at one time. Printing the current session to + * the log is not synchronized because we should not clean up a session chain while printing from + * another Thread. Either the Session chain is still active and can not be cleaned up yet, or the + * Session chain has ended and we are cleaning up. * @hide */ - public class SessionManager { // Currently using 3 letters, So don't exceed 64^3 @@ -54,11 +61,11 @@ public class SessionManager { private Context mContext; @VisibleForTesting - public ConcurrentHashMap mSessionMapper = new ConcurrentHashMap<>(100); + public final ConcurrentHashMap mSessionMapper = new ConcurrentHashMap<>(64); @VisibleForTesting public java.lang.Runnable mCleanStaleSessions = () -> cleanupStaleSessions(getSessionCleanupTimeoutMs()); - private Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper()); + private final Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper()); // Overridden in LogTest to skip query to ContentProvider private interface ISessionCleanupTimeoutMs { @@ -83,7 +90,7 @@ public class SessionManager { }; // Usage is synchronized on this class. - private List mSessionListeners = new ArrayList<>(); + private final List mSessionListeners = new ArrayList<>(); public interface ISessionListener { /** @@ -110,10 +117,19 @@ public class SessionManager { } private synchronized void resetStaleSessionTimer() { - mSessionCleanupHandler.removeCallbacksAndMessages(null); - // Will be null in Log Testing - if (mCleanStaleSessions != null) { - mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs()); + if (!Flags.endSessionImprovements()) { + mSessionCleanupHandler.removeCallbacksAndMessages(null); + // Will be null in Log Testing + if (mCleanStaleSessions != null) { + mSessionCleanupHandler.postDelayed(mCleanStaleSessions, + getSessionCleanupTimeoutMs()); + } + } else { + if (mCleanStaleSessions != null + && !mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) { + mSessionCleanupHandler.postDelayed(mCleanStaleSessions, + getSessionCleanupTimeoutMs()); + } } } @@ -147,13 +163,11 @@ public class SessionManager { Session childSession = createSubsession(true); continueSession(childSession, shortMethodName); return; - } else { - // Only Log that we are starting the parent session. - Log.d(LOGGING_TAG, Session.START_SESSION); } Session newSession = new Session(getNextSessionID(), shortMethodName, - System.currentTimeMillis(), false, callerIdentification); + System.currentTimeMillis(), false, false, callerIdentification); mSessionMapper.put(threadId, newSession); + Log.d(LOGGING_TAG, Session.START_SESSION); } /** @@ -179,17 +193,16 @@ public class SessionManager { } // Create Session from Info and add to the sessionMapper under this ID. - Log.d(LOGGING_TAG, Session.START_EXTERNAL_SESSION); Session externalSession = new Session(Session.EXTERNAL_INDICATOR + sessionInfo.sessionId, - sessionInfo.methodPath, System.currentTimeMillis(), - false /*isStartedFromActiveSession*/, sessionInfo.ownerInfo); - externalSession.setIsExternal(true); + sessionInfo.methodPath, System.currentTimeMillis(), false, true, + sessionInfo.ownerInfo); // Mark the external session as already completed, since we have no way of knowing when // the external session actually has completed. externalSession.markSessionCompleted(Session.UNDEFINED); // Track the external session with the SessionMapper so that we can create and continue // an active subsession based on it. mSessionMapper.put(threadId, externalSession); + Log.d(LOGGING_TAG, Session.START_EXTERNAL_SESSION); // Create a subsession from this external Session parent node Session childSession = createSubsession(); continueSession(childSession, shortMethodName); @@ -226,13 +239,12 @@ public class SessionManager { // Start execution time of the session will be overwritten in continueSession(...). Session newSubsession = new Session(threadSession.getNextChildId(), threadSession.getShortMethodName(), System.currentTimeMillis(), - isStartedFromActiveSession, threadSession.getOwnerInfo()); + isStartedFromActiveSession, false, threadSession.getOwnerInfo()); threadSession.addChild(newSubsession); newSubsession.setParentSession(threadSession); if (!isStartedFromActiveSession) { - Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " " + - newSubsession.toString()); + Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION); } else { Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " (Invisible subsession)"); @@ -273,7 +285,7 @@ public class SessionManager { } subsession.markSessionCompleted(Session.UNDEFINED); - endParentSessions(subsession); + cleanupSessionTreeAndNotify(subsession); } /** @@ -328,7 +340,7 @@ public class SessionManager { // Remove after completed so that reference still exists for logging the end events Session parentSession = completedSession.getParentSession(); mSessionMapper.remove(threadId); - endParentSessions(completedSession); + cleanupSessionTreeAndNotify(completedSession); // If this subsession was started from a parent session using Log.startSession, return the // ThreadID back to the parent after completion. if (parentSession != null && !parentSession.isSessionCompleted() && @@ -337,8 +349,49 @@ public class SessionManager { } } + /** + * Move up the session tree and remove completed sessions until we either hit a session that was + * not completed yet or we reach the root node. Once we reach the root node, we will report the + * session times to session complete listeners. + * @param session The Session to clean up. + */ + private void cleanupSessionTreeAndNotify(Session session) { + if (session == null) return; + if (!Flags.endSessionImprovements()) { + endParentSessionsRecursive(session); + return; + } + Session currSession = session; + // Traverse upwards and unlink until we either hit the root node or a node that isn't + // complete yet. + while (currSession != null) { + if (!currSession.isSessionCompleted() || !currSession.getChildSessions().isEmpty()) { + // We will return once the active session is completed. + return; + } + Session parentSession = currSession.getParentSession(); + currSession.setParentSession(null); + // The session is either complete when we have reached the top node or we have reached + // the node where the parent is external. We only want to report the time it took to + // complete the local session, so for external nodes, report finished when the sub-node + // completes. + boolean reportSessionComplete = + (parentSession == null && !currSession.isExternal()) + || (parentSession != null && parentSession.isExternal()); + if (parentSession != null) parentSession.removeChild(currSession); + if (reportSessionComplete) { + long fullSessionTimeMs = System.currentTimeMillis() + - currSession.getExecutionStartTimeMilliseconds(); + Log.d(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs + + " ms): " + currSession); + notifySessionCompleteListeners(currSession.getShortMethodName(), fullSessionTimeMs); + } + currSession = parentSession; + } + } + // Recursively deletes all complete parent sessions of the current subsession if it is a leaf. - private void endParentSessions(Session subsession) { + private void endParentSessionsRecursive(Session subsession) { // Session is not completed or not currently a leaf, so we can not remove because a child is // still running if (!subsession.isSessionCompleted() || subsession.getChildSessions().size() != 0) { @@ -355,7 +408,7 @@ public class SessionManager { System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds(); notifySessionCompleteListeners(subsession.getShortMethodName(), fullSessionTimeMs); } - endParentSessions(parentSession); + endParentSessionsRecursive(parentSession); } else { // All of the subsessions have been completed and it is time to report on the full // running time of the session. @@ -370,8 +423,10 @@ public class SessionManager { } private void notifySessionCompleteListeners(String methodName, long sessionTimeMs) { - for (ISessionListener l : mSessionListeners) { - l.sessionComplete(methodName, sessionTimeMs); + synchronized (mSessionListeners) { + for (ISessionListener l : mSessionListeners) { + l.sessionComplete(methodName, sessionTimeMs); + } } } @@ -380,8 +435,8 @@ public class SessionManager { return currentSession != null ? currentSession.toString() : ""; } - public synchronized void registerSessionListener(ISessionListener l) { - if (l != null) { + public void registerSessionListener(ISessionListener l) { + synchronized (mSessionListeners) { mSessionListeners.add(l); } } @@ -425,25 +480,30 @@ public class SessionManager { @VisibleForTesting public synchronized void cleanupStaleSessions(long timeoutMs) { - String logMessage = "Stale Sessions Cleaned:\n"; + StringBuilder logMessage = new StringBuilder("Stale Sessions Cleaned:"); boolean isSessionsStale = false; long currentTimeMs = System.currentTimeMillis(); // Remove references that are in the Session Mapper (causing GC to occur) on - // sessions that are lasting longer than LOGGING_SESSION_TIMEOUT_MS. + // sessions that are lasting longer than DEFAULT_SESSION_TIMEOUT_MS. // If this occurs, then there is most likely a Session active that never had // Log.endSession called on it. for (Iterator> it = mSessionMapper.entrySet().iterator(); it.hasNext(); ) { ConcurrentHashMap.Entry entry = it.next(); Session session = entry.getValue(); - if (currentTimeMs - session.getExecutionStartTimeMilliseconds() > timeoutMs) { + long runTime = currentTimeMs - session.getExecutionStartTimeMilliseconds(); + if (runTime > timeoutMs) { it.remove(); - logMessage += session.printFullSessionTree() + "\n"; + logMessage.append("\n"); + logMessage.append("["); + logMessage.append(runTime); + logMessage.append("ms] "); + logMessage.append(session.printFullSessionTree()); isSessionsStale = true; } } if (isSessionsStale) { - Log.w(LOGGING_TAG, logMessage); + Log.w(LOGGING_TAG, logMessage.toString()); } else { Log.v(LOGGING_TAG, "No stale logging sessions needed to be cleaned..."); } -- GitLab From 131ac7758ca30922bb203e74b519c99cacba78ab Mon Sep 17 00:00:00 2001 From: Ashley Holton Date: Thu, 10 Oct 2024 15:44:27 +0000 Subject: [PATCH 115/441] Update native input manager when display group removed If per-display wake by touch flag is enabled, reset display interactivities with current existing displays and inform native input manager of the change. Flag: com.android.server.power.feature.flags.per_display_wake_by_touch Bug: 372680564 Test: atest NotifierTest, CtsInputTestCases Change-Id: Ic6095d53322cb7cf91d06bb3f1b3900ea5768a80 --- .../com/android/server/power/Notifier.java | 4 +++ .../android/server/power/NotifierTest.java | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 4fae798f0cef..4ecc860e691e 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -771,6 +771,10 @@ public class Notifier { public void onGroupRemoved(int groupId) { mInteractivityByGroupId.remove(groupId); mWakefulnessSessionObserver.removePowerGroup(groupId); + if (mFlags.isPerDisplayWakeByTouchEnabled()) { + resetDisplayInteractivities(); + mInputManagerInternal.setDisplayInteractivities(mDisplayInteractivities); + } } /** diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index a1db18232c09..1c7fc63efd41 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -54,6 +54,7 @@ import android.os.test.TestLooper; import android.provider.Settings; import android.testing.TestableContext; import android.util.IntArray; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.Display; import android.view.DisplayAddress; @@ -383,6 +384,32 @@ public class NotifierTest { verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities); } + @Test + public void testOnGroupRemoved_perDisplayWakeByTouchEnabled() { + createNotifier(); + // GIVEN per-display wake by touch is enabled and one display group has been defined + when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(true); + final int groupId = 313; + final int displayId1 = 3113; + final int displayId2 = 4114; + final int[] displays = new int[]{displayId1, displayId2}; + when(mDisplayManagerInternal.getDisplayIds()).thenReturn(IntArray.wrap(displays)); + when(mDisplayManagerInternal.getDisplayIdsForGroup(groupId)).thenReturn(displays); + mNotifier.onGroupWakefulnessChangeStarted( + groupId, WAKEFULNESS_AWAKE, PowerManager.WAKE_REASON_TAP, /* eventTime= */ 1000); + final SparseBooleanArray expectedDisplayInteractivities = new SparseBooleanArray(); + expectedDisplayInteractivities.put(displayId1, true); + expectedDisplayInteractivities.put(displayId2, true); + verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities); + + // WHEN display group is removed + when(mDisplayManagerInternal.getDisplayIdsByGroupsIds()).thenReturn(new SparseArray<>()); + mNotifier.onGroupRemoved(groupId); + + // THEN native input manager is informed that displays in that group no longer exist + verify(mInputManagerInternal).setDisplayInteractivities(new SparseBooleanArray()); + } + @Test public void testOnWakeLockListener_RemoteException_NoRethrow() throws RemoteException { when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true); -- GitLab From 86ca6ce9eaabbfbde8d477040ce0838492d06434 Mon Sep 17 00:00:00 2001 From: Merissa Mitchell Date: Mon, 7 Oct 2024 13:00:12 -0700 Subject: [PATCH 116/441] [PIP2] Add Shell requested remove PiP transition. Recall: http://recall/clips/91a0fe99-d96b-4639-9812-03167a33c4b0 Bug: 372048924 Bug: 372320038 Test: Launch PiP and manually verify remove button on PiP menu removes PiP window. Flag: com.android.wm.shell.enable_pip2 Change-Id: Icad8428a6e28eb1d27d0220976e9acd93a32327d --- .../wm/shell/pip2/phone/PipMotionHelper.java | 2 +- .../wm/shell/pip2/phone/PipScheduler.java | 40 +++++++++++++++++++ .../wm/shell/pip2/phone/PipTaskListener.java | 5 ++- .../wm/shell/pip2/phone/PipTransition.java | 8 +++- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 268c3a20a41a..537ef182bb04 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -350,7 +350,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } cancelPhysicsAnimation(); mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */); - // mPipTaskOrganizer.removePip(); + mPipScheduler.removePipAfterAnimation(); } /** Sets the movement bounds to use to constrain PIP position animations. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index d4f190ebd2a2..118e2b9e5b06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -19,6 +19,7 @@ package com.android.wm.shell.pip2.phone; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; import android.content.BroadcastReceiver; import android.content.Context; @@ -38,6 +39,8 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; +import com.android.wm.shell.pip2.animation.PipAlphaAnimator; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.lang.annotation.Retention; @@ -56,6 +59,8 @@ public class PipScheduler { private final PipTransitionState mPipTransitionState; private PipSchedulerReceiver mSchedulerReceiver; private PipTransitionController mPipTransitionController; + private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + mSurfaceControlTransactionFactory; @Nullable private Runnable mUpdateMovementBoundsRunnable; @@ -109,6 +114,8 @@ public class PipScheduler { ContextCompat.registerReceiver(mContext, mSchedulerReceiver, new IntentFilter(BROADCAST_FILTER), ContextCompat.RECEIVER_EXPORTED); } + mSurfaceControlTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); } ShellExecutor getMainExecutor() { @@ -133,6 +140,18 @@ public class PipScheduler { return wct; } + @Nullable + private WindowContainerTransaction getRemovePipTransaction() { + if (mPipTransitionState.mPipTaskToken == null) { + return null; + } + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(mPipTransitionState.mPipTaskToken, null); + wct.setWindowingMode(mPipTransitionState.mPipTaskToken, WINDOWING_MODE_UNDEFINED); + wct.reorder(mPipTransitionState.mPipTaskToken, false); + return wct; + } + /** * Schedules exit PiP via expand transition. */ @@ -146,6 +165,27 @@ public class PipScheduler { } } + // TODO: Optimize this by running the animation as part of the transition + /** Runs remove PiP animation and schedules remove PiP transition after the animation ends. */ + public void removePipAfterAnimation() { + SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + PipAlphaAnimator animator = new PipAlphaAnimator(mContext, + mPipTransitionState.mPinnedTaskLeash, tx, PipAlphaAnimator.FADE_OUT); + animator.setAnimationEndCallback(this::scheduleRemovePipImmediately); + animator.start(); + } + + /** Schedules remove PiP transition. */ + private void scheduleRemovePipImmediately() { + WindowContainerTransaction wct = getRemovePipTransaction(); + if (wct != null) { + mMainExecutor.execute(() -> { + mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct, + null /* destinationBounds */); + }); + } + } + /** * Schedules resize PiP via double tap. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index c58de2c3512a..373ec806c40c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -90,9 +90,10 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, if (mPictureInPictureParams.equals(params)) { return; } - if (PipUtils.remoteActionsChanged(params.getActions(), mPictureInPictureParams.getActions()) + if (params != null && (PipUtils.remoteActionsChanged(params.getActions(), + mPictureInPictureParams.getActions()) || !PipUtils.remoteActionsMatch(params.getCloseAction(), - mPictureInPictureParams.getCloseAction())) { + mPictureInPictureParams.getCloseAction()))) { for (PipParamsChangedCallback listener : mPipParamsChangedListeners) { listener.onActionsChanged(params.getActions(), params.getCloseAction()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index b57f51aff176..ac1567aba6e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -25,6 +25,7 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP; import android.animation.Animator; @@ -605,8 +606,11 @@ public class PipTransition extends PipTransitionController implements && pipChange.getMode() == TRANSIT_TO_BACK; boolean isPipClosed = info.getType() == TRANSIT_CLOSE && pipChange.getMode() == TRANSIT_CLOSE; - // PiP is being removed if the pinned task is either moved to back or closed. - return isPipMovedToBack || isPipClosed; + // If PiP is dismissed by user (i.e. via dismiss button in PiP menu) + boolean isPipDismissed = info.getType() == TRANSIT_REMOVE_PIP + && pipChange.getMode() == TRANSIT_TO_BACK; + // PiP is being removed if the pinned task is either moved to back, closed, or dismissed. + return isPipMovedToBack || isPipClosed || isPipDismissed; } // -- GitLab From ad9a72c2ecf4e9e26fa1b47bb74c0abcbda4212d Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Mon, 14 Oct 2024 19:32:02 +0000 Subject: [PATCH 117/441] Revert "Update checkKeyIntent" This reverts commit e8d06e714a6214a84ae4db697775dbf858e26666. Reason for revert: wrong version of the fix Change-Id: I4c5daa59db7e4dd91231ab4641590359c297e10d Merged-In: Ied7961c73299681aa5b523cf3f00fd905893116f --- .../com/android/server/accounts/AccountManagerService.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 726df6aea927..36ffc40bf5f7 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4916,8 +4916,6 @@ public class AccountManagerService Log.e(TAG, String.format(tmpl, activityName, pkgName, mAccountType)); return false; } - intent.setComponent(targetActivityInfo.getComponentName()); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); return true; } finally { Binder.restoreCallingIdentity(bid); @@ -4939,16 +4937,13 @@ public class AccountManagerService Bundle simulateBundle = p.readBundle(); p.recycle(); Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT); - if (intent != null) { + if (intent != null && intent.getClass() != Intent.class) { return false; } Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT); if (intent == null) { return (simulateIntent == null); } - if (intent.getClass() != Intent.class || simulateIntent.getClass() != Intent.class) { - return false; - } if (!intent.filterEquals(simulateIntent)) { return false; } -- GitLab From 231d7dfdff8c6da84c64250e21acbf378cd38f40 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Thu, 19 Sep 2024 18:20:22 +0000 Subject: [PATCH 118/441] [SB][Notifs] Initial scaffolding for notification status bar chips. This CL: 1) Adds `ActiveNotificationsInteractor.promotedOngoingNotifications` flow that will emit all the active promoted notifs. (Right now, it just emits every notification, so every notification will have a chip.) 2) Creates `NotifChipsViewModel`, which turns the notification objects into status bar chip model objects. 3) Adds a new `OngoingActivityChipModel.Shown.ShortTimeDelta` model, which supports showing short times like "15 min" or "1 hr". 4) Connects `NotifChipsViewModel` to `OngoingActivityChipsViewModel` to show the notif chips if we don't have any screen recording or call chips. Bug: 364653005 Flag: com.android.systemui.status_bar_ron_chips Test: With flag enabled, verify the top two notifications turn into status bar chips Test: With flag disabled, verify existing screen share and call chips still work Test: atest NotifChipsViewModelTest OngoingActivityChipsViewModelTest Change-Id: I4b2dceb7d61f1c525b4c3ff150fbfcb1935f408b --- .../ui/viewmodel/NotifChipsViewModelTest.kt | 122 ++++++++++++++++++ ...goingActivityChipsWithRonsViewModelTest.kt | 108 +++++++++++++++- .../res/layout/ongoing_activity_chip.xml | 33 ++--- packages/SystemUI/res/values/styles.xml | 14 ++ .../ui/viewmodel/NotifChipsViewModel.kt | 66 ++++++++++ .../ui/binder/OngoingActivityChipBinder.kt | 40 ++++-- .../ui/model/OngoingActivityChipModel.kt | 22 ++++ .../OngoingActivityChipsViewModel.kt | 42 +++--- .../ActiveNotificationsInteractor.kt | 13 ++ .../ui/viewmodel/NotifChipsViewModelKosmos.kt | 24 ++++ .../OngoingActivityChipsViewModelKosmos.kt | 2 + 11 files changed, 438 insertions(+), 48 deletions(-) create mode 100644 packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/ui/viewmodel/NotifChipsViewModelKosmos.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt new file mode 100644 index 000000000000..2872900e01a5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2024 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.chips.notification.ui.viewmodel + +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.ron.ui.viewmodel.notifChipsViewModel +import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +@EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) +class NotifChipsViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val activeNotificationListRepository = kosmos.activeNotificationListRepository + + private val underTest = kosmos.notifChipsViewModel + + @Test + fun chips_noNotifs_empty() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + setNotifs(emptyList()) + + assertThat(latest).isEmpty() + } + + @Test + fun chips_notifMissingStatusBarChipIconView_empty() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = null))) + + assertThat(latest).isEmpty() + } + + @Test + fun chips_oneNotif_statusBarIconViewMatches() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + val icon = mock() + setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon))) + + assertThat(latest).hasSize(1) + val chip = latest!![0] + assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon)) + } + + @Test + fun chips_twoNotifs_twoChips() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + val firstIcon = mock() + val secondIcon = mock() + setNotifs( + listOf( + activeNotificationModel(key = "notif1", statusBarChipIcon = firstIcon), + activeNotificationModel(key = "notif2", statusBarChipIcon = secondIcon), + ) + ) + + assertThat(latest).hasSize(2) + assertIsNotifChip(latest!![0], firstIcon) + assertIsNotifChip(latest!![1], secondIcon) + } + + private fun setNotifs(notifs: List) { + activeNotificationListRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { notifs.forEach { addIndividualNotif(it) } } + .build() + testScope.runCurrent() + } + + companion object { + fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) { + assertThat(latest) + .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertThat((latest as OngoingActivityChipModel.Shown).icon) + .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon)) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt index 631120b39805..c5b857fc2b80 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt @@ -34,8 +34,10 @@ import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager import com.android.systemui.res.R import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository +import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection +import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModelTest.Companion.addDemoRonChip import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel @@ -46,6 +48,10 @@ import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsVie import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog import com.android.systemui.statusbar.commandline.commandRegistry +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel @@ -83,6 +89,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { private val screenRecordState = kosmos.screenRecordRepository.screenRecordState private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState private val callRepo = kosmos.ongoingCallRepository + private val activeNotificationListRepository = kosmos.activeNotificationListRepository private val pw = PrintWriter(StringWriter()) @@ -107,7 +114,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { val icon = BitmapDrawable( context.resources, - Bitmap.createBitmap(/* width= */ 100, /* height= */ 100, Bitmap.Config.ARGB_8888) + Bitmap.createBitmap(/* width= */ 100, /* height= */ 100, Bitmap.Config.ARGB_8888), ) whenever(kosmos.packageManager.getApplicationIcon(any())).thenReturn(icon) } @@ -287,6 +294,97 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } + @Test + fun chips_singleNotifChip_primaryIsNotifSecondaryIsHidden() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + val icon = mock() + setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon))) + + assertIsNotifChip(latest!!.primary, icon) + assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + } + + @Test + fun chips_twoNotifChips_primaryAndSecondaryAreNotifsInOrder() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + val firstIcon = mock() + val secondIcon = mock() + setNotifs( + listOf( + activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon), + activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon), + ) + ) + + assertIsNotifChip(latest!!.primary, firstIcon) + assertIsNotifChip(latest!!.secondary, secondIcon) + } + + @Test + fun chips_threeNotifChips_topTwoShown() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + val firstIcon = mock() + val secondIcon = mock() + val thirdIcon = mock() + setNotifs( + listOf( + activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon), + activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon), + activeNotificationModel(key = "thirdNotif", statusBarChipIcon = thirdIcon), + ) + ) + + assertIsNotifChip(latest!!.primary, firstIcon) + assertIsNotifChip(latest!!.secondary, secondIcon) + } + + @Test + fun chips_callAndNotifs_primaryIsCallSecondaryIsNotif() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + val firstIcon = mock() + setNotifs( + listOf( + activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon), + activeNotificationModel( + key = "secondNotif", + statusBarChipIcon = mock(), + ), + ) + ) + + assertIsCallChip(latest!!.primary) + assertIsNotifChip(latest!!.secondary, firstIcon) + } + + @Test + fun chips_screenRecordAndCallAndNotifs_notifsNotShown() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + screenRecordState.value = ScreenRecordModel.Recording + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock(), + ) + ) + ) + + assertIsScreenRecordChip(latest!!.primary) + assertIsCallChip(latest!!.secondary) + } + @Test fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() = testScope.runTest { @@ -531,4 +629,12 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { assertThat((latest as OngoingActivityChipModel.Shown).icon) .isInstanceOf(OngoingActivityChipModel.ChipIcon.FullColorAppIcon::class.java) } + + private fun setNotifs(notifs: List) { + activeNotificationListRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { notifs.forEach { addIndividualNotif(it) } } + .build() + testScope.runCurrent() + } } diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml index 690a89a044b7..d0a1ce8ae629 100644 --- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml @@ -45,31 +45,26 @@ android:tint="?android:attr/colorPrimary" /> - + + + - + + + + diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index e1f25f99cc98..e77892af59cf 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -70,6 +70,20 @@ 700 + + - \ No newline at end of file + + + + diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml index 9ecc297c6d36..403931764d7e 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml @@ -21,4 +21,15 @@ @color/settingslib_primary_device_default_settings_light @color/settingslib_accent_device_default_light - \ No newline at end of file + + + + diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes_bridge.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes_bridge.xml new file mode 100644 index 000000000000..bcb9baf94706 --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes_bridge.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- GitLab From 2e169a2b8253c7a01d745916e7b108ea55f06dd9 Mon Sep 17 00:00:00 2001 From: Ebru Kurnaz Date: Mon, 14 Oct 2024 14:31:30 +0000 Subject: [PATCH 154/441] Create ACONFIG flag for Notes role QS tile Test: Build Bug: 357863750 Flag: com.android.systemui.notes_role_qs_tile Change-Id: I3f8f5397ca898e5a04b5e01194bdd857793994f3 --- packages/SystemUI/aconfig/systemui.aconfig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a21a80506279..189294484085 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1481,3 +1481,9 @@ flag { bug: "370863642" } +flag { + name: "notes_role_qs_tile" + namespace: "systemui" + description: "Enables notes role qs tile which opens default notes role app in app bubbles" + bug: "357863750" +} -- GitLab From ddc40e0be0ff2d5165c8b0f1a340e2c57c188f99 Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Fri, 11 Oct 2024 21:06:48 +0000 Subject: [PATCH 155/441] Remove pieces of device entry flag part #3 f note, a lot of tests from UdfpsControllerTest were not running with the flag enabled. Opened a bug to perhaps restore some of these, as the code is maybe active. See b/372952178 Bug: 279440316 Test: atest SystemUITests Flag: com.android.systemui.device_entry_udfps_refactor Change-Id: I702bfc48bc765320dd66e4dc9c7a6c1cc6a6a7a2 --- .../biometrics/UdfpsBpViewControllerTest.kt | 70 -- .../biometrics/UdfpsControllerTest.java | 1067 ----------------- ...sKeyguardViewLegacyControllerBaseTest.java | 161 --- ...UdfpsKeyguardViewLegacyControllerTest.java | 197 --- ...dViewLegacyControllerWithCoroutinesTest.kt | 643 ---------- .../AlternateBouncerInteractorTest.kt | 70 -- .../binder/AlternateBouncerViewBinderTest.kt | 7 +- ...tificationShadeWindowViewControllerTest.kt | 104 +- .../shade/NotificationShadeWindowViewTest.kt | 39 +- .../StatusBarKeyguardViewManagerTest.java | 259 ---- .../SystemUI/res/layout/udfps_bp_view.xml | 22 - .../res/layout/udfps_fpm_empty_view.xml | 30 - .../systemui/biometrics/UdfpsBpView.kt | 35 - .../biometrics/UdfpsBpViewController.kt | 47 - .../systemui/biometrics/UdfpsController.java | 90 +- .../biometrics/UdfpsControllerOverlay.kt | 218 +--- .../systemui/biometrics/UdfpsFpmEmptyView.kt | 49 - .../biometrics/UdfpsFpmEmptyViewController.kt | 45 - .../UdfpsKeyguardViewControllerLegacy.kt | 555 --------- .../biometrics/UdfpsKeyguardViewLegacy.java | 330 ----- .../ui/binder/UdfpsTouchOverlayBinder.kt | 6 +- ...NotificationShadeWindowViewController.java | 36 +- .../LockscreenShadeTransitionController.kt | 29 +- .../phone/StatusBarKeyguardViewManager.java | 134 +-- .../biometrics/UdfpsControllerOverlayTest.kt | 289 +---- 25 files changed, 101 insertions(+), 4431 deletions(-) delete mode 100644 packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt delete mode 100644 packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java delete mode 100644 packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java delete mode 100644 packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt delete mode 100644 packages/SystemUI/res/layout/udfps_bp_view.xml delete mode 100644 packages/SystemUI/res/layout/udfps_fpm_empty_view.xml delete mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt deleted file mode 100644 index 13306becf6d2..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt +++ /dev/null @@ -1,70 +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.biometrics - -import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor -import com.android.systemui.dump.DumpManager -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.phone.SystemUIDialogManager -import org.junit.Assert.assertFalse -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.junit.MockitoJUnit - -@SmallTest -@RunWith(AndroidJUnit4::class) -@TestableLooper.RunWithLooper -class UdfpsBpViewControllerTest : SysuiTestCase() { - - @JvmField @Rule var rule = MockitoJUnit.rule() - - @Mock lateinit var udfpsBpView: UdfpsBpView - @Mock lateinit var statusBarStateController: StatusBarStateController - @Mock lateinit var shadeInteractor: ShadeInteractor - @Mock lateinit var systemUIDialogManager: SystemUIDialogManager - @Mock lateinit var dumpManager: DumpManager - @Mock lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor - - private lateinit var udfpsBpViewController: UdfpsBpViewController - - @Before - fun setup() { - udfpsBpViewController = - UdfpsBpViewController( - udfpsBpView, - statusBarStateController, - shadeInteractor, - systemUIDialogManager, - dumpManager, - udfpsOverlayInteractor, - ) - } - - @TestableLooper.RunWithLooper(setAsMainLooper = true) - @Test - fun testShouldNeverPauseAuth() { - assertFalse(udfpsBpViewController.shouldPauseAuth()) - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 437a4b357372..21c6583d4e84 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -16,34 +16,17 @@ package com.android.systemui.biometrics; -import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; -import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; -import static android.view.MotionEvent.ACTION_DOWN; -import static android.view.MotionEvent.ACTION_MOVE; -import static android.view.MotionEvent.ACTION_UP; - -import static com.android.internal.util.FunctionalUtils.ThrowingConsumer; -import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.graphics.Rect; -import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.SensorProperties; @@ -58,13 +41,9 @@ import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.testing.TestableLooper.RunWithLooper; -import android.util.Pair; -import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.Surface; import android.view.View; -import android.view.ViewGroup; import android.view.ViewRootImpl; import android.view.accessibility.AccessibilityManager; @@ -75,15 +54,11 @@ import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; -import com.android.systemui.biometrics.udfps.InteractionEvent; -import com.android.systemui.biometrics.udfps.NormalizedTouchData; import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor; -import com.android.systemui.biometrics.udfps.TouchProcessorResult; import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel; import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; @@ -102,7 +77,6 @@ import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.WakeSleepReason; import com.android.systemui.power.shared.model.WakefulnessState; -import com.android.systemui.res.R; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.VibratorHelper; @@ -130,7 +104,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; -import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -204,16 +177,6 @@ public class UdfpsControllerTest extends SysuiTestCase { private FeatureFlags mFeatureFlags; // Stuff for configuring mocks @Mock - private UdfpsView mUdfpsView; - @Mock - private UdfpsBpView mBpView; - @Mock - private UdfpsFpmEmptyView mFpmEmptyView; - @Mock - private UdfpsKeyguardViewLegacy mKeyguardView; - private final UdfpsAnimationViewController mUdfpsKeyguardViewController = - mock(UdfpsKeyguardViewControllerLegacy.class); - @Mock private SystemUIDialogManager mSystemUIDialogManager; @Mock private ActivityTransitionAnimator mActivityTransitionAnimator; @@ -241,8 +204,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor mViewCaptor; @Captor - private ArgumentCaptor mTouchListenerCaptor; - @Captor private ArgumentCaptor mHoverListenerCaptor; @Captor private ArgumentCaptor mOnDisplayConfiguredCaptor; @@ -287,18 +248,9 @@ public class UdfpsControllerTest extends SysuiTestCase { mContext.getOrCreateTestableResources() .addOverride(com.android.internal.R.bool.config_ignoreUdfpsVote, false); - when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)) - .thenReturn(mUdfpsView); - when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view_legacy, null)) - .thenReturn(mKeyguardView); // for showOverlay REASON_AUTH_FPM_KEYGUARD - when(mLayoutInflater.inflate(R.layout.udfps_bp_view, null)) - .thenReturn(mBpView); - when(mLayoutInflater.inflate(R.layout.udfps_fpm_empty_view, null)) - .thenReturn(mFpmEmptyView); when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); when(mSessionTracker.getSessionId(anyInt())).thenReturn( (new InstanceIdSequence(1 << 20)).newInstanceId()); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); final List componentInfo = new ArrayList<>(); componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, @@ -327,7 +279,6 @@ public class UdfpsControllerTest extends SysuiTestCase { // Create a fake background executor. mBiometricExecutor = new FakeExecutor(new FakeSystemClock()); - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); initUdfpsController(mOpticalProps); } @@ -349,7 +300,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mFalsingManager, mPowerManager, mAccessibilityManager, - mLockscreenShadeTransitionController, mScreenLifecycle, mVibrator, mUdfpsHapticsSimulator, @@ -389,101 +339,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode); } - @Test - public void dozeTimeTick() throws RemoteException { - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - mUdfpsController.dozeTimeTick(); - verify(mUdfpsView).dozeTimeTick(); - } - - @Test - public void onActionDownTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException { - // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = obtainMotionEvent(ACTION_DOWN, 0, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN notify keyguard authenticate to dismiss the keyguard - verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); - } - - @Test - public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException { - onActionMoveTouch_whenCanDismissLockScreen_entersDevice(false /* stale */); - } - - @Test - public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice_ignoreStale() - throws RemoteException { - onActionMoveTouch_whenCanDismissLockScreen_entersDevice(true /* stale */); - } - - public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice(boolean stale) - throws RemoteException { - // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - // WHEN ACTION_MOVE is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - if (stale) { - mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId); - mFgExecutor.runAllReady(); - } - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // THEN notify keyguard authenticate to dismiss the keyguard - verify(mStatusBarKeyguardViewManager, stale ? never() : times(1)) - .notifyKeyguardAuthenticated(anyBoolean()); - } - - @Test - public void onMultipleTouch_whenCanDismissLockScreen_entersDeviceOnce() throws RemoteException { - // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false); - - // GIVEN that the overlay is showing - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.second); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // THEN notify keyguard authenticate to dismiss the keyguard - verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); - } - @Test public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException { // GIVEN overlay was showing and the udfps bouncer is showing @@ -523,61 +378,6 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mDisplayManager).unregisterDisplayListener(any()); } - @Test - public void updateOverlayParams_recreatesOverlay_ifParamsChanged() throws Exception { - final Rect[] sensorBounds = new Rect[]{new Rect(10, 10, 20, 20), new Rect(5, 5, 25, 25)}; - final int[] displayWidth = new int[]{1080, 1440}; - final int[] displayHeight = new int[]{1920, 2560}; - final float[] scaleFactor = new float[]{1f, displayHeight[1] / (float) displayHeight[0]}; - final int[] rotation = new int[]{Surface.ROTATION_0, Surface.ROTATION_90}; - final UdfpsOverlayParams oldParams = new UdfpsOverlayParams(sensorBounds[0], - sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0], - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); - - for (int i1 = 0; i1 <= 1; ++i1) { - for (int i2 = 0; i2 <= 1; ++i2) { - for (int i3 = 0; i3 <= 1; ++i3) { - for (int i4 = 0; i4 <= 1; ++i4) { - for (int i5 = 0; i5 <= 1; ++i5) { - final UdfpsOverlayParams newParams = new UdfpsOverlayParams( - sensorBounds[i1], sensorBounds[i1], displayWidth[i2], - displayHeight[i3], scaleFactor[i4], rotation[i5], - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); - - if (newParams.equals(oldParams)) { - continue; - } - - // Initialize the overlay with old parameters. - mUdfpsController.updateOverlayParams(mOpticalProps, oldParams); - - // Show the overlay. - reset(mWindowManager); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, - mOpticalProps.sensorId, - BiometricRequestConstants.REASON_ENROLL_ENROLLING, - mUdfpsOverlayControllerCallback); - - mFgExecutor.runAllReady(); - verify(mWindowManager).addView(mViewCaptor.capture(), any()); - when(mViewCaptor.getValue().getParent()) - .thenReturn(mock(ViewGroup.class)); - - // Update overlay parameters. - reset(mWindowManager); - mUdfpsController.updateOverlayParams(mOpticalProps, newParams); - mFgExecutor.runAllReady(); - - // Ensure the overlay was recreated. - verify(mWindowManager).removeView(any()); - verify(mWindowManager).addView(any(), any()); - } - } - } - } - } - } - @Test public void updateOverlayParams_doesNothing_ifParamsDidntChange() throws Exception { final Rect sensorBounds = new Rect(10, 10, 20, 20); @@ -595,7 +395,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); - verify(mWindowManager).addView(any(), any()); // Update overlay with the same parameters. mUdfpsController.updateOverlayParams(mOpticalProps, @@ -606,870 +405,4 @@ public class UdfpsControllerTest extends SysuiTestCase { // Ensure the overlay was not recreated. verify(mWindowManager, never()).removeView(any()); } - - private static MotionEvent obtainMotionEvent(int action, float x, float y, float minor, - float major) { - MotionEvent.PointerProperties pp = new MotionEvent.PointerProperties(); - pp.id = 1; - MotionEvent.PointerCoords pc = new MotionEvent.PointerCoords(); - pc.x = x; - pc.y = y; - pc.touchMinor = minor; - pc.touchMajor = major; - return MotionEvent.obtain(0, 0, action, 1, new MotionEvent.PointerProperties[]{pp}, - new MotionEvent.PointerCoords[]{pc}, 0, 0, 1f, 1f, 0, 0, 0, 0); - } - - private static class TestParams { - public final FingerprintSensorPropertiesInternal sensorProps; - - TestParams(FingerprintSensorPropertiesInternal sensorProps) { - this.sensorProps = sensorProps; - } - } - - private void runWithAllParams(ThrowingConsumer testParamsConsumer) { - for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps, - mUltrasonicProps)) { - initUdfpsController(sensorProps); - testParamsConsumer.accept(new TestParams(sensorProps)); - } - } - - @Test - public void onTouch_propagatesTouchInNativeOrientationAndResolution() { - runWithAllParams( - this::onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized); - } - - private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized( - TestParams testParams) throws RemoteException { - reset(mUdfpsView); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); - - final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner. - final int pointerId = 0; - final int displayWidth = 1080; - final int displayHeight = 1920; - final float scaleFactor = 1f; // This means the native resolution is 1440x2560. - final float touchMinor = 10f; - final float touchMajor = 20f; - final float orientation = 30f; - - // Expecting a touch at the very bottom right corner in native orientation and resolution. - final float expectedX = displayWidth / scaleFactor; - final float expectedY = displayHeight / scaleFactor; - final float expectedMinor = touchMinor / scaleFactor; - final float expectedMajor = touchMajor / scaleFactor; - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - - // GIVEN a valid touch on sensor - NormalizedTouchData touchData = new NormalizedTouchData(pointerId, displayWidth, - displayHeight, touchMinor, touchMajor, orientation, 0L, 0L); - TouchProcessorResult processorDownResult = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.DOWN, 1, touchData); - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorDownResult); - - // Show the overlay. - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // Test ROTATION_0 - mUdfpsController.updateOverlayParams(testParams.sensorProps, - new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, - scaleFactor, Surface.ROTATION_0, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)); - MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, - touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), - anyBoolean()); - - // Test ROTATION_90 - reset(mFingerprintManager); - mUdfpsController.updateOverlayParams(testParams.sensorProps, - new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, - scaleFactor, Surface.ROTATION_90, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)); - event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), - anyBoolean()); - - // Test ROTATION_270 - reset(mFingerprintManager); - mUdfpsController.updateOverlayParams(testParams.sensorProps, - new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, - scaleFactor, Surface.ROTATION_270, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)); - event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), - anyBoolean()); - - // Test ROTATION_180 - reset(mFingerprintManager); - mUdfpsController.updateOverlayParams(testParams.sensorProps, - new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, - scaleFactor, Surface.ROTATION_180, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)); - // ROTATION_180 is not supported. It should be treated like ROTATION_0. - event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), - anyBoolean()); - } - - @Test - public void fingerDown() { - runWithAllParams(this::fingerDownParameterized); - } - - private void fingerDownParameterized(TestParams testParams) throws RemoteException { - reset(mUdfpsView, mFingerprintManager, mLatencyTracker, - mKeyguardUpdateMonitor); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.DOWN, 1 /* pointerId */, touchData); - - initUdfpsController(testParams.sensorProps); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - // WHEN ACTION_DOWN is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN the touch provider is notified about onPointerDown. - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - - // AND display configuration begins - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture()); - } else { - verify(mLatencyTracker, never()).onActionStart( - eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mUdfpsView, never()).configureDisplay(any()); - } - verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // AND onDisplayConfigured notifies FingerprintManager about onUiReady - mOnDisplayConfiguredCaptor.getValue().run(); - mBiometricExecutor.runAllReady(); - InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker); - inOrder.verify(mFingerprintManager).onUdfpsUiEvent( - eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId)); - inOrder.verify(mLatencyTracker).onActionEnd( - eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - } else { - verify(mFingerprintManager, never()).onUdfpsUiEvent( - eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt()); - verify(mLatencyTracker, never()).onActionEnd( - eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - } - } - - @Test - public void aodInterrupt() { - runWithAllParams(this::aodInterruptParameterized); - } - - private void aodInterruptParameterized(TestParams testParams) throws RemoteException { - mUdfpsController.cancelAodSendFingerUpAction(); - reset(mUdfpsView, mFingerprintManager, mKeyguardUpdateMonitor); - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); - - // GIVEN that the overlay is showing and screen is on and fp is running - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - // WHEN fingerprint is requested because of AOD interrupt - mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); - mFgExecutor.runAllReady(); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // THEN display configuration begins - // AND onDisplayConfigured notifies FingerprintManager about onUiReady - verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture()); - mOnDisplayConfiguredCaptor.getValue().run(); - } else { - verify(mUdfpsView, never()).configureDisplay(mOnDisplayConfiguredCaptor.capture()); - } - mBiometricExecutor.runAllReady(); - - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - } - - @Test - public void tryAodSendFingerUp_displayConfigurationChanges() { - runWithAllParams(this::cancelAodInterruptParameterized); - } - - private void cancelAodInterruptParameterized(TestParams testParams) throws RemoteException { - reset(mUdfpsView); - - // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - // WHEN it is cancelled - mUdfpsController.tryAodSendFingerUp(); - // THEN the display is unconfigured - verify(mUdfpsView).unconfigureDisplay(); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - // WHEN it is cancelled - mUdfpsController.tryAodSendFingerUp(); - // THEN the display configuration is unchanged. - verify(mUdfpsView, never()).unconfigureDisplay(); - } - } - - @Test - public void onFingerUp_displayConfigurationChange() { - runWithAllParams(this::onFingerUp_displayConfigurationParameterized); - } - - private void onFingerUp_displayConfigurationParameterized(TestParams testParams) - throws RemoteException { - reset(mUdfpsView); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); - - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - // GIVEN AOD interrupt - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - - // WHEN up-action received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.second); - MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - mFgExecutor.runAllReady(); - - // THEN the display is unconfigured - verify(mUdfpsView).unconfigureDisplay(); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - - // WHEN up-action received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.second); - MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - mFgExecutor.runAllReady(); - - // THEN the display configuration is unchanged. - verify(mUdfpsView, never()).unconfigureDisplay(); - } - } - - @Test - public void onAcquiredGood_displayConfigurationChange() { - runWithAllParams(this::onAcquiredGood_displayConfigurationParameterized); - } - - private void onAcquiredGood_displayConfigurationParameterized(TestParams testParams) - throws RemoteException { - reset(mUdfpsView); - - // GIVEN overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - // WHEN ACQUIRED_GOOD received - mOverlayController.onAcquired(testParams.sensorProps.sensorId, - BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD); - mFgExecutor.runAllReady(); - // THEN the display is unconfigured - verify(mUdfpsView).unconfigureDisplay(); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - // WHEN ACQUIRED_GOOD received - mOverlayController.onAcquired(testParams.sensorProps.sensorId, - BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD); - mFgExecutor.runAllReady(); - // THEN the display configuration is unchanged. - verify(mUdfpsView, never()).unconfigureDisplay(); - } - } - - @Test - public void aodInterruptTimeout() { - runWithAllParams(this::aodInterruptTimeoutParameterized); - } - - private void aodInterruptTimeoutParameterized(TestParams testParams) throws RemoteException { - reset(mUdfpsView); - - // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - mFgExecutor.runAllReady(); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - } - // WHEN it times out - mFgExecutor.advanceClockToNext(); - mFgExecutor.runAllReady(); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // THEN the display is unconfigured. - verify(mUdfpsView).unconfigureDisplay(); - } else { - // THEN the display configuration is unchanged. - verify(mUdfpsView, never()).unconfigureDisplay(); - } - } - - @Test - public void aodInterruptCancelTimeoutActionOnFingerUp() { - runWithAllParams(this::aodInterruptCancelTimeoutActionOnFingerUpParameterized); - } - - private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams) - throws RemoteException { - reset(mUdfpsView); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); - - // GIVEN AOD interrupt - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - mFgExecutor.runAllReady(); - - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // Configure UdfpsView to accept the ACTION_UP event - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - } - - // WHEN ACTION_UP is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.second); - MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - mFgExecutor.runAllReady(); - - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // Configure UdfpsView to accept the finger up event - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - } - - // WHEN it times out - mFgExecutor.advanceClockToNext(); - mFgExecutor.runAllReady(); - - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // 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).unconfigureDisplay(); - } else { - verify(mUdfpsView, never()).unconfigureDisplay(); - } - } - - @Test - public void aodInterruptScreenOff() { - runWithAllParams(this::aodInterruptScreenOffParameterized); - } - - private void aodInterruptScreenOffParameterized(TestParams testParams) throws RemoteException { - reset(mUdfpsView); - - // GIVEN screen off - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOff(); - mFgExecutor.runAllReady(); - - // WHEN aod interrupt is received - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - - // THEN display doesn't get configured because it's off - verify(mUdfpsView, never()).configureDisplay(any()); - } - - @Test - public void aodInterrupt_fingerprintNotRunning() { - runWithAllParams(this::aodInterrupt_fingerprintNotRunningParameterized); - } - - private void aodInterrupt_fingerprintNotRunningParameterized(TestParams testParams) - throws RemoteException { - reset(mUdfpsView); - - // GIVEN showing overlay - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - - // WHEN aod interrupt is received when the fingerprint service isn't running - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - - // THEN display doesn't get configured because it's off - verify(mUdfpsView, never()).configureDisplay(any()); - } - - @Test - public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled() throws RemoteException { - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, true); - - // WHEN ACTION_HOVER is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); - MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0); - mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent); - enterEvent.recycle(); - - // THEN context click haptic is played - verify(mVibrator).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.CONTEXT_CLICK) - ); - } - - @Test - public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException { - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN NO haptic played - verify(mVibrator, never()).performHapticFeedback(any(), anyInt()); - } - - @Test - public void fingerDown_falsingManagerInformed() throws RemoteException { - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN falsing manager is informed of the touch - verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION); - } - - private Pair givenFingerEvent( - InteractionEvent event1, InteractionEvent event2, boolean a11y) - throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( - event1, 1 /* pointerId */, touchData); - final TouchProcessorResult processorResultUp = new TouchProcessorResult.ProcessedTouch( - event2, 1 /* pointerId */, touchData); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(a11y); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - if (a11y) { - verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); - } else { - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - } - - return new Pair<>(processorResultDown, processorResultUp); - } - - @Test - public void onTouch_forwardToKeyguard() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.UNCHANGED, -1 /* pointerOnSensorId */, touchData); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - - // THEN the touch is forwarded to Keyguard - verify(mStatusBarKeyguardViewManager).onTouch(downEvent); - } - - @Test - public void onTouch_pilferPointer() throws RemoteException { - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - - // WHEN ACTION_MOVE is received after - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.second); - event.setAction(ACTION_MOVE); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - - // THEN only pilfer once on the initial down - verify(mInputManager).pilferPointers(any()); - } - - @Test - public void onTouch_doNotPilferPointer() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultUnchanged = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED, - -1 /* pointerId */, touchData); - - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // WHEN ACTION_DOWN is received and touch is not within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultUnchanged); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN the touch is NOT pilfered - verify(mInputManager, never()).pilferPointers(any()); - } - - @Test - public void onDownTouchReceivedWithoutPreviousUp() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - -1 /* pointerId */, touchData); - - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // WHEN ACTION_DOWN is received and touch is within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent firstDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, firstDownEvent); - mBiometricExecutor.runAllReady(); - firstDownEvent.recycle(); - - // And another ACTION_DOWN is received without an ACTION_UP before - MotionEvent secondDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, secondDownEvent); - mBiometricExecutor.runAllReady(); - secondDownEvent.recycle(); - - // THEN the touch is still processed - verify(mFingerprintManager, times(2)).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), - anyBoolean()); - } - - @Test - public void onAodDownAndDownTouchReceived() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - -1 /* pointerId */, touchData); - - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // WHEN fingerprint is requested because of AOD interrupt - // GIVEN there's been an AoD interrupt - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - mScreenObserver.onScreenTurnedOn(); - mUdfpsController.onAodInterrupt(0, 0, 0, 0); - mFgExecutor.runAllReady(); - - // and an ACTION_DOWN is received and touch is within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent firstDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, firstDownEvent); - mBiometricExecutor.runAllReady(); - firstDownEvent.recycle(); - - // THEN the touch is only processed once - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), - anyBoolean()); - } - - @Test - public void onTouch_pilferPointerWhenAltBouncerShowing() - throws RemoteException { - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false); - - // WHEN alternate bouncer is showing - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // WHEN ACTION_DOWN is received and touch is not within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN the touch is pilfered - verify(mInputManager).pilferPointers(any()); - } - - @Test - public void onTouch_doNotProcessTouchWhenPullingUpBouncer() - throws RemoteException { - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false); - - // 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( - touchProcessorResult.first); - 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 processed - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), - anyBoolean()); - } - - - @Test - public void onTouch_qsDrag_processesTouchWhenAlternateBouncerVisible() - throws RemoteException { - final Pair touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - // GIVEN swipe down for QS - when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false); - when(mLockscreenShadeTransitionController.getQSDragProgress()).thenReturn(1f); - - // WHEN ACTION_MOVE is received and touch is within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // THEN the touch is still processed - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), - anyBoolean()); - } - - @Test - public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException { - // GIVEN UDFPS overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - // GIVEN there's been an AoD interrupt - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - mScreenObserver.onScreenTurnedOn(); - mUdfpsController.onAodInterrupt(0, 0, 0, 0); - - // THEN finger is considered down - assertTrue(mUdfpsController.isFingerDown()); - - // WHEN udfps receives an ACQUIRED_GOOD after the display is configured - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - verify(mFingerprintManager).setUdfpsOverlayController( - mUdfpsOverlayControllerCaptor.capture()); - mUdfpsOverlayControllerCaptor.getValue().onAcquired(0, FINGERPRINT_ACQUIRED_GOOD); - mFgExecutor.runAllReady(); - - // THEN is fingerDown should be FALSE - assertFalse(mUdfpsController.isFingerDown()); - } - - @Test - public void playHaptic_onAodInterrupt_onAcquiredBad_performHapticFeedback() - throws RemoteException { - // GIVEN UDFPS overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - // GIVEN there's been an AoD interrupt - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); - mScreenObserver.onScreenTurnedOn(); - mUdfpsController.onAodInterrupt(0, 0, 0, 0); - - // THEN vibrate is used - verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); - } - - @Test - public void onAcquiredCalbacks() { - runWithAllParams( - this::ultrasonicCallbackOnAcquired); - } - - public void ultrasonicCallbackOnAcquired(TestParams testParams) throws RemoteException{ - if (testParams.sensorProps.sensorType - == FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC) { - reset(mUdfpsView); - - UdfpsController.Callback callbackMock = mock(UdfpsController.Callback.class); - mUdfpsController.addCallback(callbackMock); - - // GIVEN UDFPS overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, - mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mFingerprintManager).setUdfpsOverlayController( - mUdfpsOverlayControllerCaptor.capture()); - mUdfpsOverlayControllerCaptor.getValue().onAcquired(0, FINGERPRINT_ACQUIRED_START); - mFgExecutor.runAllReady(); - - verify(callbackMock).onFingerDown(); - - mUdfpsOverlayControllerCaptor.getValue().onAcquired(0, FINGERPRINT_ACQUIRED_GOOD); - mFgExecutor.runAllReady(); - - verify(callbackMock).onFingerUp(); - } - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java deleted file mode 100644 index 7986051de3e0..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java +++ /dev/null @@ -1,161 +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.systemui.biometrics; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; - -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.Flags; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.ActivityTransitionAnimator; -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shade.ShadeExpansionChangeEvent; -import com.android.systemui.shade.ShadeExpansionStateManager; -import com.android.systemui.shade.domain.interactor.ShadeInteractor; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; -import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.user.domain.interactor.SelectedUserInteractor; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import org.junit.Before; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { - // Dependencies - protected @Mock UdfpsKeyguardViewLegacy mView; - protected @Mock Context mResourceContext; - protected @Mock StatusBarStateController mStatusBarStateController; - protected @Mock ShadeExpansionStateManager mShadeExpansionStateManager; - protected @Mock StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - protected @Mock LockscreenShadeTransitionController mLockscreenShadeTransitionController; - protected @Mock DumpManager mDumpManager; - protected @Mock DelayableExecutor mExecutor; - protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; - protected @Mock KeyguardStateController mKeyguardStateController; - protected @Mock KeyguardViewMediator mKeyguardViewMediator; - protected @Mock ConfigurationController mConfigurationController; - protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; - protected @Mock SystemUIDialogManager mDialogManager; - protected @Mock UdfpsController mUdfpsController; - protected @Mock ActivityTransitionAnimator mActivityTransitionAnimator; - protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor; - protected @Mock ShadeInteractor mShadeInteractor; - protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor; - protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; - protected @Mock SelectedUserInteractor mSelectedUserInteractor; - protected @Mock KeyguardTransitionInteractor mKeyguardTransitionInteractor; - protected @Mock UdfpsOverlayInteractor mUdfpsOverlayInteractor; - - protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); - - protected UdfpsKeyguardViewControllerLegacy mController; - - // Capture listeners so that they can be used to send events - private @Captor ArgumentCaptor mStateListenerCaptor; - protected StatusBarStateController.StateListener mStatusBarStateListener; - - private @Captor ArgumentCaptor - mKeyguardStateControllerCallbackCaptor; - protected KeyguardStateController.Callback mKeyguardStateControllerCallback; - - private @Captor ArgumentCaptor - mKeyguardViewManagerCallbackArgumentCaptor; - protected StatusBarKeyguardViewManager.KeyguardViewManagerCallback mKeyguardViewManagerCallback; - - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); - when(mView.getContext()).thenReturn(mResourceContext); - when(mResourceContext.getString(anyInt())).thenReturn("test string"); - when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false); - when(mView.getUnpausedAlpha()).thenReturn(255); - when(mShadeExpansionStateManager.addExpansionListener(any())).thenReturn( - new ShadeExpansionChangeEvent(0, false, false)); - mController = createUdfpsKeyguardViewController(); - } - - protected void sendStatusBarStateChanged(int statusBarState) { - mStatusBarStateListener.onStateChanged(statusBarState); - } - - protected void captureStatusBarStateListeners() { - verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture()); - mStatusBarStateListener = mStateListenerCaptor.getValue(); - } - - protected void captureKeyguardStateControllerCallback() { - verify(mKeyguardStateController).addCallback( - mKeyguardStateControllerCallbackCaptor.capture()); - mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue(); - } - - public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() { - return createUdfpsKeyguardViewController(false); - } - - public void captureKeyGuardViewManagerCallback() { - verify(mStatusBarKeyguardViewManager).addCallback( - mKeyguardViewManagerCallbackArgumentCaptor.capture()); - mKeyguardViewManagerCallback = mKeyguardViewManagerCallbackArgumentCaptor.getValue(); - } - - protected UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController( - boolean useModernBouncer) { - UdfpsKeyguardViewControllerLegacy controller = new UdfpsKeyguardViewControllerLegacy( - mView, - mStatusBarStateController, - mStatusBarKeyguardViewManager, - mKeyguardUpdateMonitor, - mDumpManager, - mLockscreenShadeTransitionController, - mConfigurationController, - mKeyguardStateController, - mUnlockedScreenOffAnimationController, - mDialogManager, - mUdfpsController, - mActivityTransitionAnimator, - mPrimaryBouncerInteractor, - mAlternateBouncerInteractor, - mUdfpsKeyguardAccessibilityDelegate, - mSelectedUserInteractor, - mKeyguardTransitionInteractor, - mShadeInteractor, - mUdfpsOverlayInteractor); - return controller; - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java deleted file mode 100644 index 98d8b054716c..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java +++ /dev/null @@ -1,197 +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.systemui.biometrics; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.TestableLooper; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.statusbar.StatusBarState; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) - -@TestableLooper.RunWithLooper(setAsMainLooper = true) -public class UdfpsKeyguardViewLegacyControllerTest extends - UdfpsKeyguardViewLegacyControllerBaseTest { - @Override - public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() { - return createUdfpsKeyguardViewController(/* useModernBouncer */ false); - } - - @Test - public void testShouldPauseAuth_bouncerShowing() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true); - when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true); - when(mView.getUnpausedAlpha()).thenReturn(0); - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testRegistersStatusBarStateListenersOnAttached() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - } - - @Test - public void testViewControllerQueriesSBStateOnAttached() { - mController.onViewAttached(); - verify(mStatusBarStateController).getState(); - - when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); - captureStatusBarStateListeners(); - - mController.onViewAttached(); - verify(mView, atLeast(1)).setPauseAuth(true); - } - - @Test - public void testListenersUnregisteredOnDetached() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - captureKeyguardStateControllerCallback(); - mController.onViewDetached(); - - verify(mStatusBarStateController).removeCallback(mStatusBarStateListener); - verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback); - } - - @Test - public void testShouldPauseAuthUnpausedAlpha0() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - when(mView.getUnpausedAlpha()).thenReturn(0); - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testShouldNotPauseAuthOnKeyguard() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - assertFalse(mController.shouldPauseAuth()); - } - - @Test - public void onBiometricAuthenticated_pauseAuth() { - // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard) - mController.onViewAttached(); - captureStatusBarStateListeners(); - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - // WHEN biometric is authenticated - captureKeyguardStateControllerCallback(); - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); - mKeyguardStateControllerCallback.onUnlockedChanged(); - - // THEN pause auth - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testShouldPauseAuthIsLaunchTransitionFadingAway() { - // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard) - mController.onViewAttached(); - captureStatusBarStateListeners(); - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - // WHEN isLaunchTransitionFadingAway=true - captureKeyguardStateControllerCallback(); - when(mKeyguardStateController.isLaunchTransitionFadingAway()).thenReturn(true); - mKeyguardStateControllerCallback.onLaunchTransitionFadingAwayChanged(); - - // THEN pause auth - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testShouldPauseAuthOnShadeLocked() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED); - - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testShouldPauseAuthOnShade() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - // WHEN not on keyguard yet (shade = home) - sendStatusBarStateChanged(StatusBarState.SHADE); - - // THEN pause auth - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testShouldPauseAuthAnimatingScreenOffFromShade() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - // WHEN transitioning from home/shade => keyguard + animating screen off - mStatusBarStateListener.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD); - when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true); - - // THEN pause auth - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testDoNotPauseAuthAnimatingScreenOffFromLS() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - // WHEN animating screen off transition from LS => AOD - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true); - - // THEN don't pause auth - assertFalse(mController.shouldPauseAuth()); - } - - @Test - public void testOverrideShouldPauseAuthOnShadeLocked() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED); - assertTrue(mController.shouldPauseAuth()); - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt deleted file mode 100644 index 29a6e56891af..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ /dev/null @@ -1,643 +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.systemui.biometrics - -import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF -import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN -import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor -import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository -import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor -import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.kosmos.testScope -import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.whenever -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@RunWith(AndroidJUnit4::class) -@SmallTest -@TestableLooper.RunWithLooper -@kotlinx.coroutines.ExperimentalCoroutinesApi -class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : - UdfpsKeyguardViewLegacyControllerBaseTest() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - - private val keyguardBouncerRepository = kosmos.fakeKeyguardBouncerRepository - private val transitionRepository = kosmos.fakeKeyguardTransitionRepository - - @Before - override fun setUp() { - allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread - MockitoAnnotations.initMocks(this) - super.setUp() - } - - override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewControllerLegacy { - mPrimaryBouncerInteractor = kosmos.primaryBouncerInteractor - mAlternateBouncerInteractor = kosmos.alternateBouncerInteractor - mKeyguardTransitionInteractor = kosmos.keyguardTransitionInteractor - mUdfpsOverlayInteractor = kosmos.udfpsOverlayInteractor - return createUdfpsKeyguardViewController(/* useModernBouncer */ true) - } - - @Test - fun bouncerExpansionChange_fadeIn() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - captureKeyguardStateControllerCallback() - Mockito.reset(mView) - - // WHEN status bar expansion is 0 - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - runCurrent() - - // THEN alpha is 0 - verify(mView).unpausedAlpha = 0 - - job.cancel() - } - - @Test - fun bouncerExpansionChange_pauseAuth() = - testScope.runTest { - // GIVEN view is attached + on the keyguard - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - Mockito.reset(mView) - - // WHEN panelViewExpansion changes to hide - whenever(mView.unpausedAlpha).thenReturn(0) - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - runCurrent() - - // THEN pause auth is updated to PAUSE - verify(mView, Mockito.atLeastOnce()).setPauseAuth(true) - - job.cancel() - } - - @Test - fun bouncerExpansionChange_unpauseAuth() = - testScope.runTest { - // GIVEN view is attached + on the keyguard + panel expansion is 0f - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - Mockito.reset(mView) - - // WHEN panelViewExpansion changes to expanded - whenever(mView.unpausedAlpha).thenReturn(255) - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN) - runCurrent() - - // THEN pause auth is updated to NOT pause - verify(mView, Mockito.atLeastOnce()).setPauseAuth(false) - - job.cancel() - } - - @Test - fun shadeLocked_showAlternateBouncer_unpauseAuth() = - testScope.runTest { - // GIVEN view is attached + on the SHADE_LOCKED (udfps view not showing) - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED) - - // WHEN alternate bouncer is requested - val job = mController.listenForAlternateBouncerVisibility(this) - keyguardBouncerRepository.setAlternateVisible(true) - runCurrent() - - // THEN udfps view will animate in & pause auth is updated to NOT pause - verify(mView).animateInUdfpsBouncer(any()) - assertFalse(mController.shouldPauseAuth()) - - job.cancel() - } - - /** After migration to MODERN_BOUNCER, replaces UdfpsKeyguardViewControllerTest version */ - @Test - fun shouldPauseAuthBouncerShowing() = - testScope.runTest { - // GIVEN view attached and we're on the keyguard - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - - // WHEN the bouncer expansion is VISIBLE - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - runCurrent() - - // THEN UDFPS shouldPauseAuth == true - assertTrue(mController.shouldPauseAuth()) - - job.cancel() - } - - @Test - fun shouldHandleTouchesChange() = - testScope.runTest { - val shouldHandleTouches by collectLastValue(mUdfpsOverlayInteractor.shouldHandleTouches) - - // GIVEN view is attached + on the keyguard - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - whenever(mView.setPauseAuth(true)).thenReturn(true) - whenever(mView.unpausedAlpha).thenReturn(0) - - // WHEN panelViewExpansion changes to expanded - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - runCurrent() - - // THEN UDFPS auth is paused and should not handle touches - assertThat(mController.shouldPauseAuth()).isTrue() - assertThat(shouldHandleTouches!!).isFalse() - - job.cancel() - } - - @Test - fun shouldHandleTouchesOnDetach() = - testScope.runTest { - val shouldHandleTouches by collectLastValue(mUdfpsOverlayInteractor.shouldHandleTouches) - - // GIVEN view is attached + on the keyguard - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - whenever(mView.setPauseAuth(true)).thenReturn(true) - whenever(mView.unpausedAlpha).thenReturn(0) - - // WHEN panelViewExpansion changes to expanded - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - runCurrent() - - mController.onViewDetached() - - // THEN UDFPS auth is paused and should not handle touches - assertThat(mController.shouldPauseAuth()).isTrue() - assertThat(shouldHandleTouches!!).isFalse() - - job.cancel() - } - - @Test - fun fadeFromDialogSuggestedAlpha() = - testScope.runTest { - // GIVEN view is attached and status bar expansion is 1f - mController.onViewAttached() - captureStatusBarStateListeners() - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN) - runCurrent() - Mockito.reset(mView) - - // WHEN dialog suggested alpha is .6f - whenever(mView.dialogSuggestedAlpha).thenReturn(.6f) - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - - // THEN alpha is updated based on dialog suggested alpha - verify(mView).unpausedAlpha = (.6f * 255).toInt() - - job.cancel() - } - - @Test - fun transitionToFullShadeProgress() = - testScope.runTest { - // GIVEN view is attached and status bar expansion is 1f - mController.onViewAttached() - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN) - runCurrent() - Mockito.reset(mView) - whenever(mView.dialogSuggestedAlpha).thenReturn(1f) - - // WHEN we're transitioning to the full shade - val transitionProgress = .6f - mController.setTransitionToFullShadeProgress(transitionProgress) - - // THEN alpha is between 0 and 255 - verify(mView).unpausedAlpha = ((1f - transitionProgress) * 255).toInt() - - job.cancel() - } - - @Test - fun aodToLockscreen_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForLockscreenAodTransitions(this) - - // WHEN transitioning from lockscreen to aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(.3f), - eq(.3f), - eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) - ) - - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(1f), - eq(1f), - eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) - ) - - job.cancel() - } - - @Test - fun lockscreenToAod_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForLockscreenAodTransitions(this) - - // WHEN transitioning from lockscreen to aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(.3f), - eq(.3f), - eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) - ) - - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(1f), - eq(1f), - eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) - ) - - job.cancel() - } - - @Test - fun goneToAod_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForGoneToAodTransition(this) - - // WHEN transitioning from lockscreen to aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(.3f), - eq(.3f), - eq(UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF) - ) - - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(1f), - eq(1f), - eq(UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF) - ) - - job.cancel() - } - - @Test - fun aodToOccluded_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForAodToOccludedTransitions(this) - - // WHEN transitioning from aod to occluded - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.OCCLUDED, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged(eq(.7f), eq(.7f), eq(UdfpsKeyguardViewLegacy.ANIMATION_NONE)) - - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.OCCLUDED, - value = 1f, - transitionState = TransitionState.FINISHED - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged(eq(0f), eq(0f), eq(UdfpsKeyguardViewLegacy.ANIMATION_NONE)) - - job.cancel() - } - - @Test - fun occludedToAod_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForOccludedToAodTransition(this) - - // WHEN transitioning from occluded to aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(.3f), - eq(.3f), - eq(UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF) - ) - - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(1f), - eq(1f), - eq(UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF) - ) - - job.cancel() - } - - @Test - fun cancelledAodToLockscreen_dozeAmountChangedToZero() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - val job = mController.listenForLockscreenAodTransitions(this) - runCurrent() - Mockito.reset(mView) - - // WHEN aod to lockscreen transition is cancelled - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - value = 1f, - transitionState = TransitionState.CANCELED - ) - ) - runCurrent() - // ... and WHEN the next transition is from lockscreen => occluded - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.OCCLUDED, - value = .4f, - transitionState = TransitionState.STARTED - ) - ) - runCurrent() - - // THEN doze amount is updated to zero - verify(mView) - .onDozeAmountChanged(eq(0f), eq(0f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)) - job.cancel() - } - - @Test - fun cancelledLockscreenToAod_dozeAmountNotUpdatedToZero() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - val job = mController.listenForLockscreenAodTransitions(this) - runCurrent() - Mockito.reset(mView) - - // WHEN lockscreen to aod transition is cancelled - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.CANCELED - ) - ) - runCurrent() - - // THEN doze amount is NOT updated to zero - verify(mView, never()).onDozeAmountChanged(eq(0f), eq(0f), anyInt()) - job.cancel() - } - - @Test - fun dreamingToAod_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForDreamingToAodTransitions(this) - // WHEN dreaming to aod transition in progress - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.DREAMING, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - - // THEN doze amount is updated to - verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF)) - job.cancel() - } - - @Test - fun alternateBouncerToAod_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForAlternateBouncerToAodTransitions(this) - // WHEN alternate bouncer to aod transition in progress - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - - // THEN doze amount is updated to - verify(mView) - .onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)) - job.cancel() - } - - @Test - fun bouncerToAod_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForPrimaryBouncerToAodTransitions(this) - // WHEN alternate bouncer to aod transition in progress - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - - // THEN doze amount is updated to - verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF)) - job.cancel() - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt index 68cfa28dabd7..82ff61795e98 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt @@ -16,12 +16,9 @@ package com.android.systemui.bouncer.domain.interactor -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.keyguardUpdateMonitor -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.authenticationInteractor @@ -61,12 +58,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { underTest = kosmos.alternateBouncerInteractor } - @Test(expected = IllegalStateException::class) - @EnableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - fun enableUdfpsRefactor_deprecatedShowMethod_throwsIllegalStateException() { - underTest.show() - } - @Test @DisableSceneContainer fun canShowAlternateBouncer_false_dueToTransitionState() = @@ -100,21 +91,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { assertThat(canShowAlternateBouncer).isFalse() } - @Test - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - fun canShowAlternateBouncerForFingerprint_givenCanShow() { - givenCanShowAlternateBouncer() - assertTrue(underTest.canShowAlternateBouncerForFingerprint()) - } - - @Test - fun canShowAlternateBouncerForFingerprint_alternateBouncerUIUnavailable() { - givenCanShowAlternateBouncer() - kosmos.keyguardBouncerRepository.setAlternateBouncerUIAvailable(false) - - assertFalse(underTest.canShowAlternateBouncerForFingerprint()) - } - @Test fun canShowAlternateBouncerForFingerprint_ifFingerprintIsNotUsuallyAllowed() { givenCanShowAlternateBouncer() @@ -139,15 +115,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { assertFalse(underTest.canShowAlternateBouncerForFingerprint()) } - @Test - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - fun show_whenCanShow() { - givenCanShowAlternateBouncer() - - assertTrue(underTest.show()) - assertTrue(kosmos.keyguardBouncerRepository.alternateBouncerVisible.value) - } - @Test fun canShowAlternateBouncerForFingerprint_butCanDismissLockScreen() { givenCanShowAlternateBouncer() @@ -164,15 +131,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { assertFalse(underTest.canShowAlternateBouncerForFingerprint()) } - @Test - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - fun show_whenCannotShow() { - givenCannotShowAlternateBouncer() - - assertFalse(underTest.show()) - assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerVisible.value) - } - @Test fun hide_wasPreviouslyShowing() { kosmos.keyguardBouncerRepository.setAlternateVisible(true) @@ -190,7 +148,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { } @Test - @EnableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) fun canShowAlternateBouncerForFingerprint_rearFps() { givenCanShowAlternateBouncer() kosmos.fingerprintPropertyRepository.supportsRearFps() // does not support alternate bouncer @@ -198,29 +155,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { assertFalse(underTest.canShowAlternateBouncerForFingerprint()) } - @Test - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - fun alternateBouncerUiAvailable_fromMultipleSources() { - assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value) - - // GIVEN there are two different sources indicating the alternate bouncer is available - underTest.setAlternateBouncerUIAvailable(true, "source1") - underTest.setAlternateBouncerUIAvailable(true, "source2") - assertTrue(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value) - - // WHEN one of the sources no longer says the UI is available - underTest.setAlternateBouncerUIAvailable(false, "source1") - - // THEN alternate bouncer UI is still available (from the other source) - assertTrue(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value) - - // WHEN all sources say the UI is not available - underTest.setAlternateBouncerUIAvailable(false, "source2") - - // THEN alternate boucer UI is not available - assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value) - } - private fun givenAlternateBouncerSupported() { kosmos.givenAlternateBouncerSupported() } @@ -228,8 +162,4 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { private fun givenCanShowAlternateBouncer() { kosmos.givenCanShowAlternateBouncer() } - - private fun givenCannotShowAlternateBouncer() { - kosmos.givenCannotShowAlternateBouncer() - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt index c4eabd84e031..380060865282 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.keyguard.ui.binder -import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper import android.view.View import android.view.layoutInflater @@ -24,7 +23,6 @@ import android.view.mockedLayoutInflater import android.view.windowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.givenCanShowAlternateBouncer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -63,7 +61,7 @@ class AlternateBouncerViewBinderTest : SysuiTestCase() { kosmos.mockedLayoutInflater.inflate( eq(R.layout.alternate_bouncer), isNull(), - anyBoolean() + anyBoolean(), ) ) .thenReturn(mockedAltBouncerView) @@ -71,7 +69,6 @@ class AlternateBouncerViewBinderTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) fun addViewToWindowManager() { testScope.runTest { kosmos.givenCanShowAlternateBouncer() @@ -85,7 +82,6 @@ class AlternateBouncerViewBinderTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) fun viewRemovedImmediatelyIfAlreadyAttachedToWindow() { testScope.runTest { kosmos.givenCanShowAlternateBouncer() @@ -107,7 +103,6 @@ class AlternateBouncerViewBinderTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) fun viewNotRemovedUntilAttachedToWindow() { testScope.runTest { kosmos.givenCanShowAlternateBouncer() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 6f2302a22d7b..9fe52991c3a0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -30,7 +30,6 @@ import android.view.ViewGroup import android.view.ViewTreeObserver import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityContainerController -import com.android.keyguard.LegacyLockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT @@ -54,7 +53,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.res.R -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.statusbar.DragDownHelper @@ -71,7 +69,6 @@ import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.DozeScrimController import com.android.systemui.statusbar.phone.DozeServiceHost import com.android.systemui.statusbar.phone.PhoneStatusBarViewController -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.UnfoldTransitionProgressProvider @@ -80,6 +77,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow @@ -98,11 +96,10 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters -import java.util.Optional -import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -125,12 +122,10 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController - @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController @Mock private lateinit var quickSettingsController: QuickSettingsControllerImpl @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController - @Mock private lateinit var lockIconViewController: LegacyLockIconViewController @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController @Mock private lateinit var pulsingGestureListener: PulsingGestureListener @Mock @@ -144,7 +139,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @Mock private lateinit var unfoldTransitionProgressProvider: - Optional + Optional @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock lateinit var dragDownHelper: DragDownHelper @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor @@ -176,20 +171,17 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : MockitoAnnotations.initMocks(this) whenever(view.bottom).thenReturn(VIEW_BOTTOM) whenever(view.findViewById(R.id.keyguard_bouncer_container)) - .thenReturn(mock(ViewGroup::class.java)) + .thenReturn(mock(ViewGroup::class.java)) whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java))) - .thenReturn(keyguardBouncerComponent) + .thenReturn(keyguardBouncerComponent) whenever(keyguardBouncerComponent.securityContainerController) - .thenReturn(keyguardSecurityContainerController) + .thenReturn(keyguardSecurityContainerController) whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING))) - .thenReturn(emptyFlow()) + .thenReturn(emptyFlow()) featureFlagsClassic = FakeFeatureFlagsClassic() featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) - if (!SceneContainerFlag.isEnabled) { - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - } mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES) testScope = TestScope() @@ -208,9 +200,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : panelExpansionInteractor, ShadeExpansionStateManager(), stackScrollLayoutController, - statusBarKeyguardViewManager, statusBarWindowStateController, - lockIconViewController, centralSurfaces, dozeServiceHost, dozeScrimController, @@ -233,7 +223,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : quickSettingsController, primaryBouncerInteractor, alternateBouncerInteractor, - mock(BouncerViewBinder::class.java) + mock(BouncerViewBinder::class.java), ) underTest.setupExpandedStatusBar() underTest.setDragDownHelper(dragDownHelper) @@ -294,7 +284,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) + .thenReturn(true) whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true) val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -309,7 +299,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) + .thenReturn(true) // Item we're testing whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(false) @@ -327,7 +317,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) // Item we're testing whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(false) + .thenReturn(false) val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -341,7 +331,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) + .thenReturn(true) // Item we're testing whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false) @@ -358,7 +348,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) + .thenReturn(true) // Down event first interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -379,7 +369,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : // GIVEN touch dispatcher in a state that returns true underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) - .thenReturn(true) + .thenReturn(true) assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue() // WHEN launch animation is running for 2 seconds @@ -431,48 +421,14 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : assertThat(returnVal).isTrue() } - @Test - fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() { - // down event should be intercepted by keyguardViewManager - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(true) - - // Then touch should not be intercepted - val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) - assertThat(shouldIntercept).isTrue() - } - - @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun shouldInterceptTouchEvent_dozing_touchInLockIconArea_touchNotIntercepted() { - // GIVEN dozing - whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) - // AND alternate bouncer doesn't want the touch - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(false) - // AND quick settings controller doesn't want it - whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) - .thenReturn(false) - // AND the lock icon wants the touch - whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true) - - // THEN touch should NOT be intercepted by NotificationShade - assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse() - } - @Test @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) - // AND alternate bouncer doesn't want the touch - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(false) - // AND the lock icon does NOT want the touch - whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false) // AND quick settings controller doesn't want it whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) - .thenReturn(false) + .thenReturn(false) // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() @@ -483,14 +439,9 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) - // AND alternate bouncer doesn't want the touch - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(false) - // AND the lock icon does NOT want the touch - whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false) // AND quick settings controller DOES want it whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) - .thenReturn(true) + .thenReturn(true) // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() @@ -503,20 +454,13 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) // AND pulsing whenever(dozeServiceHost.isPulsing()).thenReturn(true) - // AND status bar doesn't want it - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(false) - // AND shade is not fully expanded (mock is false by default) - // AND the lock icon does NOT want the touch - whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false) // AND quick settings controller DOES want it whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) - .thenReturn(true) + .thenReturn(true) // AND bouncer is not showing whenever(centralSurfaces.isBouncerShowing()).thenReturn(false) // AND panel view controller wants it - whenever(shadeViewController.handleExternalInterceptTouch(DOWN_EVENT)) - .thenReturn(true) + whenever(shadeViewController.handleExternalInterceptTouch(DOWN_EVENT)).thenReturn(true) // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() @@ -589,12 +533,10 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : underTest.setupCommunalHubLayout() // Simluate attaching the view so flow collection starts. - val onAttachStateChangeListenerArgumentCaptor = ArgumentCaptor.forClass( - View.OnAttachStateChangeListener::class.java - ) - verify(view, atLeast(1)).addOnAttachStateChangeListener( - onAttachStateChangeListenerArgumentCaptor.capture() - ) + val onAttachStateChangeListenerArgumentCaptor = + ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java) + verify(view, atLeast(1)) + .addOnAttachStateChangeListener(onAttachStateChangeListenerArgumentCaptor.capture()) for (listener in onAttachStateChangeListenerArgumentCaptor.allValues) { listener.onViewAttachedToWindow(view) } @@ -608,7 +550,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : @RequiresFlagsDisabled(Flags.FLAG_COMMUNAL_HUB) fun doesNotSetupCommunalHubLayout_whenFlagDisabled() { whenever(mGlanceableHubContainerController.communalAvailable()) - .thenReturn(MutableStateFlow(false)) + .thenReturn(MutableStateFlow(false)) val mockCommunalPlaceholder = mock(View::class.java) val fakeViewIndex = 20 diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index ca29dd98a637..9093b2bcbbf8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -23,7 +23,6 @@ import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityContainerController -import com.android.keyguard.LegacyLockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase @@ -57,7 +56,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.DozeScrimController import com.android.systemui.statusbar.phone.DozeServiceHost -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.UnfoldTransitionProgressProvider @@ -104,11 +102,9 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Mock private lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController - @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController - @Mock private lateinit var lockIconViewController: LegacyLockIconViewController @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var shadeLogger: ShadeLogger @@ -161,7 +157,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { val featureFlags = FakeFeatureFlags() featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES) testScope = TestScope() controller = @@ -176,9 +171,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { panelExpansionInteractor, ShadeExpansionStateManager(), notificationStackScrollLayoutController, - statusBarKeyguardViewManager, statusBarWindowStateController, - lockIconViewController, centralSurfaces, dozeServiceHost, dozeScrimController, @@ -220,49 +213,19 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { ev.recycle() } - @Test - fun testInterceptTouchWhenShowingAltAuth() = - testScope.runTest { - captureInteractionEventHandler() - - // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept - whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any())).thenReturn(true) - whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) - - // THEN we should intercept touch - assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isTrue() - } - @Test fun testNoInterceptTouch() = testScope.runTest { captureInteractionEventHandler() - // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept + // WHEN not dozing, drag down helper doesn't want to intercept whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any())) - .thenReturn(false) whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) // THEN we shouldn't intercept touch assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isFalse() } - @Test - fun testHandleTouchEventWhenShowingAltAuth() = - testScope.runTest { - captureInteractionEventHandler() - - // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept - whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(statusBarKeyguardViewManager.onTouch(any())).thenReturn(true) - whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) - - // THEN we should handle the touch - assertThat(interactionEventHandler.handleTouchEvent(mock())).isTrue() - } - private fun captureInteractionEventHandler() { verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) interactionEventHandler = interactionEventHandlerCaptor.value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 1d74331e429b..94753f7e5203 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -34,7 +34,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,9 +42,7 @@ import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.service.trust.TrustAgentService; import android.testing.TestableLooper; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; @@ -67,7 +64,6 @@ import com.android.keyguard.KeyguardMessageAreaController; import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; @@ -579,22 +575,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { verify(mPrimaryBouncerInteractor).show(eq(scrimmed)); } - @Test - @DisableSceneContainer - @DisableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testShowAlternateBouncer_unlockingWithBiometricAllowed() { - // GIVEN will show alternate bouncer - when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false); - when(mAlternateBouncerInteractor.show()).thenReturn(true); - - // WHEN showGenericBouncer is called - mStatusBarKeyguardViewManager.showBouncer(true); - - // THEN alt auth bouncer is shown - verify(mAlternateBouncerInteractor).show(); - verify(mPrimaryBouncerInteractor, never()).show(anyBoolean()); - } - @Test public void testUpdateResources_delegatesToBouncer() { mStatusBarKeyguardViewManager.updateResources(); @@ -840,145 +820,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { verify(mPrimaryBouncerInteractor, never()).hide(); } - @Test - @EnableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void handleDispatchTouchEvent_alternateBouncerViewFlagEnabled() { - mStatusBarKeyguardViewManager.addCallback(mCallback); - - // GIVEN alternate bouncer view flag enabled & the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // THEN the touch is not acted upon - verify(mCallback, never()).onTouch(any()); - } - - @Test - @EnableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void onInterceptTouch_alternateBouncerViewFlagEnabled() { - // GIVEN alternate bouncer view flag enabled & the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // THEN the touch is not intercepted - assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - } - - @Test - public void handleDispatchTouchEvent_alternateBouncerNotVisible() { - mStatusBarKeyguardViewManager.addCallback(mCallback); - - // GIVEN the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - - // THEN handleDispatchTouchEvent doesn't use the touches - assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - )); - assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - )); - - // THEN the touch is not acted upon - verify(mCallback, never()).onTouch(any()); - } - - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void handleDispatchTouchEvent_shouldInterceptTouchAndHandleTouch() { - mStatusBarKeyguardViewManager.addCallback(mCallback); - - // GIVEN the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // GIVEN all touches are NOT the udfps overlay - when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - - // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent - // to its child views (handleDispatchTouchEvent returns true) - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - )); - - // THEN the touch is acted upon once for each dispatchTOuchEvent call - verify(mCallback, times(3)).onTouch(any()); - } - - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void handleDispatchTouchEvent_shouldInterceptTouchButNotHandleTouch() { - mStatusBarKeyguardViewManager.addCallback(mCallback); - - // GIVEN the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // GIVEN all touches are within the udfps overlay - when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(true); - - // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent - // to its child views (handleDispatchTouchEvent returns true) - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - )); - - // THEN the touch is NOT acted upon at the moment - verify(mCallback, never()).onTouch(any()); - } - - @Test - @DisableSceneContainer - public void shouldInterceptTouch_alternateBouncerNotVisible() { - // GIVEN the alternate bouncer is not visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - - // THEN no motion events are intercepted - assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - )); - assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - )); - } - - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void shouldInterceptTouch_alternateBouncerVisible() { - // GIVEN the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // THEN all motion events are intercepted - assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - )); - } - @Test public void alternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() { // GIVEN the alternate bouncer has shown and calls to hide() will result in successfully @@ -995,106 +836,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { verify(mCentralSurfaces, never()).updateScrimController(); } - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void alternateBouncerOnTouch_actionDownThenUp_noMinTimeShown_noHideAltBouncer() { - reset(mAlternateBouncerInteractor); - - // GIVEN the alternate bouncer has shown for a minimum amount of time - when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - - // WHEN ACTION_DOWN and ACTION_UP touch event comes - boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); - when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true); - boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); - - // THEN the touches are handled (doesn't let touches through to underlying views) - assertTrue(touchHandledDown); - assertTrue(touchHandledUp); - - // THEN alternate bouncer does NOT attempt to hide since min showing time wasn't met - verify(mAlternateBouncerInteractor, never()).hide(); - } - - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void alternateBouncerOnTouch_actionDownThenUp_handlesTouch_hidesAltBouncer() { - reset(mAlternateBouncerInteractor); - - // GIVEN the alternate bouncer has shown for a minimum amount of time - when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - - // WHEN ACTION_DOWN and ACTION_UP touch event comes - boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); - when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true); - boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); - - // THEN the touches are handled - assertTrue(touchHandledDown); - assertTrue(touchHandledUp); - - // THEN alternate bouncer attempts to hide - verify(mAlternateBouncerInteractor).hide(); - } - - @Test - @DisableSceneContainer - public void alternateBouncerOnTouch_actionUp_doesNotHideAlternateBouncer() { - reset(mAlternateBouncerInteractor); - - // GIVEN the alternate bouncer has shown for a minimum amount of time - when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - - // WHEN only ACTION_UP touch event comes - mStatusBarKeyguardViewManager.onTouch( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); - - // THEN the alternateBouncer doesn't hide - verify(mAlternateBouncerInteractor, never()).hide(); - } - - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void onTrustChanged_hideAlternateBouncerAndClearMessageArea() { - // GIVEN keyguard update monitor callback is registered - verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallback.capture()); - - reset(mKeyguardUpdateMonitor); - reset(mKeyguardMessageAreaController); - - // GIVEN alternate bouncer state = not visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - - // WHEN the device is trusted by active unlock - mKeyguardUpdateMonitorCallback.getValue().onTrustGrantedForCurrentUser( - true, - true, - new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD - | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE), - null - ); - - // THEN the false visibility state is propagated to the keyguardUpdateMonitor - verify(mKeyguardUpdateMonitor).setAlternateBouncerShowing(eq(false)); - - // THEN message area visibility updated to FALSE with empty message - verify(mKeyguardMessageAreaController).setIsVisible(eq(false)); - verify(mKeyguardMessageAreaController).setMessage(eq("")); - } - @Test @DisableSceneContainer @DisableFlags(Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART) diff --git a/packages/SystemUI/res/layout/udfps_bp_view.xml b/packages/SystemUI/res/layout/udfps_bp_view.xml deleted file mode 100644 index f1c55ef16cdc..000000000000 --- a/packages/SystemUI/res/layout/udfps_bp_view.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - diff --git a/packages/SystemUI/res/layout/udfps_fpm_empty_view.xml b/packages/SystemUI/res/layout/udfps_fpm_empty_view.xml deleted file mode 100644 index 4799f8c5b668..000000000000 --- a/packages/SystemUI/res/layout/udfps_fpm_empty_view.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt deleted file mode 100644 index 242601d46fa4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt +++ /dev/null @@ -1,35 +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 com.android.systemui.biometrics - -import android.content.Context -import android.util.AttributeSet - -/** - * Class that coordinates non-HBM animations during BiometricPrompt. - * - * Currently doesn't draw anything. - * - * Note that [AuthBiometricFingerprintViewController] also shows UDFPS animations. At some point we should - * de-dupe this if necessary. - */ -class UdfpsBpView(context: Context, attrs: AttributeSet?) : UdfpsAnimationView(context, attrs) { - - // Drawable isn't ever added to the view, so we don't currently show anything - private val fingerprintDrawable: UdfpsFpDrawable = UdfpsFpDrawable(context) - - override fun getDrawable(): UdfpsDrawable = fingerprintDrawable -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt deleted file mode 100644 index e0455b58b919..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt +++ /dev/null @@ -1,47 +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 com.android.systemui.biometrics - -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor -import com.android.systemui.dump.DumpManager -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.phone.SystemUIDialogManager - -/** - * Class that coordinates non-HBM animations for biometric prompt. - */ -class UdfpsBpViewController( - view: UdfpsBpView, - statusBarStateController: StatusBarStateController, - shadeInteractor: ShadeInteractor, - systemUIDialogManager: SystemUIDialogManager, - dumpManager: DumpManager, - udfpsOverlayInteractor: UdfpsOverlayInteractor, -) : UdfpsAnimationViewController( - view, - statusBarStateController, - shadeInteractor, - systemUIDialogManager, - dumpManager, - udfpsOverlayInteractor, -) { - override val tag = "UdfpsBpViewController" - - override fun shouldPauseAuth(): Boolean { - 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 a3904caa9dcc..2863e29c9a34 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -87,7 +87,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.ScreenLifecycle; @@ -98,7 +97,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.SystemUIDialogManager; @@ -162,7 +160,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final FalsingManager mFalsingManager; @NonNull private final PowerManager mPowerManager; @NonNull private final AccessibilityManager mAccessibilityManager; - @NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; @NonNull private final ConfigurationController mConfigurationController; @NonNull private final SystemClock mSystemClock; @NonNull private final UnlockedScreenOffAnimationController @@ -283,7 +280,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mKeyguardUpdateMonitor, mDialogManager, mDumpManager, - mLockscreenShadeTransitionController, mConfigurationController, mKeyguardStateController, mUnlockedScreenOffAnimationController, @@ -291,10 +287,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { requestId, reason, callback, - (view, event, fromUdfpsView) -> onTouch( + (view, event) -> onTouch( requestId, - event, - fromUdfpsView + event ), mActivityTransitionAnimator, mPrimaryBouncerInteractor, @@ -374,9 +369,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (mOverlay == null || mOverlay.isHiding()) { return; } - if (!DeviceEntryUdfpsRefactor.isEnabled()) { - ((UdfpsView) mOverlay.getTouchOverlay()).setDebugMessage(message); - } }); } @@ -391,7 +383,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { */ public void debugOnTouch(MotionEvent event) { final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L; - UdfpsController.this.onTouch(requestId, event, true); + UdfpsController.this.onTouch(requestId, event); } /** @@ -449,22 +441,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (!mOverlayParams.equals(overlayParams)) { mOverlayParams = overlayParams; - if (DeviceEntryUdfpsRefactor.isEnabled()) { - if (mOverlay != null && mOverlay.getRequestReason() == REASON_AUTH_KEYGUARD) { - mOverlay.updateOverlayParams(mOverlayParams); - } else { - redrawOverlay(); - } + if (mOverlay != null && mOverlay.getRequestReason() == REASON_AUTH_KEYGUARD) { + mOverlay.updateOverlayParams(mOverlayParams); } else { - final boolean wasShowingAlternateBouncer = - mAlternateBouncerInteractor.isVisibleState(); - // When the bounds change it's always to re-create the overlay's window with new - // LayoutParams. If the overlay needs to be shown, this will re-create and show the - // overlay with the updated LayoutParams. Otherwise, the overlay will remain hidden. redrawOverlay(); - if (wasShowingAlternateBouncer) { - mKeyguardViewManager.showBouncer(true); - } } } } @@ -563,11 +543,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } - private boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { - if (!fromUdfpsView) { - Log.e(TAG, "ignoring the touch injected from outside of UdfpsView"); - return false; - } + private boolean onTouch(long requestId, @NonNull MotionEvent event) { if (mOverlay == null) { Log.w(TAG, "ignoring onTouch with null overlay"); return false; @@ -591,13 +567,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (!mIsAodInterruptActive) { mOnFingerDown = false; } - } else if (!DeviceEntryUdfpsRefactor.isEnabled()) { - if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f - && !mAlternateBouncerInteractor.isVisibleState()) - || mPrimaryBouncerInteractor.isInTransit()) { - Log.w(TAG, "ignoring touch due to qsDragProcess or primaryBouncerInteractor"); - return false; - } } final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId, @@ -661,22 +630,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { mStatusBarStateController.isDozing()); break; - case UNCHANGED: - if (mActivePointerId == MotionEvent.INVALID_POINTER_ID - && mAlternateBouncerInteractor.isVisibleState()) { - // No pointer on sensor, forward to keyguard if alternateBouncer is visible - mKeyguardViewManager.onTouch(event); - } - default: break; } logBiometricTouch(processedTouch.getEvent(), data); // Always pilfer pointers that are within sensor area or when alternate bouncer is showing - if (mActivePointerId != MotionEvent.INVALID_POINTER_ID - || (mAlternateBouncerInteractor.isVisibleState() - && !DeviceEntryUdfpsRefactor.isEnabled())) { + if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) { shouldPilfer = true; } @@ -692,14 +652,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } private boolean shouldTryToDismissKeyguard() { - boolean onKeyguard = false; - if (DeviceEntryUdfpsRefactor.isEnabled()) { - onKeyguard = mKeyguardStateController.isShowing(); - } else { - onKeyguard = mOverlay != null - && mOverlay.getAnimationViewController() - instanceof UdfpsKeyguardViewControllerLegacy; - } + boolean onKeyguard = mKeyguardStateController.isShowing(); return onKeyguard && mKeyguardStateController.canDismissLockScreen() && !mAttemptedToDismissKeyguard; @@ -719,7 +672,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull FalsingManager falsingManager, @NonNull PowerManager powerManager, @NonNull AccessibilityManager accessibilityManager, - @NonNull LockscreenShadeTransitionController lockscreenShadeTransitionController, @NonNull ScreenLifecycle screenLifecycle, @NonNull VibratorHelper vibrator, @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator, @@ -769,7 +721,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mFalsingManager = falsingManager; mPowerManager = powerManager; mAccessibilityManager = accessibilityManager; - mLockscreenShadeTransitionController = lockscreenShadeTransitionController; screenLifecycle.addObserver(mScreenObserver); mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON; mConfigurationController = configurationController; @@ -849,13 +800,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @Override public void dozeTimeTick() { - if (mOverlay != null && mOverlay.getTouchOverlay() instanceof UdfpsView) { - DeviceEntryUdfpsRefactor.assertInLegacyMode(); - final View view = mOverlay.getTouchOverlay(); - if (view != null) { - ((UdfpsView) view).dozeTimeTick(); - } - } + } private void redrawOverlay() { @@ -915,17 +860,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (!isOptical()) { return; } - if (DeviceEntryUdfpsRefactor.isEnabled()) { - if (mUdfpsDisplayMode != null) { - mUdfpsDisplayMode.disable(null); - } - } else { - if (view != null) { - UdfpsView udfpsView = (UdfpsView) view; - if (udfpsView.isDisplayConfigured()) { - udfpsView.unconfigureDisplay(); - } - } + if (mUdfpsDisplayMode != null) { + mUdfpsDisplayMode.disable(null); } } @@ -1118,11 +1054,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (mIgnoreRefreshRate) { dispatchOnUiReady(requestId); } else { - if (DeviceEntryUdfpsRefactor.isEnabled()) { mUdfpsDisplayMode.enable(() -> dispatchOnUiReady(requestId)); - } else { - ((UdfpsView) view).configureDisplay(() -> dispatchOnUiReady(requestId)); - } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 1bac0bc26a94..a1efc196dbee 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -23,8 +23,6 @@ import android.graphics.PixelFormat import android.graphics.Rect import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR import android.hardware.biometrics.BiometricRequestConstants.RequestReason @@ -42,7 +40,6 @@ import android.view.View import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener -import androidx.annotation.LayoutRes import androidx.annotation.VisibleForTesting import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.keyguard.KeyguardUpdateMonitor @@ -56,7 +53,6 @@ import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlay import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -64,7 +60,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController @@ -102,7 +97,6 @@ constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val dialogManager: SystemUIDialogManager, private val dumpManager: DumpManager, - private val transitionController: LockscreenShadeTransitionController, private val configurationController: ConfigurationController, private val keyguardStateController: KeyguardStateController, private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, @@ -110,7 +104,7 @@ constructor( val requestId: Long, @RequestReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, - private val onTouch: (View, MotionEvent, Boolean) -> Boolean, + private val onTouch: (View, MotionEvent) -> Boolean, private val activityTransitionAnimator: ActivityTransitionAnimator, private val primaryBouncerInteractor: PrimaryBouncerInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, @@ -133,23 +127,15 @@ constructor( .map {} // map to Unit private var listenForCurrentKeyguardState: Job? = null private var addViewRunnable: Runnable? = null - private var overlayViewLegacy: UdfpsView? = null - private set - private var overlayTouchView: UdfpsTouchOverlay? = null /** - * Get the current UDFPS overlay touch view which is a different View depending on whether the - * DeviceEntryUdfpsRefactor flag is enabled or not. + * Get the current UDFPS overlay touch view * * @return The view, when [isShowing], else null */ fun getTouchOverlay(): View? { - return if (DeviceEntryUdfpsRefactor.isEnabled) { - overlayTouchView - } else { - overlayViewLegacy - } + return overlayTouchView } private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams() @@ -161,7 +147,7 @@ constructor( WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 0 /* flags set in computeLayoutParams() */, - PixelFormat.TRANSLUCENT + PixelFormat.TRANSLUCENT, ) .apply { title = TAG @@ -188,10 +174,6 @@ constructor( val isHiding: Boolean get() = getTouchOverlay() == null - /** The animation controller if the overlay [isShowing]. */ - val animationViewController: UdfpsAnimationViewController<*>? - get() = overlayViewLegacy?.animationViewController - private var touchExplorationEnabled = false private fun shouldRemoveEnrollmentUi(): Boolean { @@ -199,7 +181,7 @@ constructor( return Settings.Global.getInt( context.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, - 0 /* def */ + 0, /* def */ ) != 0 } return false @@ -212,63 +194,43 @@ constructor( overlayParams = params sensorBounds = Rect(params.sensorBounds) try { - if (DeviceEntryUdfpsRefactor.isEnabled) { - overlayTouchView = - (inflater.inflate(R.layout.udfps_touch_overlay, null, false) - as UdfpsTouchOverlay) - .apply { - // This view overlaps the sensor area - // prevent it from being selectable during a11y - if (requestReason.isImportantForAccessibility()) { - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - } - - addViewNowOrLater(this, null) - when (requestReason) { - REASON_AUTH_KEYGUARD -> - UdfpsTouchOverlayBinder.bind( - view = this, - viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(), - udfpsOverlayInteractor = udfpsOverlayInteractor, - ) - else -> - UdfpsTouchOverlayBinder.bind( - view = this, - viewModel = defaultUdfpsTouchOverlayViewModel.get(), - udfpsOverlayInteractor = udfpsOverlayInteractor, - ) - } - } - } else { - overlayViewLegacy = - (inflater.inflate(R.layout.udfps_view, null, false) as UdfpsView).apply { - overlayParams = params - setUdfpsDisplayModeProvider(udfpsDisplayModeProvider) - val animation = inflateUdfpsAnimation(this, controller) - if (animation != null) { - animation.init() - animationViewController = animation - } + overlayTouchView = + (inflater.inflate(R.layout.udfps_touch_overlay, null, false) + as UdfpsTouchOverlay) + .apply { // This view overlaps the sensor area // prevent it from being selectable during a11y if (requestReason.isImportantForAccessibility()) { importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO } - addViewNowOrLater(this, animation) - sensorRect = sensorBounds + addViewNowOrLater(this, null) + when (requestReason) { + REASON_AUTH_KEYGUARD -> + UdfpsTouchOverlayBinder.bind( + view = this, + viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(), + udfpsOverlayInteractor = udfpsOverlayInteractor, + ) + else -> + UdfpsTouchOverlayBinder.bind( + view = this, + viewModel = defaultUdfpsTouchOverlayViewModel.get(), + udfpsOverlayInteractor = udfpsOverlayInteractor, + ) + } } - } + getTouchOverlay()?.apply { touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled overlayTouchListener = TouchExplorationStateChangeListener { if (accessibilityManager.isTouchExplorationEnabled) { - setOnHoverListener { v, event -> onTouch(v, event, true) } + setOnHoverListener { v, event -> onTouch(v, event) } setOnTouchListener(null) touchExplorationEnabled = true } else { setOnHoverListener(null) - setOnTouchListener { v, event -> onTouch(v, event, true) } + setOnTouchListener { v, event -> onTouch(v, event) } touchExplorationEnabled = false } } @@ -312,7 +274,6 @@ constructor( } fun updateOverlayParams(updatedOverlayParams: UdfpsOverlayParams) { - DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode() overlayParams = updatedOverlayParams sensorBounds = updatedOverlayParams.sensorBounds getTouchOverlay()?.let { @@ -326,108 +287,11 @@ constructor( } } - fun inflateUdfpsAnimation( - view: UdfpsView, - controller: UdfpsController - ): UdfpsAnimationViewController<*>? { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - - val isEnrollment = - when (requestReason) { - REASON_ENROLL_FIND_SENSOR, - REASON_ENROLL_ENROLLING -> true - else -> false - } - - val filteredRequestReason = - if (isEnrollment && shouldRemoveEnrollmentUi()) { - REASON_AUTH_OTHER - } else { - requestReason - } - - return when (filteredRequestReason) { - REASON_ENROLL_FIND_SENSOR, - REASON_ENROLL_ENROLLING -> { - // Enroll udfps UI is handled by settings, so use empty view here - UdfpsFpmEmptyViewController( - view.addUdfpsView(R.layout.udfps_fpm_empty_view) { - updateAccessibilityViewLocation(sensorBounds) - }, - statusBarStateController, - shadeInteractor, - dialogManager, - dumpManager, - udfpsOverlayInteractor, - ) - } - REASON_AUTH_KEYGUARD -> { - UdfpsKeyguardViewControllerLegacy( - view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { - updateSensorLocation(sensorBounds) - }, - statusBarStateController, - statusBarKeyguardViewManager, - keyguardUpdateMonitor, - dumpManager, - transitionController, - configurationController, - keyguardStateController, - unlockedScreenOffAnimationController, - dialogManager, - controller, - activityTransitionAnimator, - primaryBouncerInteractor, - alternateBouncerInteractor, - udfpsKeyguardAccessibilityDelegate, - selectedUserInteractor, - transitionInteractor, - shadeInteractor, - udfpsOverlayInteractor, - ) - } - REASON_AUTH_BP -> { - // note: empty controller, currently shows no visual affordance - UdfpsBpViewController( - view.addUdfpsView(R.layout.udfps_bp_view), - statusBarStateController, - shadeInteractor, - dialogManager, - dumpManager, - udfpsOverlayInteractor, - ) - } - REASON_AUTH_OTHER, - REASON_AUTH_SETTINGS -> { - UdfpsFpmEmptyViewController( - view.addUdfpsView(R.layout.udfps_fpm_empty_view), - statusBarStateController, - shadeInteractor, - dialogManager, - dumpManager, - udfpsOverlayInteractor, - ) - } - else -> { - Log.e(TAG, "Animation for reason $requestReason not supported yet") - null - } - } - } - /** Hide the overlay or return false and do nothing if it is already hidden. */ fun hide(): Boolean { val wasShowing = isShowing - overlayViewLegacy?.apply { - if (isDisplayConfigured) { - unconfigureDisplay() - } - animationViewController = null - } - if (DeviceEntryUdfpsRefactor.isEnabled) { - udfpsDisplayModeProvider.disable(null) - } + udfpsDisplayModeProvider.disable(null) getTouchOverlay()?.apply { if (this.parent != null) { windowManager.removeView(this) @@ -440,7 +304,6 @@ constructor( } } - overlayViewLegacy = null overlayTouchView = null overlayTouchListener = null listenForCurrentKeyguardState?.cancel() @@ -490,7 +353,7 @@ constructor( Surface.rotationToString(rot) + " animation=$animation" + " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" + - " isOccluded=${keyguardStateController.isOccluded}" + " isOccluded=${keyguardStateController.isOccluded}", ) } else { Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot)) @@ -498,14 +361,14 @@ constructor( rotatedBounds, overlayParams.naturalDisplayWidth, overlayParams.naturalDisplayHeight, - rot + rot, ) RotationUtils.rotateBounds( sensorBounds, overlayParams.naturalDisplayWidth, overlayParams.naturalDisplayHeight, - rot + rot, ) } } @@ -519,14 +382,7 @@ constructor( } private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean { - val keyguardNotShowing = - if (DeviceEntryUdfpsRefactor.isEnabled) { - !keyguardStateController.isShowing - } else { - animation !is UdfpsKeyguardViewControllerLegacy - } - - if (keyguardNotShowing) { + if (!keyguardStateController.isShowing) { // always rotate view if we're not on the keyguard return true } @@ -534,16 +390,6 @@ constructor( // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded return !(keyguardUpdateMonitor.isGoingToSleep || !keyguardStateController.isOccluded) } - - private inline fun UdfpsView.addUdfpsView( - @LayoutRes id: Int, - init: T.() -> Unit = {} - ): T { - val subView = inflater.inflate(id, null) as T - addView(subView) - subView.init() - return subView - } } @RequestReason diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt deleted file mode 100644 index 0838855f4756..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt +++ /dev/null @@ -1,49 +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 com.android.systemui.biometrics - -import android.content.Context -import android.graphics.Rect -import android.util.AttributeSet -import android.view.View -import android.view.ViewGroup -import com.android.systemui.res.R - -/** - * View corresponding with udfps_fpm_empty_view.xml - * - * Currently doesn't draw anything. - */ -class UdfpsFpmEmptyView( - context: Context, - attrs: AttributeSet? -) : UdfpsAnimationView(context, attrs) { - - // Drawable isn't ever added to the view, so we don't currently show anything - private val fingerprintDrawable: UdfpsFpDrawable = UdfpsFpDrawable(context) - - override fun getDrawable(): UdfpsDrawable = fingerprintDrawable - - fun updateAccessibilityViewLocation(sensorBounds: Rect) { - val fingerprintAccessibilityView: View = - requireViewById(R.id.udfps_enroll_accessibility_view) - val params: ViewGroup.LayoutParams = fingerprintAccessibilityView.layoutParams - params.width = sensorBounds.width() - params.height = sensorBounds.height() - fingerprintAccessibilityView.layoutParams = params - fingerprintAccessibilityView.requestLayout() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt deleted file mode 100644 index cfbbc26800d2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt +++ /dev/null @@ -1,45 +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 com.android.systemui.biometrics - -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor -import com.android.systemui.dump.DumpManager -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.phone.SystemUIDialogManager - -/** - * Class that coordinates non-HBM animations for non keyguard, or biometric prompt states. - * - * Currently doesn't draw anything. - */ -class UdfpsFpmEmptyViewController( - view: UdfpsFpmEmptyView, - statusBarStateController: StatusBarStateController, - shadeInteractor: ShadeInteractor, - systemUIDialogManager: SystemUIDialogManager, - dumpManager: DumpManager, - udfpsOverlayInteractor: UdfpsOverlayInteractor, -) : UdfpsAnimationViewController( - view, - statusBarStateController, - shadeInteractor, - systemUIDialogManager, - dumpManager, - udfpsOverlayInteractor, -) { - override val tag = "UdfpsFpmOtherViewController" -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt deleted file mode 100644 index c3d9240c40a1..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ /dev/null @@ -1,555 +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 com.android.systemui.biometrics - -import android.content.res.Configuration -import android.util.MathUtils -import android.view.View -import androidx.annotation.VisibleForTesting -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.android.app.animation.Interpolators -import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.animation.ActivityTransitionAnimator -import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor -import com.android.systemui.dump.DumpManager -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.Edge -import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER -import com.android.systemui.keyguard.shared.model.KeyguardState.AOD -import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING -import com.android.systemui.keyguard.shared.model.KeyguardState.GONE -import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED -import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.res.R -import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.LockscreenShadeTransitionController -import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI -import com.android.systemui.statusbar.phone.SystemUIDialogManager -import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.user.domain.interactor.SelectedUserInteractor -import java.io.PrintWriter -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch - -/** Class that coordinates non-HBM animations during keyguard authentication. */ -@ExperimentalCoroutinesApi -open class UdfpsKeyguardViewControllerLegacy( - private val view: UdfpsKeyguardViewLegacy, - statusBarStateController: StatusBarStateController, - private val keyguardViewManager: StatusBarKeyguardViewManager, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - dumpManager: DumpManager, - private val lockScreenShadeTransitionController: LockscreenShadeTransitionController, - private val configurationController: ConfigurationController, - private val keyguardStateController: KeyguardStateController, - private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, - systemUIDialogManager: SystemUIDialogManager, - private val udfpsController: UdfpsController, - private val activityTransitionAnimator: ActivityTransitionAnimator, - private val primaryBouncerInteractor: PrimaryBouncerInteractor, - private val alternateBouncerInteractor: AlternateBouncerInteractor, - private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, - private val selectedUserInteractor: SelectedUserInteractor, - private val transitionInteractor: KeyguardTransitionInteractor, - shadeInteractor: ShadeInteractor, - udfpsOverlayInteractor: UdfpsOverlayInteractor, -) : - UdfpsAnimationViewController( - view, - statusBarStateController, - shadeInteractor, - systemUIDialogManager, - dumpManager, - udfpsOverlayInteractor, - ) { - private val uniqueIdentifier = this.toString() - private var showingUdfpsBouncer = false - private var udfpsRequested = false - private var qsExpansion = 0f - private var faceDetectRunning = false - private var statusBarState = 0 - private var transitionToFullShadeProgress = 0f - private var lastDozeAmount = 0f - private var panelExpansionFraction = 0f - private var launchTransitionFadingAway = false - private var isLaunchingActivity = false - private var activityLaunchProgress = 0f - private var inputBouncerExpansion = 0f - - private val stateListener: StatusBarStateController.StateListener = - object : StatusBarStateController.StateListener { - override fun onStateChanged(statusBarState: Int) { - this@UdfpsKeyguardViewControllerLegacy.statusBarState = statusBarState - updateAlpha() - updatePauseAuth() - } - } - - private val configurationListener: ConfigurationController.ConfigurationListener = - object : ConfigurationController.ConfigurationListener { - override fun onUiModeChanged() { - view.updateColor() - } - - override fun onThemeChanged() { - view.updateColor() - } - - override fun onConfigChanged(newConfig: Configuration) { - updateScaleFactor() - view.updatePadding() - view.updateColor() - } - } - - private val keyguardStateControllerCallback: KeyguardStateController.Callback = - object : KeyguardStateController.Callback { - override fun onUnlockedChanged() { - updatePauseAuth() - } - - override fun onLaunchTransitionFadingAwayChanged() { - launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway - updatePauseAuth() - } - } - - private val mActivityTransitionAnimatorListener: ActivityTransitionAnimator.Listener = - object : ActivityTransitionAnimator.Listener { - override fun onTransitionAnimationStart() { - isLaunchingActivity = true - activityLaunchProgress = 0f - updateAlpha() - } - - override fun onTransitionAnimationEnd() { - isLaunchingActivity = false - updateAlpha() - } - - override fun onTransitionAnimationProgress(linearProgress: Float) { - activityLaunchProgress = linearProgress - updateAlpha() - } - } - - private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback = - object : KeyguardViewManagerCallback { - override fun onQSExpansionChanged(qsExpansion: Float) { - this@UdfpsKeyguardViewControllerLegacy.qsExpansion = qsExpansion - updateAlpha() - updatePauseAuth() - } - } - - private val occludingAppBiometricUI: OccludingAppBiometricUI = - object : OccludingAppBiometricUI { - override fun requestUdfps(request: Boolean, color: Int) { - udfpsRequested = request - view.requestUdfps(request, color) - updateAlpha() - updatePauseAuth() - } - - override fun dump(pw: PrintWriter) { - pw.println(tag) - } - } - - override val tag: String - get() = TAG - - override fun onInit() { - super.onInit() - keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) - } - - init { - com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor.assertInLegacyMode() - view.repeatWhenAttached { - // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion - // can make the view not visible; and we still want to listen for events - // that may make the view visible again. - repeatOnLifecycle(Lifecycle.State.CREATED) { - listenForBouncerExpansion(this) - listenForAlternateBouncerVisibility(this) - listenForOccludedToAodTransition(this) - listenForGoneToAodTransition(this) - listenForLockscreenAodTransitions(this) - listenForAodToOccludedTransitions(this) - listenForAlternateBouncerToAodTransitions(this) - listenForDreamingToAodTransitions(this) - listenForPrimaryBouncerToAodTransitions(this) - } - } - } - - @VisibleForTesting - suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor - .transition( - edge = Edge.create(Scenes.Bouncer, AOD), - edgeWithoutSceneContainer = Edge.create(PRIMARY_BOUNCER, AOD) - ) - .collect { transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - ANIMATE_APPEAR_ON_SCREEN_OFF, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor.transition(Edge.create(DREAMING, AOD)).collect { transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - ANIMATE_APPEAR_ON_SCREEN_OFF, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor.transition(Edge.create(ALTERNATE_BOUNCER, AOD)).collect { - transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor.transition(Edge.create(AOD, OCCLUDED)).collect { transitionStep -> - view.onDozeAmountChanged( - 1f - transitionStep.value, - 1f - transitionStep.value, - UdfpsKeyguardViewLegacy.ANIMATION_NONE, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForOccludedToAodTransition(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor.transition(Edge.create(OCCLUDED, AOD)).collect { transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - ANIMATE_APPEAR_ON_SCREEN_OFF, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor - .transition( - edge = Edge.create(Scenes.Gone, AOD), - edgeWithoutSceneContainer = Edge.create(GONE, AOD) - ) - .collect { transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - ANIMATE_APPEAR_ON_SCREEN_OFF, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor.transitionValue(AOD).collect { - view.onDozeAmountChanged( - it, - it, - UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job { - return scope.launch { - primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float -> - inputBouncerExpansion = bouncerExpansion - - panelExpansionFraction = - if (keyguardViewManager.isPrimaryBouncerInTransit) { - aboutToShowBouncerProgress(1f - bouncerExpansion) - } else { - 1f - bouncerExpansion - } - updateAlpha() - updatePauseAuth() - } - } - } - - @VisibleForTesting - suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job { - return scope.launch { - alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> - showUdfpsBouncer(isVisible) - } - } - } - - public override fun onViewAttached() { - super.onViewAttached() - alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier) - val dozeAmount = statusBarStateController.dozeAmount - lastDozeAmount = dozeAmount - stateListener.onDozeAmountChanged(dozeAmount, dozeAmount) - statusBarStateController.addCallback(stateListener) - udfpsRequested = false - launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway - keyguardStateController.addCallback(keyguardStateControllerCallback) - statusBarState = statusBarStateController.state - qsExpansion = keyguardViewManager.qsExpansion - keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback) - configurationController.addCallback(configurationListener) - updateScaleFactor() - view.updatePadding() - updateAlpha() - updatePauseAuth() - keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) - lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this - activityTransitionAnimator.addListener(mActivityTransitionAnimatorListener) - view.startIconAsyncInflate { - val animationViewInternal: View = - view.requireViewById(R.id.udfps_animation_view_internal) - animationViewInternal.accessibilityDelegate = udfpsKeyguardAccessibilityDelegate - } - } - - public override fun onViewDetached() { - super.onViewDetached() - alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier) - faceDetectRunning = false - keyguardStateController.removeCallback(keyguardStateControllerCallback) - statusBarStateController.removeCallback(stateListener) - keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI) - configurationController.removeCallback(configurationListener) - if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) { - lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null - } - activityTransitionAnimator.removeListener(mActivityTransitionAnimatorListener) - keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback) - } - - override fun dump(pw: PrintWriter, args: Array) { - super.dump(pw, args) - pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer") - pw.println( - "altBouncerInteractor#isAlternateBouncerVisible=" + - "${alternateBouncerInteractor.isVisibleState()}" - ) - pw.println( - "altBouncerInteractor#canShowAlternateBouncerForFingerprint=" + - "${alternateBouncerInteractor.canShowAlternateBouncerForFingerprint()}" - ) - pw.println("faceDetectRunning=$faceDetectRunning") - pw.println("statusBarState=" + StatusBarState.toString(statusBarState)) - pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress") - pw.println("qsExpansion=$qsExpansion") - pw.println("panelExpansionFraction=$panelExpansionFraction") - pw.println("unpausedAlpha=" + view.unpausedAlpha) - pw.println("udfpsRequestedByApp=$udfpsRequested") - pw.println("launchTransitionFadingAway=$launchTransitionFadingAway") - pw.println("lastDozeAmount=$lastDozeAmount") - pw.println("inputBouncerExpansion=$inputBouncerExpansion") - view.dump(pw) - } - - /** - * Overrides non-bouncer show logic in shouldPauseAuth to still show icon. - * - * @return whether the udfpsBouncer has been newly shown or hidden - */ - private fun showUdfpsBouncer(show: Boolean): Boolean { - if (showingUdfpsBouncer == show) { - return false - } - val udfpsAffordanceWasNotShowing = shouldPauseAuth() - showingUdfpsBouncer = show - if (showingUdfpsBouncer) { - if (udfpsAffordanceWasNotShowing) { - view.animateInUdfpsBouncer(null) - } - view.announceForAccessibility( - view.context.getString(R.string.accessibility_fingerprint_bouncer) - ) - } - updateAlpha() - updatePauseAuth() - return true - } - - /** - * Returns true if the fingerprint manager is running but we want to temporarily pause - * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so - * this can be overridden with the showBouncer method. - */ - override fun shouldPauseAuth(): Boolean { - if (showingUdfpsBouncer) { - return false - } - if ( - udfpsRequested && - !notificationShadeVisible && - !isInputBouncerFullyVisible() && - keyguardStateController.isShowing - ) { - return false - } - if (launchTransitionFadingAway) { - return true - } - - // Only pause auth if we're not on the keyguard AND we're not transitioning to doze. - // For the UnlockedScreenOffAnimation, the statusBarState is - // delayed. However, we still animate in the UDFPS affordance with the - // unlockedScreenOffDozeAnimator. - if ( - statusBarState != StatusBarState.KEYGUARD && - !unlockedScreenOffAnimationController.isAnimationPlaying() - ) { - return true - } - if (isBouncerExpansionGreaterThan(.5f)) { - return true - } - if ( - keyguardUpdateMonitor.getUserUnlockedWithBiometric( - selectedUserInteractor.getSelectedUserId() - ) - ) { - // If the device was unlocked by a biometric, immediately hide the UDFPS icon to avoid - // overlap with the LockIconView. Shortly afterwards, UDFPS will stop running. - return true - } - return view.unpausedAlpha < 255 * .1 - } - - fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean { - return inputBouncerExpansion >= bouncerExpansionThreshold - } - - fun isInputBouncerFullyVisible(): Boolean { - return inputBouncerExpansion == 1f - } - - override fun listenForTouchesOutsideView(): Boolean { - return true - } - - /** - * Set the progress we're currently transitioning to the full shade. 0.0f means we're not - * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down - * to expand the notification shade from the empty space in the middle of the lock screen. - */ - fun setTransitionToFullShadeProgress(progress: Float) { - transitionToFullShadeProgress = progress - updateAlpha() - } - - /** - * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is - * based on the doze amount. - */ - override fun updateAlpha() { - // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested, - // then the keyguard is occluded by some application - so instead use the input bouncer - // hidden amount to determine the fade. - val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction - var alpha: Int = - if (showingUdfpsBouncer) 255 - else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt() - if (!showingUdfpsBouncer) { - // swipe from top of the lockscreen to expand full QS: - alpha = - (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion))) - .toInt() - - // swipe from the middle (empty space) of lockscreen to expand the notification shade: - alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt() - - // Fade out the icon if we are animating an activity launch over the lockscreen and the - // activity didn't request the UDFPS. - if (isLaunchingActivity && !udfpsRequested) { - val udfpsActivityLaunchAlphaMultiplier = - 1f - - (activityLaunchProgress * - (ActivityTransitionAnimator.TIMINGS.totalDuration / 83)) - .coerceIn(0f, 1f) - alpha = (alpha * udfpsActivityLaunchAlphaMultiplier).toInt() - } - - // Fade out alpha when a dialog is shown - // Fade in alpha when a dialog is hidden - alpha = (alpha * view.dialogSuggestedAlpha).toInt() - } - view.unpausedAlpha = alpha - } - - private fun getInputBouncerHiddenAmt(): Float { - return 1f - inputBouncerExpansion - } - - /** Update the scale factor based on the device's resolution. */ - private fun updateScaleFactor() { - udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) } - } - - companion object { - const val TAG = "UdfpsKeyguardViewController" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java deleted file mode 100644 index 6d4eea852d26..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java +++ /dev/null @@ -1,330 +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 com.android.systemui.biometrics; - -import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; -import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.util.MathUtils; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.asynclayoutinflater.view.AsyncLayoutInflater; - -import com.android.app.animation.Interpolators; -import com.android.settingslib.Utils; -import com.android.systemui.res.R; - -import com.airbnb.lottie.LottieAnimationView; -import com.airbnb.lottie.LottieProperty; -import com.airbnb.lottie.model.KeyPath; - -import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * View corresponding with udfps_keyguard_view_legacy.xml - */ -public class UdfpsKeyguardViewLegacy extends UdfpsAnimationView { - private UdfpsDrawable mFingerprintDrawable; // placeholder - private LottieAnimationView mAodFp; - private LottieAnimationView mLockScreenFp; - - // used when highlighting fp icon: - private int mTextColorPrimary; - private ImageView mBgProtection; - boolean mUdfpsRequested; - - private AnimatorSet mBackgroundInAnimator = new AnimatorSet(); - private int mAlpha; // 0-255 - private float mScaleFactor = 1; - private Rect mSensorBounds = new Rect(); - - // AOD anti-burn-in offsets - private final int mMaxBurnInOffsetX; - private final int mMaxBurnInOffsetY; - private float mInterpolatedDarkAmount; - private int mAnimationType = ANIMATION_NONE; - private boolean mFullyInflated; - private Runnable mOnFinishInflateRunnable; - - public UdfpsKeyguardViewLegacy(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - mFingerprintDrawable = new UdfpsFpDrawable(context); - - mMaxBurnInOffsetX = context.getResources() - .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); - mMaxBurnInOffsetY = context.getResources() - .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); - } - - /** - * Inflate internal udfps view on a background thread and call the onFinishRunnable - * when inflation is finished. - */ - public void startIconAsyncInflate(Runnable onFinishInflate) { - mOnFinishInflateRunnable = onFinishInflate; - // inflate Lottie views on a background thread in case it takes a while to inflate - AsyncLayoutInflater inflater = new AsyncLayoutInflater(mContext); - inflater.inflate(R.layout.udfps_keyguard_view_internal, this, - mLayoutInflaterFinishListener); - } - - @Override - public UdfpsDrawable getDrawable() { - return mFingerprintDrawable; - } - - @Override - void onSensorRectUpdated(RectF bounds) { - super.onSensorRectUpdated(bounds); - bounds.round(this.mSensorBounds); - postInvalidate(); - } - - @Override - void onDisplayConfiguring() { - } - - @Override - void onDisplayUnconfigured() { - } - - @Override - public boolean dozeTimeTick() { - updateBurnInOffsets(); - return true; - } - - private void updateBurnInOffsets() { - if (!mFullyInflated) { - return; - } - - // if we're animating from screen off, we can immediately place the icon in the - // AoD-burn in location, else we need to translate the icon from LS => AoD. - final float darkAmountForAnimation = mAnimationType == ANIMATE_APPEAR_ON_SCREEN_OFF - ? 1f : mInterpolatedDarkAmount; - final float burnInOffsetX = MathUtils.lerp(0f, - getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) - - mMaxBurnInOffsetX, darkAmountForAnimation); - final float burnInOffsetY = MathUtils.lerp(0f, - getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) - - mMaxBurnInOffsetY, darkAmountForAnimation); - final float burnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), - darkAmountForAnimation); - - if (mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN && !mPauseAuth) { - mLockScreenFp.setTranslationX(burnInOffsetX); - mLockScreenFp.setTranslationY(burnInOffsetY); - mBgProtection.setAlpha(1f - mInterpolatedDarkAmount); - mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount); - } else if (darkAmountForAnimation == 0f) { - // we're on the lockscreen and should use mAlpha (changes based on shade expansion) - mLockScreenFp.setTranslationX(0); - mLockScreenFp.setTranslationY(0); - mBgProtection.setAlpha(mAlpha / 255f); - mLockScreenFp.setAlpha(mAlpha / 255f); - } else { - mBgProtection.setAlpha(0f); - mLockScreenFp.setAlpha(0f); - } - mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount); - - mAodFp.setTranslationX(burnInOffsetX); - mAodFp.setTranslationY(burnInOffsetY); - mAodFp.setProgress(burnInProgress); - mAodFp.setAlpha(mInterpolatedDarkAmount); - - // done animating - final boolean doneAnimatingBetweenAodAndLS = - mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN - && (mInterpolatedDarkAmount == 0f || mInterpolatedDarkAmount == 1f); - final boolean doneAnimatingUnlockedScreenOff = - mAnimationType == ANIMATE_APPEAR_ON_SCREEN_OFF - && (mInterpolatedDarkAmount == 1f); - if (doneAnimatingBetweenAodAndLS || doneAnimatingUnlockedScreenOff) { - mAnimationType = ANIMATION_NONE; - } - } - - void requestUdfps(boolean request, int color) { - mUdfpsRequested = request; - } - - void updateColor() { - if (!mFullyInflated) { - return; - } - - mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext, - com.android.internal.R.attr.materialColorOnSurface); - final int backgroundColor = Utils.getColorAttrDefaultColor(getContext(), - com.android.internal.R.attr.materialColorSurfaceContainerHigh); - mBgProtection.setImageTintList(ColorStateList.valueOf(backgroundColor)); - mLockScreenFp.invalidate(); // updated with a valueCallback - } - - void setScaleFactor(float scale) { - mScaleFactor = scale; - } - - void updatePadding() { - if (mLockScreenFp == null || mAodFp == null) { - return; - } - - final int defaultPaddingPx = - getResources().getDimensionPixelSize(R.dimen.lock_icon_padding); - final int padding = (int) (defaultPaddingPx * mScaleFactor); - mLockScreenFp.setPadding(padding, padding, padding, padding); - mAodFp.setPadding(padding, padding, padding, padding); - } - - /** - * @param alpha between 0 and 255 - */ - void setUnpausedAlpha(int alpha) { - mAlpha = alpha; - updateAlpha(); - } - - /** - * @return alpha between 0 and 255 - */ - int getUnpausedAlpha() { - return mAlpha; - } - - @Override - protected int updateAlpha() { - int alpha = super.updateAlpha(); - updateBurnInOffsets(); - return alpha; - } - - @Override - int calculateAlpha() { - if (mPauseAuth) { - return 0; - } - return mAlpha; - } - - static final int ANIMATION_NONE = 0; - static final int ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN = 1; - static final int ANIMATE_APPEAR_ON_SCREEN_OFF = 2; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ANIMATION_NONE, ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, ANIMATE_APPEAR_ON_SCREEN_OFF}) - private @interface AnimationType {} - - void onDozeAmountChanged(float linear, float eased, @AnimationType int animationType) { - mAnimationType = animationType; - mInterpolatedDarkAmount = eased; - updateAlpha(); - } - - void updateSensorLocation(@NonNull Rect sensorBounds) { - mSensorBounds.set(sensorBounds); - } - - /** - * Animates in the bg protection circle behind the fp icon to highlight the icon. - */ - void animateInUdfpsBouncer(Runnable onEndAnimation) { - if (mBackgroundInAnimator.isRunning() || !mFullyInflated) { - // already animating in or not yet inflated - return; - } - - // fade in and scale up - mBackgroundInAnimator = new AnimatorSet(); - mBackgroundInAnimator.playTogether( - ObjectAnimator.ofFloat(mBgProtection, View.ALPHA, 0f, 1f), - ObjectAnimator.ofFloat(mBgProtection, View.SCALE_X, 0f, 1f), - ObjectAnimator.ofFloat(mBgProtection, View.SCALE_Y, 0f, 1f)); - mBackgroundInAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - mBackgroundInAnimator.setDuration(500); - mBackgroundInAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (onEndAnimation != null) { - onEndAnimation.run(); - } - } - }); - mBackgroundInAnimator.start(); - } - - /** - * Print debugging information for this class. - */ - public void dump(PrintWriter pw) { - pw.println("UdfpsKeyguardView (" + this + ")"); - pw.println(" mPauseAuth=" + mPauseAuth); - pw.println(" mUnpausedAlpha=" + getUnpausedAlpha()); - pw.println(" mUdfpsRequested=" + mUdfpsRequested); - pw.println(" mInterpolatedDarkAmount=" + mInterpolatedDarkAmount); - pw.println(" mAnimationType=" + mAnimationType); - } - - private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener = - new AsyncLayoutInflater.OnInflateFinishedListener() { - @Override - public void onInflateFinished(View view, int resid, ViewGroup parent) { - mFullyInflated = true; - mAodFp = view.findViewById(R.id.udfps_aod_fp); - mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp); - mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg); - - updatePadding(); - updateColor(); - updateAlpha(); - - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - lp.width = mSensorBounds.width(); - lp.height = mSensorBounds.height(); - RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds)); - lp.setMarginsRelative((int) relativeToView.left, (int) relativeToView.top, - (int) relativeToView.right, (int) relativeToView.bottom); - parent.addView(view, lp); - - // requires call to invalidate to update the color - mLockScreenFp.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER, - frameInfo -> new PorterDuffColorFilter(mTextColorPrimary, - PorterDuff.Mode.SRC_ATOP)); - mOnFinishInflateRunnable.run(); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt index 7503a8b4362d..a105d663424c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt @@ -23,7 +23,6 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay import com.android.systemui.biometrics.ui.viewmodel.UdfpsTouchOverlayViewModel -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.lifecycle.repeatWhenAttached import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -42,14 +41,13 @@ object UdfpsTouchOverlayBinder { viewModel: UdfpsTouchOverlayViewModel, udfpsOverlayInteractor: UdfpsOverlayInteractor, ) { - if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { viewModel.shouldHandleTouches.collect { shouldHandleTouches -> Log.d( "UdfpsTouchOverlayBinder", - "[$view]: update shouldHandleTouches=$shouldHandleTouches" + "[$view]: update shouldHandleTouches=$shouldHandleTouches", ) view.isInvisible = !shouldHandleTouches udfpsOverlayInteractor.setHandleTouches(shouldHandleTouches) @@ -58,7 +56,7 @@ object UdfpsTouchOverlayBinder { .invokeOnCompletion { Log.d( "UdfpsTouchOverlayBinder", - "[$view-detached]: update shouldHandleTouches=false" + "[$view-detached]: update shouldHandleTouches=false", ) udfpsOverlayInteractor.setHandleTouches(false) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index cae141d014a8..5699557c14e7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -35,7 +35,6 @@ import androidx.core.view.ViewKt; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.KeyguardUnfoldTransition; -import com.android.keyguard.LockIconViewController; import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; @@ -44,7 +43,6 @@ import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags; import com.android.systemui.bouncer.ui.binder.BouncerViewBinder; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.dock.DockManager; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlagsClassic; @@ -75,7 +73,6 @@ import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeScrimController; import com.android.systemui.statusbar.phone.DozeServiceHost; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; @@ -101,9 +98,7 @@ public class NotificationShadeWindowViewController implements Dumpable { private final NotificationShadeDepthController mDepthController; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; - private final LockIconViewController mLockIconViewController; private final ShadeLogger mShadeLogger; - private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final StatusBarWindowStateController mStatusBarWindowStateController; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final AmbientState mAmbientState; @@ -174,9 +169,7 @@ public class NotificationShadeWindowViewController implements Dumpable { PanelExpansionInteractor panelExpansionInteractor, ShadeExpansionStateManager shadeExpansionStateManager, NotificationStackScrollLayoutController notificationStackScrollLayoutController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, StatusBarWindowStateController statusBarWindowStateController, - LockIconViewController lockIconViewController, CentralSurfaces centralSurfaces, DozeServiceHost dozeServiceHost, DozeScrimController dozeScrimController, @@ -210,9 +203,7 @@ public class NotificationShadeWindowViewController implements Dumpable { mShadeExpansionStateManager = shadeExpansionStateManager; mDepthController = depthController; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mStatusBarWindowStateController = statusBarWindowStateController; - mLockIconViewController = lockIconViewController; mShadeLogger = shadeLogger; mService = centralSurfaces; mDozeServiceHost = dozeServiceHost; @@ -259,7 +250,6 @@ public class NotificationShadeWindowViewController implements Dumpable { mDisableSubpixelTextTransitionListener)); } - lockIconViewController.setLockIconView(mView.findViewById(R.id.lock_icon_view)); dumpManager.registerDumpable(this); } @@ -392,9 +382,6 @@ public class NotificationShadeWindowViewController implements Dumpable { } if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { - // If the user was sliding their finger across the lock screen, - // we may have been intercepting the touch and forwarding it to the - // UDFPS affordance via mStatusBarKeyguardViewManager.onTouch (see below). // If this touch ended up unlocking the device, we want to cancel the touch // immediately, so we don't cause swipe or expand animations afterwards. cancelCurrentTouch(); @@ -419,9 +406,6 @@ public class NotificationShadeWindowViewController implements Dumpable { && mDreamingWakeupGestureHandler.onTouchEvent(ev)) { return logDownDispatch(ev, "dream wakeup gesture handled", true); } - if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) { - return logDownDispatch(ev, "dispatched to Keyguard", true); - } if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == View.VISIBLE) { // Disallow new pointers while the brightness mirror is visible. This is so that @@ -512,7 +496,6 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mStatusBarStateController.isDozing() && !mDozeServiceHost.isPulsing() && !mDockManager.isDocked() - && !mLockIconViewController.willHandleTouchWhileDozing(ev) ) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mShadeLogger.d("NSWVC: capture all touch events in always-on"); @@ -520,22 +503,8 @@ public class NotificationShadeWindowViewController implements Dumpable { return true; } - if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) { - // Don't allow touches to proceed to underlying views if alternate - // bouncer is showing - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mShadeLogger.d("NSWVC: alt bouncer showing"); - } - return true; - } - - boolean bouncerShowing; - if (DeviceEntryUdfpsRefactor.isEnabled()) { - bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing() + boolean bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing() || mAlternateBouncerInteractor.isVisibleState(); - } else { - bouncerShowing = mService.isBouncerShowing(); - } if (mPanelExpansionInteractor.isFullyExpanded() && !bouncerShowing && !mStatusBarStateController.isDozing()) { @@ -603,9 +572,6 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mStatusBarStateController.isDozing()) { handled = !mDozeServiceHost.isPulsing(); } - if (mStatusBarKeyguardViewManager.onTouch(ev)) { - return true; - } if (MigrateClocksToBlueprint.isEnabled()) { if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) { // we still want to finish our drag down gesture when locking the screen diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 1481b734ff61..1ec5357d0d30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -15,7 +15,6 @@ import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.ExpandHelper import com.android.systemui.Gefingerpoken -import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton @@ -90,6 +89,7 @@ constructor( @get:VisibleForTesting var fractionToShade: Float = 0f private set + private var useSplitShade: Boolean = false private lateinit var nsslController: NotificationStackScrollLayoutController lateinit var centralSurfaces: CentralSurfaces @@ -161,9 +161,6 @@ constructor( val distanceUntilShowingPulsingNotifications get() = fullTransitionDistance - /** The udfpsKeyguardViewController if it exists. */ - var mUdfpsKeyguardViewControllerLegacy: UdfpsKeyguardViewControllerLegacy? = null - /** The touch helper responsible for the drag down animation. */ val touchHelper = DragDownHelper( @@ -171,7 +168,7 @@ constructor( this, naturalScrollingSettingObserver, shadeRepository, - context + context, ) private val splitShadeOverScroller: SplitShadeLockScreenOverScroller by lazy { @@ -448,7 +445,6 @@ constructor( val udfpsProgress = MathUtils.saturate(dragDownAmount / udfpsTransitionDistance) shadeRepository.setUdfpsTransitionToFullShadeProgress(udfpsProgress) - mUdfpsKeyguardViewControllerLegacy?.setTransitionToFullShadeProgress(udfpsProgress) val statusBarProgress = MathUtils.saturate(dragDownAmount / statusBarTransitionDistance) centralSurfaces.setTransitionToFullShadeProgress(statusBarProgress) @@ -457,7 +453,7 @@ constructor( private fun setDragDownAmountAnimated( target: Float, delay: Long = 0, - endlistener: (() -> Unit)? = null + endlistener: (() -> Unit)? = null, ) { logger.logDragDownAnimation(target) val dragDownAnimator = ValueAnimator.ofFloat(dragDownAmount, target) @@ -553,7 +549,7 @@ constructor( private fun goToLockedShadeInternal( expandView: View?, animationHandler: ((Long) -> Unit)? = null, - cancelAction: Runnable? = null + cancelAction: Runnable? = null, ) { if (!shadeInteractor.isShadeEnabled.value) { cancelAction?.run() @@ -564,10 +560,7 @@ constructor( var entry: NotificationEntry? = null if (expandView is ExpandableNotificationRow) { entry = expandView.entry - entry.setUserExpanded( - /* userExpanded= */ true, - /* allowChildExpansion= */ true, - ) + entry.setUserExpanded(/* userExpanded= */ true, /* allowChildExpansion= */ true) // Indicate that the group expansion is changing at this time -- this way the group // and children backgrounds / divider animations will look correct. entry.setGroupExpansionChanging(true) @@ -594,9 +587,7 @@ constructor( statusBarStateController.setLeaveOpenOnKeyguardHide(false) draggedDownEntry?.apply { setUserLocked(false) - notifyHeightChanged( - /* needsAnimation= */ false, - ) + notifyHeightChanged(/* needsAnimation= */ false) draggedDownEntry = null } cancelAction?.run() @@ -614,9 +605,7 @@ constructor( // This call needs to be after updating the shade state since otherwise // the scrimstate resets too early if (animationHandler != null) { - animationHandler.invoke( - /* delay= */ 0, - ) + animationHandler.invoke(/* delay= */ 0) } else { performDefaultGoToFullShadeAnimation(0) } @@ -757,7 +746,7 @@ class DragDownHelper( private val dragDownCallback: LockscreenShadeTransitionController, private val naturalScrollingSettingObserver: NaturalScrollingSettingObserver, private val shadeRepository: ShadeRepository, - context: Context + context: Context, ) : Gefingerpoken { private var dragDownAmountOnStart = 0.0f @@ -932,7 +921,7 @@ class DragDownHelper( @VisibleForTesting fun cancelChildExpansion( child: ExpandableView, - animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS + animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS, ) { if (child.actualHeight == child.collapsedHeight) { expandCallback.setUserLockedChild(child, false) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 17bd53869ee5..74c6e72d3400 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -47,7 +47,6 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.KeyguardMessageAreaController; import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; @@ -68,7 +67,6 @@ import com.android.systemui.bouncer.util.BouncerTestUtilsKt; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.keyguard.DismissCallbackRegistry; @@ -77,9 +75,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInte import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.DismissAction; -import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.KeyguardDone; -import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.TaskbarDelegate; @@ -165,7 +161,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final DreamOverlayStateController mDreamOverlayStateController; @Nullable private final FoldAodAnimationController mFoldAodAnimationController; - KeyguardMessageAreaController mKeyguardMessageAreaController; private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; private final AlternateBouncerInteractor mAlternateBouncerInteractor; @@ -463,11 +458,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb onPanelExpansionChanged(currentState); } mNotificationContainer = notificationContainer; - if (!DeviceEntryUdfpsRefactor.isEnabled()) { - mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create( - centralSurfaces.getKeyguardMessageArea()); - } - mCentralSurfacesRegistered = true; registerListeners(); @@ -518,24 +508,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mListenForCanShowAlternateBouncer.cancel(null); } mListenForCanShowAlternateBouncer = null; - if (!DeviceEntryUdfpsRefactor.isEnabled()) { - mListenForAlternateBouncerTransitionSteps = mJavaAdapter.alwaysCollectFlow( - mKeyguardTransitionInteractor - .transition(Edge.create(KeyguardState.ALTERNATE_BOUNCER)), - this::consumeFromAlternateBouncerTransitionSteps - ); - - mListenForKeyguardAuthenticatedBiometricsHandled = mJavaAdapter.alwaysCollectFlow( - mPrimaryBouncerInteractor.getKeyguardAuthenticatedBiometricsHandled(), - this::consumeKeyguardAuthenticatedBiometricsHandled - ); - } else { - // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot. - mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow( - mAlternateBouncerInteractor.getCanShowAlternateBouncer(), - this::consumeCanShowAlternateBouncer - ); - } + // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot. + mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow( + mAlternateBouncerInteractor.getCanShowAlternateBouncer(), + this::consumeCanShowAlternateBouncer + ); if (KeyguardWmStateRefactor.isEnabled()) { // Show the keyguard views whenever we've told WM that the lockscreen is visible. @@ -792,21 +769,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return; } - if (DeviceEntryUdfpsRefactor.isEnabled()) { - if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) { - Log.d(TAG, "showBouncer:alternateBouncer.forceShow()"); - mAlternateBouncerInteractor.forceShow(); - updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); - } else { - showPrimaryBouncer(scrimmed); - } - return; - } - - if (!mAlternateBouncerInteractor.show()) { - showPrimaryBouncer(scrimmed); - } else { + if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) { + Log.d(TAG, "showBouncer:alternateBouncer.forceShow()"); + mAlternateBouncerInteractor.forceShow(); updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); + } else { + showPrimaryBouncer(scrimmed); } } @@ -921,13 +889,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardGoneCancelAction = null; } - if (DeviceEntryUdfpsRefactor.isEnabled()) { - Log.d(TAG, "dismissWithAction:alternateBouncer.forceShow()"); - mAlternateBouncerInteractor.forceShow(); - updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); - } else { - updateAlternateBouncerShowing(mAlternateBouncerInteractor.show()); - } + Log.d(TAG, "dismissWithAction:alternateBouncer.forceShow()"); + mAlternateBouncerInteractor.forceShow(); + updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); setKeyguardMessage(message, null, null); return; } @@ -1033,11 +997,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } final boolean isShowingAlternateBouncer = mAlternateBouncerInteractor.isVisibleState(); - if (mKeyguardMessageAreaController != null) { - DeviceEntryUdfpsRefactor.assertInLegacyMode(); - mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer); - mKeyguardMessageAreaController.setMessage(""); - } if (!SceneContainerFlag.isEnabled()) { mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer); } @@ -1646,12 +1605,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb /** Display security message to relevant KeyguardMessageArea. */ public void setKeyguardMessage(String message, ColorStateList colorState, BiometricSourceType biometricSourceType) { - if (mAlternateBouncerInteractor.isVisibleState()) { - if (mKeyguardMessageAreaController != null) { - DeviceEntryUdfpsRefactor.assertInLegacyMode(); - mKeyguardMessageAreaController.setMessage(message, biometricSourceType); - } - } else { + if (!mAlternateBouncerInteractor.isVisibleState()) { mPrimaryBouncerInteractor.showMessage(message, colorState); } } @@ -1778,66 +1732,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } - /** - * An opportunity for the AlternateBouncer to handle the touch instead of sending - * the touch to NPVC child views. - * @return true if the alternate bouncer should consime the touch and prevent it from - * going to its child views - */ - public boolean dispatchTouchEvent(MotionEvent event) { - if (shouldInterceptTouchEvent(event) - && !mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(event)) { - onTouch(event); - } - return shouldInterceptTouchEvent(event); - } - - /** - * Whether the touch should be intercepted by the AlternateBouncer before going to the - * notification shade's child views. - */ - public boolean shouldInterceptTouchEvent(MotionEvent event) { - if (DeviceEntryUdfpsRefactor.isEnabled()) { - return false; - } - return mAlternateBouncerInteractor.isVisibleState(); - } - - /** - * For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently - * showing. - */ - public boolean onTouch(MotionEvent event) { - if (DeviceEntryUdfpsRefactor.isEnabled()) { - return false; - } - - boolean handleTouch = shouldInterceptTouchEvent(event); - if (handleTouch) { - final boolean actionDown = event.getActionMasked() == MotionEvent.ACTION_DOWN; - final boolean actionDownThenUp = mAlternateBouncerInteractor.getReceivedDownTouch() - && event.getActionMasked() == MotionEvent.ACTION_UP; - final boolean udfpsOverlayWillForwardEventsOutsideNotificationShade = - mKeyguardUpdateManager.isUdfpsEnrolled(); - final boolean actionOutsideShouldDismissAlternateBouncer = - event.getActionMasked() == MotionEvent.ACTION_OUTSIDE - && !udfpsOverlayWillForwardEventsOutsideNotificationShade; - if (actionDown) { - mAlternateBouncerInteractor.setReceivedDownTouch(true); - } else if ((actionDownThenUp || actionOutsideShouldDismissAlternateBouncer) - && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) { - showPrimaryBouncer(true); - } - } - - // Forward NPVC touches to callbacks in case they want to respond to touches - for (KeyguardViewManagerCallback callback: mCallbacks) { - callback.onTouch(event); - } - - return handleTouch; - } - /** Update keyguard position based on a tapped X coordinate. */ public void updateKeyguardPosition(float x) { mPrimaryBouncerInteractor.setKeyguardPosition(x); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index e2a6a5508992..4baca713e19f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -16,21 +16,14 @@ package com.android.systemui.biometrics -import android.graphics.Rect import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING import android.hardware.biometrics.BiometricRequestConstants.RequestReason import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater import android.view.MotionEvent -import android.view.Surface -import android.view.Surface.Rotation import android.view.View -import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -38,7 +31,6 @@ import androidx.test.filters.SmallTest import com.android.app.viewcapture.ViewCapture import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor @@ -61,9 +53,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState -import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController @@ -86,7 +76,6 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -118,7 +107,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var dialogManager: SystemUIDialogManager @Mock private lateinit var dumpManager: DumpManager - @Mock private lateinit var transitionController: LockscreenShadeTransitionController @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock @@ -126,8 +114,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider @Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback @Mock private lateinit var udfpsController: UdfpsController - @Mock private lateinit var udfpsView: UdfpsView - @Mock private lateinit var mUdfpsKeyguardViewLegacy: UdfpsKeyguardViewLegacy @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @@ -147,7 +133,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { private lateinit var powerInteractor: PowerInteractor private lateinit var testScope: TestScope - private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } + private val onTouch = { _: View, _: MotionEvent -> true } private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams() private lateinit var controllerOverlay: UdfpsControllerOverlay @@ -158,53 +144,37 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { powerInteractor = kosmos.powerInteractor keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor - whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView) - whenever(inflater.inflate(R.layout.udfps_bp_view, null)) - .thenReturn(mock(UdfpsBpView::class.java)) - whenever(inflater.inflate(R.layout.udfps_keyguard_view_legacy, null)) - .thenReturn(mUdfpsKeyguardViewLegacy) - whenever(inflater.inflate(R.layout.udfps_fpm_empty_view, null)) - .thenReturn(mock(UdfpsFpmEmptyView::class.java)) } private suspend fun withReasonSuspend( @RequestReason reason: Int, isDebuggable: Boolean = false, - enableDeviceEntryUdfpsRefactor: Boolean = false, block: suspend () -> Unit, ) { - withReason( - reason, - isDebuggable, - enableDeviceEntryUdfpsRefactor, - ) + withReason(reason, isDebuggable) block() } private fun withReason( @RequestReason reason: Int, isDebuggable: Boolean = false, - enableDeviceEntryUdfpsRefactor: Boolean = false, block: () -> Unit = {}, ) { - if (enableDeviceEntryUdfpsRefactor) { - mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - } else { - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - } controllerOverlay = UdfpsControllerOverlay( context, inflater, - ViewCaptureAwareWindowManager(windowManager, lazyViewCapture, - isViewCaptureEnabled = false), + ViewCaptureAwareWindowManager( + windowManager, + lazyViewCapture, + isViewCaptureEnabled = false, + ), accessibilityManager, statusBarStateController, statusBarKeyguardViewManager, keyguardUpdateMonitor, dialogManager, dumpManager, - transitionController, configurationController, keyguardStateController, unlockedScreenOffAnimationController, @@ -230,117 +200,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { block() } - @Test fun showUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { showUdfpsOverlay() } - - @Test - fun showUdfpsOverlay_keyguard() = - withReason(REASON_AUTH_KEYGUARD) { - showUdfpsOverlay() - verify(mUdfpsKeyguardViewLegacy).updateSensorLocation(eq(overlayParams.sensorBounds)) - } - - @Test fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() } - - private fun withRotation(@Rotation rotation: Int, block: () -> Unit) { - // Sensor that's in the top left corner of the display in natural orientation. - val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT) - val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT) - overlayParams = - UdfpsOverlayParams( - sensorBounds, - overlayBounds, - DISPLAY_WIDTH, - DISPLAY_HEIGHT, - scaleFactor = 1f, - rotation - ) - block() - } - - @Test - fun showUdfpsOverlay_withRotation0() = - withRotation(Surface.ROTATION_0) { - withReason(REASON_AUTH_BP) { - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - - // ROTATION_0 is the native orientation. Sensor should stay in the top left corner. - val lp = layoutParamsCaptor.value - assertThat(lp.x).isEqualTo(0) - assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(DISPLAY_WIDTH) - assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT) - } - } - - @Test - fun showUdfpsOverlay_withRotation180() = - withRotation(Surface.ROTATION_180) { - withReason(REASON_AUTH_BP) { - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - - // ROTATION_180 is not supported. Sensor should stay in the top left corner. - val lp = layoutParamsCaptor.value - assertThat(lp.x).isEqualTo(0) - assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(DISPLAY_WIDTH) - assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT) - } - } - - @Test - fun showUdfpsOverlay_withRotation90() = - withRotation(Surface.ROTATION_90) { - withReason(REASON_AUTH_BP) { - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - - // Sensor should be in the bottom left corner in ROTATION_90. - val lp = layoutParamsCaptor.value - assertThat(lp.x).isEqualTo(0) - assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT) - assertThat(lp.height).isEqualTo(DISPLAY_WIDTH) - } - } - - @Test - fun showUdfpsOverlay_withRotation270() = - withRotation(Surface.ROTATION_270) { - withReason(REASON_AUTH_BP) { - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - - // Sensor should be in the top right corner in ROTATION_270. - val lp = layoutParamsCaptor.value - assertThat(lp.x).isEqualTo(0) - assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT) - assertThat(lp.height).isEqualTo(DISPLAY_WIDTH) - } - } - - @Test - fun showUdfpsOverlay_awake() = - testScope.runTest { - withReason(REASON_AUTH_KEYGUARD) { - powerRepository.updateWakefulness( - rawState = WakefulnessState.AWAKE, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - runCurrent() - controllerOverlay.show(udfpsController, overlayParams) - runCurrent() - verify(windowManager).addView(any(), any()) - } - } - @Test fun showUdfpsOverlay_whileGoingToSleep() = testScope.runTest { @@ -411,91 +270,9 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } } - @Test - fun showUdfpsOverlay_afterFinishedTransitioningToAOD() = - testScope.runTest { - withReasonSuspend(REASON_AUTH_KEYGUARD) { - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.OFF, - to = KeyguardState.GONE, - testScope = this, - ) - powerRepository.updateWakefulness( - rawState = WakefulnessState.STARTING_TO_SLEEP, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - runCurrent() - - // WHEN a request comes to show the view - controllerOverlay.show(udfpsController, overlayParams) - runCurrent() - - // THEN the view does not get added immediately - verify(windowManager, never()).addView(any(), any()) - - // WHEN the device finishes transitioning to AOD - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - testScope = this, - ) - runCurrent() - - // THEN the view gets added - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - } - } - - private fun showUdfpsOverlay() { - val didShow = controllerOverlay.show(udfpsController, overlayParams) - - verify(windowManager).addView(eq(controllerOverlay.getTouchOverlay()), any()) - verify(udfpsView).setUdfpsDisplayModeProvider(eq(udfpsDisplayMode)) - verify(udfpsView).animationViewController = any() - verify(udfpsView).addView(any()) - - assertThat(didShow).isTrue() - assertThat(controllerOverlay.isShowing).isTrue() - assertThat(controllerOverlay.isHiding).isFalse() - assertThat(controllerOverlay.getTouchOverlay()).isNotNull() - } - - @Test fun hideUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { hideUdfpsOverlay() } - - @Test fun hideUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) { hideUdfpsOverlay() } - - @Test fun hideUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { hideUdfpsOverlay() } - - @Test fun hideUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { hideUdfpsOverlay() } - - private fun hideUdfpsOverlay() { - val didShow = controllerOverlay.show(udfpsController, overlayParams) - val view = controllerOverlay.getTouchOverlay() - view?.let { whenever(view.parent).thenReturn(mock(ViewGroup::class.java)) } - val didHide = controllerOverlay.hide() - - verify(windowManager).removeView(eq(view)) - - assertThat(didShow).isTrue() - assertThat(didHide).isTrue() - assertThat(controllerOverlay.getTouchOverlay()).isNull() - assertThat(controllerOverlay.animationViewController).isNull() - assertThat(controllerOverlay.isShowing).isFalse() - assertThat(controllerOverlay.isHiding).isTrue() - } - @Test fun canNotHide() = withReason(REASON_AUTH_BP) { assertThat(controllerOverlay.hide()).isFalse() } - @Test - fun canNotReshow() = - withReason(REASON_AUTH_BP) { - assertThat(controllerOverlay.show(udfpsController, overlayParams)).isTrue() - assertThat(controllerOverlay.show(udfpsController, overlayParams)).isFalse() - } - @Test fun cancels() = withReason(REASON_AUTH_BP) { @@ -503,16 +280,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { verify(controllerCallback).onUserCanceled() } - @Test - fun unconfigureDisplayOnHide() = - withReason(REASON_AUTH_BP) { - whenever(udfpsView.isDisplayConfigured).thenReturn(true) - - controllerOverlay.show(udfpsController, overlayParams) - controllerOverlay.hide() - verify(udfpsView).unconfigureDisplay() - } - @Test fun matchesRequestIds() = withReason(REASON_AUTH_BP) { @@ -520,30 +287,10 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse() } - @Test - fun smallOverlayOnEnrollmentWithA11y() = - withRotation(Surface.ROTATION_0) { - withReason(REASON_ENROLL_ENROLLING) { - // When a11y enabled during enrollment - whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true) - - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - - // Layout params should use sensor bounds - val lp = layoutParamsCaptor.value - assertThat(lp.width).isEqualTo(overlayParams.sensorBounds.width()) - assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height()) - } - } - @Test fun addViewPending_layoutIsNotUpdated() = testScope.runTest { withReasonSuspend(REASON_AUTH_KEYGUARD) { - mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - // GIVEN going to sleep keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OFF, @@ -574,26 +321,4 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { controllerOverlay.hide() } } - - @Test - fun updateOverlayParams_viewLayoutUpdated() = - testScope.runTest { - withReasonSuspend(REASON_AUTH_KEYGUARD) { - powerRepository.updateWakefulness( - rawState = WakefulnessState.AWAKE, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - runCurrent() - controllerOverlay.show(udfpsController, overlayParams) - runCurrent() - verify(windowManager).addView(any(), any()) - - // WHEN updateOverlayParams gets called - controllerOverlay.updateOverlayParams(overlayParams) - - // THEN the view layout is updated - verify(windowManager).updateViewLayout(any(), any()) - } - } } -- GitLab From 7c243dd628f237e9a5f8525b5050978f2e099912 Mon Sep 17 00:00:00 2001 From: Rupesh Bansal Date: Fri, 27 Sep 2024 11:25:24 +0000 Subject: [PATCH 156/441] Dont select automatic strategy when stylus is in use To stop the brightness from changing when stylus is being used, we will not select the automatic brightness strategy. This way, the selector will falkback to the next valid strategy(FallbackBrightnessStrategy) which will select the current brightness Bug: 352411468 Test: atest com.android.server.display Flag: com.android.server.display.feature.flags.block_autobrightness_changes_on_stylus_usage Change-Id: I3014ea0b1e6b71378a1cdfd402b4b37df70f5a20 --- .../display/brightness/BrightnessReason.java | 7 +- .../DisplayBrightnessController.java | 17 ++++- .../DisplayBrightnessStrategySelector.java | 3 +- .../brightness/StrategyExecutionRequest.java | 15 +++- .../brightness/StrategySelectionRequest.java | 16 +++- .../strategy/FallbackBrightnessStrategy.java | 3 + .../display/DisplayPowerControllerTest.java | 36 +++++++++ .../brightness/BrightnessReasonTest.java | 2 +- .../DisplayBrightnessControllerTest.java | 9 ++- ...DisplayBrightnessStrategySelectorTest.java | 76 +++++++++++++++---- .../AutoBrightnessFallbackStrategyTest.java | 3 +- .../AutomaticBrightnessStrategyTest.java | 8 +- .../strategy/BoostBrightnessStrategyTest.java | 3 +- .../strategy/DozeBrightnessStrategyTest.java | 3 +- .../FallbackBrightnessStrategyTest.java | 3 +- .../FollowerBrightnessStrategyTest.java | 3 +- .../OffloadBrightnessStrategyTest.java | 3 +- .../OverrideBrightnessStrategyTest.java | 3 +- .../ScreenOffBrightnessStrategyTest.java | 3 +- .../TemporaryBrightnessStrategyTest.java | 3 +- 20 files changed, 177 insertions(+), 42 deletions(-) diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java index 9a0ee034a8f2..a4804e1887fe 100644 --- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java +++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java @@ -50,8 +50,10 @@ public final class BrightnessReason { public static final int MODIFIER_THROTTLED = 0x8; public static final int MODIFIER_MIN_LUX = 0x10; public static final int MODIFIER_MIN_USER_SET_LOWER_BOUND = 0x20; + public static final int MODIFIER_STYLUS_UNDER_USE = 0x40; public static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR - | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND; + | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND + | MODIFIER_STYLUS_UNDER_USE; // ADJUSTMENT_* // These things can happen at any point, even if the main brightness reason doesn't @@ -158,6 +160,9 @@ public final class BrightnessReason { if ((mModifier & MODIFIER_MIN_USER_SET_LOWER_BOUND) != 0) { sb.append(" user_min_pref"); } + if ((mModifier & MODIFIER_STYLUS_UNDER_USE) != 0) { + sb.append(" stylus_under_use"); + } int strlen = sb.length(); if (sb.charAt(strlen - 1) == '[') { sb.setLength(strlen - 2); diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index 71fdaf3f85b6..4bd980822e46 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -110,6 +110,9 @@ public final class DisplayBrightnessController { @VisibleForTesting AutomaticBrightnessController mAutomaticBrightnessController; + // True if the stylus is being used + private boolean mIsStylusBeingUsed; + /** * The constructor of DisplayBrightnessController. */ @@ -460,6 +463,8 @@ public final class DisplayBrightnessController { writer.println(" mScreenBrightnessDefault=" + mScreenBrightnessDefault); writer.println(" mPersistBrightnessNitsForDefaultDisplay=" + mPersistBrightnessNitsForDefaultDisplay); + writer.println(" mIsStylusBeingUsed=" + + mIsStylusBeingUsed); synchronized (mLock) { writer.println(" mPendingScreenBrightness=" + mPendingScreenBrightness); writer.println(" mCurrentScreenBrightness=" + mCurrentScreenBrightness); @@ -505,7 +510,12 @@ public final class DisplayBrightnessController { * Notifies if the stylus is currently being used or not. */ public void setStylusBeingUsed(boolean isEnabled) { - // Todo(b/369977976) - Disable the auto-brightness strategy + mIsStylusBeingUsed = isEnabled; + } + + @VisibleForTesting + boolean isStylusBeingUsed() { + return mIsStylusBeingUsed; } @VisibleForTesting @@ -626,13 +636,14 @@ public final class DisplayBrightnessController { lastUserSetScreenBrightness = mLastUserSetScreenBrightness; } return new StrategySelectionRequest(displayPowerRequest, targetDisplayState, - lastUserSetScreenBrightness, userSetBrightnessChanged, displayOffloadSession); + lastUserSetScreenBrightness, userSetBrightnessChanged, displayOffloadSession, + mIsStylusBeingUsed); } private StrategyExecutionRequest constructStrategyExecutionRequest( DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) { float currentScreenBrightness = getCurrentBrightness(); return new StrategyExecutionRequest(displayPowerRequest, currentScreenBrightness, - mUserSetScreenBrightnessUpdated); + mUserSetScreenBrightnessUpdated, mIsStylusBeingUsed); } } diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index 06890e72f068..ded7447c5fbc 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -306,7 +306,8 @@ public class DisplayBrightnessStrategySelector { strategySelectionRequest.getDisplayPowerRequest().useNormalBrightnessForDoze, strategySelectionRequest.getLastUserSetScreenBrightness(), strategySelectionRequest.isUserSetBrightnessChanged()); - return mAutomaticBrightnessStrategy1.isAutoBrightnessValid(); + return !strategySelectionRequest.isStylusBeingUsed() + && mAutomaticBrightnessStrategy1.isAutoBrightnessValid(); } private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest( diff --git a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java index 304640b884ef..7a07c4fc22bf 100644 --- a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java +++ b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java @@ -32,11 +32,15 @@ public final class StrategyExecutionRequest { // Represents if the user set screen brightness was changed or not. private boolean mUserSetBrightnessChanged; + private boolean mIsStylusBeingUsed; + public StrategyExecutionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, - float currentScreenBrightness, boolean userSetBrightnessChanged) { + float currentScreenBrightness, boolean userSetBrightnessChanged, + boolean isStylusBeingUsed) { mDisplayPowerRequest = displayPowerRequest; mCurrentScreenBrightness = currentScreenBrightness; mUserSetBrightnessChanged = userSetBrightnessChanged; + mIsStylusBeingUsed = isStylusBeingUsed; } public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() { @@ -51,6 +55,10 @@ public final class StrategyExecutionRequest { return mUserSetBrightnessChanged; } + public boolean isStylusBeingUsed() { + return mIsStylusBeingUsed; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof StrategyExecutionRequest)) { @@ -59,12 +67,13 @@ public final class StrategyExecutionRequest { StrategyExecutionRequest other = (StrategyExecutionRequest) obj; return Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest()) && mCurrentScreenBrightness == other.getCurrentScreenBrightness() - && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged(); + && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged() + && mIsStylusBeingUsed == other.isStylusBeingUsed(); } @Override public int hashCode() { return Objects.hash(mDisplayPowerRequest, mCurrentScreenBrightness, - mUserSetBrightnessChanged); + mUserSetBrightnessChanged, mIsStylusBeingUsed); } } diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java index aa2f23ef9ec1..5c1f03d877a6 100644 --- a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java +++ b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java @@ -40,15 +40,19 @@ public final class StrategySelectionRequest { private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; + private boolean mIsStylusBeingUsed; + public StrategySelectionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int targetDisplayState, float lastUserSetScreenBrightness, boolean userSetBrightnessChanged, - DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) { + DisplayManagerInternal.DisplayOffloadSession displayOffloadSession, + boolean isStylusBeingUsed) { mDisplayPowerRequest = displayPowerRequest; mTargetDisplayState = targetDisplayState; mLastUserSetScreenBrightness = lastUserSetScreenBrightness; mUserSetBrightnessChanged = userSetBrightnessChanged; mDisplayOffloadSession = displayOffloadSession; + mIsStylusBeingUsed = isStylusBeingUsed; } public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() { @@ -72,6 +76,10 @@ public final class StrategySelectionRequest { return mDisplayOffloadSession; } + public boolean isStylusBeingUsed() { + return mIsStylusBeingUsed; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof StrategySelectionRequest)) { @@ -82,12 +90,14 @@ public final class StrategySelectionRequest { && mTargetDisplayState == other.getTargetDisplayState() && mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness() && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged() - && mDisplayOffloadSession.equals(other.getDisplayOffloadSession()); + && mDisplayOffloadSession.equals(other.getDisplayOffloadSession()) + && mIsStylusBeingUsed == other.isStylusBeingUsed(); } @Override public int hashCode() { return Objects.hash(mDisplayPowerRequest, mTargetDisplayState, - mLastUserSetScreenBrightness, mUserSetBrightnessChanged, mDisplayOffloadSession); + mLastUserSetScreenBrightness, mUserSetBrightnessChanged, mDisplayOffloadSession, + mIsStylusBeingUsed); } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java index 7c0c9312888e..b9de34a80bda 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java @@ -37,6 +37,9 @@ public class FallbackBrightnessStrategy implements DisplayBrightnessStrategy{ StrategyExecutionRequest strategyExecutionRequest) { BrightnessReason brightnessReason = new BrightnessReason(); brightnessReason.setReason(BrightnessReason.REASON_MANUAL); + if (strategyExecutionRequest.isStylusBeingUsed()) { + brightnessReason.setModifier(BrightnessReason.MODIFIER_STYLUS_UNDER_USE); + } return new DisplayBrightnessState.Builder() .setBrightness(strategyExecutionRequest.getCurrentScreenBrightness()) .setBrightnessReason(brightnessReason) diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index c70bf8abaef6..ac65e59fa9e7 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -2319,6 +2319,41 @@ public final class DisplayPowerControllerTest { } } + @Test + public void stylusUsageStarted_disablesAutomaticBrightnessStrategy() { + when(mDisplayManagerFlagsMock.isBlockAutobrightnessChangesOnStylusUsage()) + .thenReturn(true); + when(mDisplayManagerFlagsMock.isRefactorDisplayPowerControllerEnabled()) + .thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_BRIGHT; + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + advanceTime(2); + clearInvocations(mHolder.automaticBrightnessController); + mHolder.dpc.stylusGestureStarted(2000000); + + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + verify(mHolder.automaticBrightnessController, times(0)) + .getAutomaticScreenBrightness(any()); + + // Stylus usage timed out, hence autobrightness is now enabled back again + advanceTime(6); + verify(mHolder.automaticBrightnessController).getAutomaticScreenBrightness(null); + + // Ideally we should be able to assert against new BrightnessEvent(Display.DEFAULT_DISPLAY), + // but because brightnessEvent has the mTime field which refers to the current time, + // asserting against that is non-trivial + verify(mHolder.automaticBrightnessController).getAutomaticScreenBrightness( + any(BrightnessEvent.class)); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -2383,6 +2418,7 @@ public final class DisplayPowerControllerTest { .thenReturn(new int[0]); when(displayDeviceConfigMock.getDefaultDozeBrightness()) .thenReturn(DEFAULT_DOZE_BRIGHTNESS); + when(displayDeviceConfigMock.getIdleStylusTimeoutMillis()).thenReturn(5); when(displayDeviceConfigMock.getBrightnessRampFastDecrease()) .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java index 04b79b4f1761..d93ee84f4870 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java @@ -74,7 +74,7 @@ public final class BrightnessReasonTest { @Test public void setModifierDoesntSetIfModifierIsBeyondExtremes() { - int extremeModifier = 0x40; // equal to BrightnessReason.MODIFIER_MASK * 2 + int extremeModifier = 0x80; // reset modifier mBrightnessReason.setModifier(0); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index c0698756a3d7..b3baa5deb4a7 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -125,7 +125,7 @@ public final class DisplayBrightnessControllerTest { DisplayManagerInternal.DisplayOffloadSession.class)); verify(displayBrightnessStrategy).updateBrightness( eq(new StrategyExecutionRequest(displayPowerRequest, DEFAULT_BRIGHTNESS, - /* userSetBrightnessChanged= */ false))); + /* userSetBrightnessChanged= */ false, /* isStylusBeingUsed */ false))); assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(), displayBrightnessStrategy); } @@ -559,4 +559,11 @@ public final class DisplayBrightnessControllerTest { displayDeviceConfig, handler, brightnessMappingStrategy, isDisplayEnabled, leadDisplayId); } + + @Test + public void setStylusBeingUsed_setsStylusInUseState() { + assertFalse(mDisplayBrightnessController.isStylusBeingUsed()); + mDisplayBrightnessController.setStylusBeingUsed(true); + assertTrue(mDisplayBrightnessController.isStylusBeingUsed()); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index a44c517ed9cf..fe1505162e24 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -68,6 +68,8 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public final class DisplayBrightnessStrategySelectorTest { private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false; + private static final boolean STYLUS_IS_NOT_BEING_USED = false; + private static final boolean STYLUS_IS_BEING_USED = true; private static final int DISPLAY_ID = 1; @Mock @@ -196,7 +198,8 @@ public final class DisplayBrightnessStrategySelectorTest { DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -212,7 +215,8 @@ public final class DisplayBrightnessStrategySelectorTest { DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -226,7 +230,8 @@ public final class DisplayBrightnessStrategySelectorTest { DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -249,7 +254,8 @@ public final class DisplayBrightnessStrategySelectorTest { assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -259,7 +265,8 @@ public final class DisplayBrightnessStrategySelectorTest { DisplayManagerInternal.DisplayPowerRequest.class); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_OFF, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mScreenOffBrightnessModeStrategy); } @@ -271,7 +278,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mOverrideBrightnessStrategy); } @@ -284,7 +292,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mTemporaryBrightnessStrategy); } @@ -298,7 +307,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mBoostBrightnessStrategy); } @@ -312,7 +322,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mInvalidBrightnessStrategy); } @@ -323,7 +334,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mFollowerBrightnessStrategy); } @@ -341,7 +353,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mOffloadBrightnessStrategy); } @@ -365,7 +378,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(true); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mAutomaticBrightnessStrategy); verify(mAutomaticBrightnessStrategy).setAutoBrightnessState(Display.STATE_ON, true, BrightnessReason.REASON_UNKNOWN, @@ -373,6 +387,32 @@ public final class DisplayBrightnessStrategySelectorTest { /* useNormalBrightnessForDoze= */ false, 0.1f, false); } + + @Test + public void selectStrategy_doesNotSelectAutomaticStrategyWhenStylusInUse() { + when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); + when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); + when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); + when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); + when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( + true); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true); + when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(true); + assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( + new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_BEING_USED)), + mAutomaticBrightnessStrategy); + } + @Test public void selectStrategy_selectsAutomaticFallbackStrategyWhenValid() { when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); @@ -389,7 +429,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mAutoBrightnessFallbackStrategy.isValid()).thenReturn(true); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mAutoBrightnessFallbackStrategy); } @@ -407,7 +448,8 @@ public final class DisplayBrightnessStrategySelectorTest { assertNotEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession))); + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED))); } @Test @@ -425,7 +467,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(false); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mFallbackBrightnessStrategy); } @@ -440,7 +483,8 @@ public final class DisplayBrightnessStrategySelectorTest { mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)); + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)); StrategySelectionNotifyRequest strategySelectionNotifyRequest = new StrategySelectionNotifyRequest(displayPowerRequest, Display.STATE_ON, diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java index 99dfa739fb80..2a71af06e0c2 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java @@ -129,7 +129,8 @@ public class AutoBrightnessFallbackStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mAutoBrightnessFallbackStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index efa8b3ef775f..8a1f86093ecf 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -637,7 +637,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } @@ -686,7 +686,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } @@ -725,7 +725,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } @@ -764,7 +764,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java index 275bb3efee8e..c03309e8b4a4 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java @@ -60,7 +60,8 @@ public class BoostBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mBoostBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java index 23e447c25245..e7f80b04e669 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java @@ -57,7 +57,8 @@ public class DozeBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mDozeBrightnessModeStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java index c4a579092d38..dcfa174a53f5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java @@ -61,7 +61,8 @@ public class FallbackBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mFallbackBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, currentBrightness, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java index c01f96e800de..239cdb6002e9 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java @@ -61,7 +61,8 @@ public class FollowerBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mFollowerBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, updatedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java index 9fb2afa26ed2..77302f8747c1 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java @@ -72,7 +72,8 @@ public class OffloadBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mOffloadBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT, mOffloadBrightnessStrategy .getOffloadScreenBrightness(), 0.0f); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java index e8b4c06b9c89..cc21af19722b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java @@ -60,7 +60,8 @@ public class OverrideBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mOverrideBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java index 38709ece7007..652663e52a0a 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java @@ -58,7 +58,8 @@ public final class ScreenOffBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mScreenOffBrightnessModeStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java index f523b6af426b..0022cab371e8 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java @@ -60,7 +60,8 @@ public class TemporaryBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mTemporaryBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } -- GitLab From aa6cf998e07d3914ca7808fcb2c52d692bf84f44 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Thu, 3 Oct 2024 16:55:12 +0200 Subject: [PATCH 157/441] Notif redesign: Use launcher app icons for notification rows This is just the simplest implementation for fetching the icon properly. AppIconProvider will have a cache, and we'll also need to not show app icons for certain notifications, but that will be in follow-up CLs. I also didn't add tests yet since the actual implementation in AppIconProvider will change soon. One KI I noticed is this doesn't seem to work for group headers yet. Bug: 371174789 Test: manually post notifications and see that they have the right icon Flag: android.app.notifications_redesign_app_icons Change-Id: Iad9f68877c6c3bc356505d30036ba4dc1166847e --- core/java/android/app/notification.aconfig | 8 + .../widget/NotificationRowIconView.java | 224 ++++++++++++++++-- .../row/NotifRemoteViewsFactoryContainer.kt | 5 + .../row/NotificationRowModule.java | 3 +- .../notification/row/icon/AppIconProvider.kt | 81 +++++++ .../NotificationRowIconViewInflaterFactory.kt | 64 +++++ .../row/ExpandableNotificationRowBuilder.kt | 33 +-- .../row/icon/AppIconProviderKosmos.kt | 22 ++ 8 files changed, 398 insertions(+), 42 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index b13901721909..48e66c1cfaa5 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -5,6 +5,14 @@ container: "system" # when appropriate, as it's not currently part of the namespace so it may not be obvious what the # flag relates to. +flag { + name: "notifications_redesign_app_icons" + namespace: "systemui" + description: "Notifications Redesign: Use app icons in notification rows (not to be confused with" + " notifications_use_app_icons, notifications_use_app_icon_in_row which are just experiments)." + bug: "371174789" +} + flag { name: "modes_api" is_exported: true diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index 98e6e8505534..adcc0f64b598 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -39,17 +39,24 @@ import com.android.internal.R; /** * An image view that holds the icon displayed at the start of a notification row. + * This can generally either display the "small icon" of a notification set via + * {@link this#setImageIcon(Icon)}, or an app icon controlled and fetched by the provider set + * through {@link this#setIconProvider(NotificationIconProvider)}. */ @RemoteViews.RemoteView public class NotificationRowIconView extends CachingIconView { + private NotificationIconProvider mIconProvider; + private boolean mApplyCircularCrop = false; private boolean mShouldShowAppIcon = false; + private Drawable mAppIcon = null; - // Padding and background set on the view prior to being changed by setShouldShowAppIcon(true), - // to be restored if shouldShowAppIcon becomes false again. + // Padding, background and colors set on the view prior to being overridden when showing the app + // icon, to be restored if we're showing the small icon again. private Rect mOriginalPadding = null; private Drawable mOriginalBackground = null; - + private int mOriginalBackgroundColor = ColoredIconHelper.COLOR_INVALID; + private int mOriginalIconColor = ColoredIconHelper.COLOR_INVALID; public NotificationRowIconView(Context context) { super(context); @@ -81,6 +88,71 @@ public class NotificationRowIconView extends CachingIconView { super.onFinishInflate(); } + /** + * Sets the icon provider for this view. This is used to determine whether we should show the + * app icon instead of the small icon, and to fetch the app icon if needed. + */ + public void setIconProvider(NotificationIconProvider iconProvider) { + mIconProvider = iconProvider; + } + + private Drawable loadAppIcon() { + if (mIconProvider != null && mIconProvider.shouldShowAppIcon()) { + return mIconProvider.getAppIcon(); + } + return null; + } + + @RemotableViewMethod(asyncImpl = "setImageIconAsync") + @Override + public void setImageIcon(Icon icon) { + if (Flags.notificationsRedesignAppIcons()) { + if (mAppIcon != null) { + // We already know that we should be using the app icon, and we already loaded it. + // We assume that cannot change throughout the lifetime of a notification, so + // there's nothing to do here. + return; + } + mAppIcon = loadAppIcon(); + if (mAppIcon != null) { + setImageDrawable(mAppIcon); + adjustViewForAppIcon(); + } else { + super.setImageIcon(icon); + restoreViewForSmallIcon(); + } + return; + } + super.setImageIcon(icon); + } + + @RemotableViewMethod + @Override + public Runnable setImageIconAsync(Icon icon) { + if (Flags.notificationsRedesignAppIcons()) { + if (mAppIcon != null) { + // We already know that we should be using the app icon, and we already loaded it. + // We assume that cannot change throughout the lifetime of a notification, so + // there's nothing to do here. + return () -> { + }; + } + mAppIcon = loadAppIcon(); + if (mAppIcon != null) { + return () -> { + setImageDrawable(mAppIcon); + adjustViewForAppIcon(); + }; + } else { + return () -> { + super.setImageIcon(icon); + restoreViewForSmallIcon(); + }; + } + } + return super.setImageIconAsync(icon); + } + /** Whether the icon represents the app icon (instead of the small icon). */ @RemotableViewMethod public void setShouldShowAppIcon(boolean shouldShowAppIcon) { @@ -91,35 +163,122 @@ public class NotificationRowIconView extends CachingIconView { mShouldShowAppIcon = shouldShowAppIcon; if (mShouldShowAppIcon) { - if (mOriginalPadding == null && mOriginalBackground == null) { - mOriginalPadding = new Rect(getPaddingLeft(), getPaddingTop(), - getPaddingRight(), getPaddingBottom()); - mOriginalBackground = getBackground(); - } - - setPadding(0, 0, 0, 0); - - // Make the background white in case the icon itself doesn't have one. - ColorFilter colorFilter = new PorterDuffColorFilter(Color.WHITE, - PorterDuff.Mode.SRC_ATOP); - - if (mOriginalBackground == null) { - setBackground(getContext().getDrawable(R.drawable.notification_icon_circle)); - } - getBackground().mutate().setColorFilter(colorFilter); + adjustViewForAppIcon(); } else { // Restore original padding and background if needed - if (mOriginalPadding != null) { - setPadding(mOriginalPadding.left, mOriginalPadding.top, mOriginalPadding.right, - mOriginalPadding.bottom); - mOriginalPadding = null; - } - setBackground(mOriginalBackground); - mOriginalBackground = null; + restoreViewForSmallIcon(); } } } + /** + * Override padding and background from the view to display the app icon. + */ + private void adjustViewForAppIcon() { + removePadding(); + + if (Flags.notificationsUseAppIconInRow()) { + addWhiteBackground(); + } else { + // No need to set the background for notification redesign, since the icon + // factory already does that for us. + removeBackground(); + } + } + + /** + * Restore padding and background overridden by {@link this#adjustViewForAppIcon}. + * Does nothing if they were not overridden. + */ + private void restoreViewForSmallIcon() { + restorePadding(); + restoreBackground(); + restoreColors(); + } + + private void removePadding() { + if (mOriginalPadding == null) { + mOriginalPadding = new Rect(getPaddingLeft(), getPaddingTop(), + getPaddingRight(), getPaddingBottom()); + } + setPadding(0, 0, 0, 0); + } + + private void restorePadding() { + if (mOriginalPadding != null) { + setPadding(mOriginalPadding.left, mOriginalPadding.top, + mOriginalPadding.right, + mOriginalPadding.bottom); + mOriginalPadding = null; + } + } + + private void removeBackground() { + if (mOriginalBackground == null) { + mOriginalBackground = getBackground(); + } + + setBackground(null); + } + + private void addWhiteBackground() { + if (mOriginalBackground == null) { + mOriginalBackground = getBackground(); + } + + // Make the background white in case the icon itself doesn't have one. + ColorFilter colorFilter = new PorterDuffColorFilter(Color.WHITE, + PorterDuff.Mode.SRC_ATOP); + + if (mOriginalBackground == null) { + setBackground(getContext().getDrawable(R.drawable.notification_icon_circle)); + } + getBackground().mutate().setColorFilter(colorFilter); + } + + private void restoreBackground() { + // NOTE: This will not work if the original background was null, but that's better than + // accidentally clearing the background. We expect that there's generally going to be one + // anyway unless we manually clear it. + if (mOriginalBackground != null) { + setBackground(mOriginalBackground); + mOriginalBackground = null; + } + } + + private void restoreColors() { + if (mOriginalBackgroundColor != ColoredIconHelper.COLOR_INVALID) { + super.setBackgroundColor(mOriginalBackgroundColor); + mOriginalBackgroundColor = ColoredIconHelper.COLOR_INVALID; + } + if (mOriginalIconColor != ColoredIconHelper.COLOR_INVALID) { + super.setOriginalIconColor(mOriginalIconColor); + mOriginalIconColor = ColoredIconHelper.COLOR_INVALID; + } + } + + @RemotableViewMethod + @Override + public void setBackgroundColor(int color) { + // Ignore color overrides if we're showing the app icon. + if (mAppIcon == null) { + super.setBackgroundColor(color); + } else { + mOriginalBackgroundColor = color; + } + } + + @RemotableViewMethod + @Override + public void setOriginalIconColor(int color) { + // Ignore color overrides if we're showing the app icon. + if (mAppIcon == null) { + super.setOriginalIconColor(color); + } else { + mOriginalIconColor = color; + } + } + @Nullable @Override Drawable loadSizeRestrictedIcon(@Nullable Icon icon) { @@ -197,4 +356,17 @@ public class NotificationRowIconView extends CachingIconView { return bitmap; } + + /** + * A provider that allows this view to verify whether it should use the app icon instead of the + * icon provided to it via setImageIcon, as well as actually fetching the app icon. It should + * primarily be called on the background thread. + */ + public interface NotificationIconProvider { + /** Whether this notification should use the app icon instead of the small icon. */ + boolean shouldShowAppIcon(); + + /** Get the app icon for this notification. */ + Drawable getAppIcon(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt index b90aa107d617..71f9c07ba2d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row import android.widget.flags.Flags.notifLinearlayoutOptimized import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.notification.row.icon.NotificationRowIconViewInflaterFactory import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing import javax.inject.Inject import javax.inject.Provider @@ -35,6 +36,7 @@ constructor( bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory, optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory, notificationViewFlipperFactory: Provider, + notificationRowIconViewInflaterFactory: NotificationRowIconViewInflaterFactory, ) : NotifRemoteViewsFactoryContainer { override val factories: Set = buildSet { add(precomputedTextViewFactory) @@ -47,5 +49,8 @@ constructor( if (NotificationViewFlipperPausing.isEnabled) { add(notificationViewFlipperFactory.get()) } + if (android.app.Flags.notificationsRedesignAppIcons()) { + add(notificationRowIconViewInflaterFactory) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java index 84f2f6670839..43ade5cc5a7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.row; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.notification.row.icon.AppIconProviderModule; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; import dagger.Binds; @@ -28,7 +29,7 @@ import javax.inject.Provider; /** * Dagger Module containing notification row and view inflation implementations. */ -@Module +@Module(includes = {AppIconProviderModule.class}) public abstract class NotificationRowModule { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt new file mode 100644 index 000000000000..13779886e321 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 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.row.icon + +import android.app.Flags +import android.content.Context +import android.content.pm.PackageManager.NameNotFoundException +import android.graphics.Color +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.util.Log +import com.android.internal.R +import com.android.launcher3.icons.BaseIconFactory +import com.android.systemui.dagger.SysUISingleton +import dagger.Module +import dagger.Provides +import javax.inject.Inject +import javax.inject.Provider + +/** A provider used to cache and fetch app icons used by notifications. */ +interface AppIconProvider { + @Throws(NameNotFoundException::class) + fun getOrFetchAppIcon(packageName: String, context: Context): Drawable +} + +@SysUISingleton +class AppIconProviderImpl @Inject constructor(private val sysuiContext: Context) : AppIconProvider { + private val iconFactory: BaseIconFactory + get() = + BaseIconFactory( + sysuiContext, + sysuiContext.resources.configuration.densityDpi, + sysuiContext.resources.getDimensionPixelSize(R.dimen.notification_icon_circle_size), + ) + + override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable { + val icon = context.packageManager.getApplicationIcon(packageName) + return BitmapDrawable( + context.resources, + iconFactory.createScaledBitmap(icon, BaseIconFactory.MODE_HARDWARE), + ) + } +} + +class NoOpIconProvider : AppIconProvider { + companion object { + const val TAG = "NoOpIconProvider" + } + + override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable { + Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.") + return ColorDrawable(Color.WHITE) + } +} + +@Module +class AppIconProviderModule { + @Provides + @SysUISingleton + fun provideImpl(realImpl: Provider): AppIconProvider = + if (Flags.notificationsRedesignAppIcons()) { + realImpl.get() + } else { + NoOpIconProvider() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt new file mode 100644 index 000000000000..2522e58a08a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 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.row.icon + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.View +import com.android.internal.widget.NotificationRowIconView +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.NotifRemoteViewsFactory +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder +import javax.inject.Inject + +/** + * A factory which owns the construction of any NotificationRowIconView inside of Notifications in + * SystemUI. This allows overriding the small icon with the app icon in notifications. + */ +class NotificationRowIconViewInflaterFactory +@Inject +constructor(private val appIconProvider: AppIconProvider) : NotifRemoteViewsFactory { + override fun instantiate( + row: ExpandableNotificationRow, + @NotificationRowContentBinder.InflationFlag layoutType: Int, + parent: View?, + name: String, + context: Context, + attrs: AttributeSet, + ): View? { + return when (name) { + NotificationRowIconView::class.java.name -> + NotificationRowIconView(context, attrs).also { view -> + val sbn = row.entry.sbn + view.setIconProvider( + object : NotificationRowIconView.NotificationIconProvider { + override fun shouldShowAppIcon(): Boolean { + // TODO(b/371174789): implement me + return true + } + + override fun getAppIcon(): Drawable { + return appIconProvider.getOrFetchAppIcon(sbn.packageName, context) + } + } + ) + } + else -> null + } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt index fc4f05df26ed..a7a61957d104 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt @@ -69,6 +69,8 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag +import com.android.systemui.statusbar.notification.row.icon.AppIconProviderImpl +import com.android.systemui.statusbar.notification.row.icon.NotificationRowIconViewInflaterFactory import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger import com.android.systemui.statusbar.phone.KeyguardBypassController @@ -99,7 +101,7 @@ import org.mockito.Mockito class ExpandableNotificationRowBuilder( private val context: Context, dependency: TestableDependency, - private val featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic() + private val featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic(), ) { private val mMockLogger: ExpandableNotificationRowLogger @@ -161,21 +163,21 @@ class ExpandableNotificationRowBuilder( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, "true", - true + true, ) setProperty( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SSIN_ENABLED, "true", - true + true, ) setProperty( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, "false", - true + true, ) - } + }, ) val remoteViewsFactories = getNotifRemoteViewsFactoryContainer(featureFlags) val remoteInputManager = Mockito.mock(NotificationRemoteInputManager::class.java, STUB_ONLY) @@ -192,21 +194,21 @@ class ExpandableNotificationRowBuilder( Mockito.mock(KeyguardDismissUtil::class.java, STUB_ONLY), remoteInputManager = remoteInputManager, smartReplyController = mSmartReplyController, - context = context + context = context, ), smartActionsInflater = SmartActionInflaterImpl( constants = mSmartReplyConstants, activityStarter = Mockito.mock(ActivityStarter::class.java, STUB_ONLY), smartReplyController = mSmartReplyController, - headsUpManager = mHeadsUpManager - ) + headsUpManager = mHeadsUpManager, + ), ) val notifLayoutInflaterFactoryProvider = object : NotifLayoutInflaterFactory.Provider { override fun provide( row: ExpandableNotificationRow, - layoutType: Int + layoutType: Int, ): NotifLayoutInflaterFactory = NotifLayoutInflaterFactory(row, layoutType, remoteViewsFactories) } @@ -270,14 +272,14 @@ class ExpandableNotificationRowBuilder( whenever( mOnUserInteractionCallback.registerFutureDismissal( ArgumentMatchers.any(), - ArgumentMatchers.anyInt() + ArgumentMatchers.anyInt(), ) ) .thenReturn(mFutureDismissalRunnable) } private fun getNotifRemoteViewsFactoryContainer( - featureFlags: FeatureFlags, + featureFlags: FeatureFlags ): NotifRemoteViewsFactoryContainer { return NotifRemoteViewsFactoryContainerImpl( featureFlags, @@ -285,6 +287,7 @@ class ExpandableNotificationRowBuilder( BigPictureLayoutInflaterFactory(), NotificationOptimizedLinearLayoutFactory(), { Mockito.mock(NotificationViewFlipperFactory::class.java) }, + NotificationRowIconViewInflaterFactory(AppIconProviderImpl(context)), ) } @@ -293,7 +296,7 @@ class ExpandableNotificationRowBuilder( NotificationChannel( notification.channelId, notification.channelId, - NotificationManager.IMPORTANCE_DEFAULT + NotificationManager.IMPORTANCE_DEFAULT, ) channel.isBlockable = true val entry = @@ -321,7 +324,7 @@ class ExpandableNotificationRowBuilder( private fun generateRow( entry: NotificationEntry, - @InflationFlag extraInflationFlags: Int + @InflationFlag extraInflationFlags: Int, ): ExpandableNotificationRow { // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be // set, but we do not want to override an existing value that is needed by a specific test. @@ -329,7 +332,7 @@ class ExpandableNotificationRowBuilder( val rowInflaterTask = RowInflaterTask( mFakeSystemClock, - Mockito.mock(RowInflaterTaskLogger::class.java, STUB_ONLY) + Mockito.mock(RowInflaterTaskLogger::class.java, STUB_ONLY), ) val row = rowInflaterTask.inflateSynchronously(context, null, entry) @@ -364,7 +367,7 @@ class ExpandableNotificationRowBuilder( mSmartReplyController, featureFlags, Mockito.mock(IStatusBarService::class.java, STUB_ONLY), - Mockito.mock(UiEventLogger::class.java, STUB_ONLY) + Mockito.mock(UiEventLogger::class.java, STUB_ONLY), ) row.setAboveShelfChangedListener { aboveShelf: Boolean -> } mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt new file mode 100644 index 000000000000..08c6bbab6dd6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 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.row.icon + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos + +val Kosmos.appIconProvider by Kosmos.Fixture { AppIconProviderImpl(applicationContext) } -- GitLab From aab30aa51bb4f6b85caa7bb2bf9ed2b479587424 Mon Sep 17 00:00:00 2001 From: Olivier St-Onge Date: Mon, 14 Oct 2024 12:56:54 -0400 Subject: [PATCH 158/441] Add rows config for QS and QQS for bc25 Flag: com.android.systemui.qs_ui_refactor_compose_fragment Fixes: 373009020 Test: QuickQuickSettingsViewModelTest.kt Test: QqsPanelTilesLandscape Test: QqsPanelTilesPortrait Change-Id: If992b5b268429bc3e1755824d92121dbd287a994 --- .../panels/data/repository/PaginatedGridRepositoryTest.kt | 2 +- .../data/repository/QuickQuickSettingsRowRepositoryTest.kt | 2 +- .../panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt | 2 +- packages/SystemUI/res/values-land/config.xml | 6 ++++++ packages/SystemUI/res/values-sw600dp-land/config.xml | 6 ++++++ packages/SystemUI/res/values-sw600dp-port/config.xml | 6 ++++++ packages/SystemUI/res/values/config.xml | 6 ++++++ .../qs/panels/data/repository/PaginatedGridRepository.kt | 2 +- .../data/repository/QuickQuickSettingsRowRepository.kt | 2 +- 9 files changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt index 14d60943149f..e5bdc2e56b11 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt @@ -54,7 +54,7 @@ class PaginatedGridRepositoryTest : SysuiTestCase() { private fun setRowsInConfig(rows: Int) = with(kosmos) { testCase.context.orCreateTestableResources.addOverride( - R.integer.quick_settings_max_rows, + R.integer.quick_settings_paginated_grid_num_rows, rows, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt index ae6f576bcf3e..cda3d488cb1e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt @@ -54,7 +54,7 @@ class QuickQuickSettingsRowRepositoryTest : SysuiTestCase() { private fun setRowsInConfig(rows: Int) = with(kosmos) { testCase.context.orCreateTestableResources.addOverride( - R.integer.quick_qs_panel_max_rows, + R.integer.quick_qs_paginated_grid_num_rows, rows, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt index a1c0ef2789d5..2c894f9aa20f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt @@ -151,7 +151,7 @@ class QuickQuickSettingsViewModelTest : SysuiTestCase() { private fun Kosmos.setRows(rows: Int) { testCase.context.orCreateTestableResources.addOverride( - R.integer.quick_qs_panel_max_rows, + R.integer.quick_qs_paginated_grid_num_rows, rows, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index b5efeb5f6b3b..5d5b95546d0d 100644 --- a/packages/SystemUI/res/values-land/config.xml +++ b/packages/SystemUI/res/values-land/config.xml @@ -25,6 +25,12 @@ 4 + + 2 + + + 1 + 8 diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index fc6d20e11d3b..c661846d025f 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -27,6 +27,12 @@ true + + 3 + + + 2 + 2 diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml index 7daad1a43f73..f556b97eefc2 100644 --- a/packages/SystemUI/res/values-sw600dp-port/config.xml +++ b/packages/SystemUI/res/values-sw600dp-port/config.xml @@ -21,6 +21,12 @@ 3 + + 3 + + + 2 + 3 diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 6f94f9e2a216..16a8bc5b034f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -70,6 +70,12 @@ 4 + + 4 + + + 2 + 4 diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt index 26b2e2b7bd66..424be90ba2ec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt @@ -38,6 +38,6 @@ constructor( ) { val rows = configurationRepository.onConfigurationChange.emitOnStart().map { - resources.getInteger(R.integer.quick_settings_max_rows) + resources.getInteger(R.integer.quick_settings_paginated_grid_num_rows) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt index f7c71ceb9e6c..ee0cfb304db0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt @@ -34,6 +34,6 @@ constructor( ) { val rows = configurationRepository.onConfigurationChange.emitOnStart().map { - resources.getInteger(R.integer.quick_qs_panel_max_rows) + resources.getInteger(R.integer.quick_qs_paginated_grid_num_rows) } } -- GitLab From 364a7091acef77bff6355bbc2b321379482957d2 Mon Sep 17 00:00:00 2001 From: Valentin Iftime Date: Fri, 11 Oct 2024 18:42:34 +0200 Subject: [PATCH 159/441] Allow TestHarness exemption from forced grouping notifications Forced grouping for all notifications in Test Harness can be disabled via ADB cmd: - adb shell cmd notification set_exempt_th_force_grouping true While running tests in Test Harness mode, disable forced grouping of abusive notifications: - group summary notifications without children - group child notifications without summary Autogrouping of ungrouped notifications is still enabled. Flag: android.service.notification.notification_force_grouping Test: atest PlatformScenarioTests:android.platform.test.scenario.notification Bug: 359340654 Change-Id: Iaa74d9d5d3b338b68d3201f8f96854cd72f19a2c --- .../com/android/server/notification/GroupHelper.java | 11 +++++++++++ .../notification/NotificationManagerService.java | 5 +++++ .../server/notification/NotificationShellCmd.java | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java index 9b9be4cd8f3f..6681e36e00ee 100644 --- a/services/core/java/com/android/server/notification/GroupHelper.java +++ b/services/core/java/com/android/server/notification/GroupHelper.java @@ -29,6 +29,7 @@ import static android.service.notification.Flags.notificationForceGrouping; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -88,6 +89,7 @@ public class GroupHelper { private final int mAutogroupSparseGroupsAtCount; private final Context mContext; private final PackageManager mPackageManager; + private boolean mIsTestHarnessExempted; // Only contains notifications that are not explicitly grouped by the app (aka no group or // sort key). @@ -174,6 +176,11 @@ public class GroupHelper { NOTIFICATION_SHADE_SECTIONS = getNotificationShadeSections(); } + void setTestHarnessExempted(boolean isExempted) { + // Allow E2E tests to post ungrouped notifications + mIsTestHarnessExempted = ActivityManager.isRunningInUserTestHarness() && isExempted; + } + private String generatePackageKey(int userId, String pkg) { return userId + "|" + pkg; } @@ -696,6 +703,10 @@ public class GroupHelper { return; } + if (mIsTestHarnessExempted) { + return; + } + final NotificationSectioner sectioner = getSection(record); if (sectioner == null) { if (DEBUG) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6c2d4f72ce57..91e5302c9e5e 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2982,6 +2982,11 @@ public class NotificationManagerService extends SystemService { }); } + //Enables tests running in TH mode to be exempted from forced grouping of notifications + void setTestHarnessExempted(boolean isExempted) { + mGroupHelper.setTestHarnessExempted(isExempted); + } + private void sendRegisteredOnlyBroadcast(String action) { sendRegisteredOnlyBroadcast(new Intent(action)); } diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java index 10169d544b73..c305d66c24c1 100644 --- a/services/core/java/com/android/server/notification/NotificationShellCmd.java +++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java @@ -80,6 +80,7 @@ public class NotificationShellCmd extends ShellCommand { + " get \n" + " snooze --for \n" + " unsnooze \n" + + " set_exempt_th_force_grouping [true|false]\n" ; private static final String NOTIFY_USAGE = @@ -428,6 +429,13 @@ public class NotificationShellCmd extends ShellCommand { } break; } + case "set_exempt_th_force_grouping": { + String arg = getNextArgRequired(); + final boolean exemptTestHarnessFromForceGrouping = + "true".equals(arg) || "1".equals(arg); + mDirectService.setTestHarnessExempted(exemptTestHarnessFromForceGrouping); + break; + } default: return handleDefaultCommands(cmd); } -- GitLab From f94d00571fd25d56d80385adc8ccebaa7b196d49 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Fri, 11 Oct 2024 19:48:10 +0200 Subject: [PATCH 160/441] Set inflater factory for group summary header This enables the app icon changes there too. Bug: 371174789 Test: manually post group notifications and see that they have the right icon Flag: android.app.notifications_redesign_app_icons Change-Id: I093701466fecd66435b140eee415733ffa256e5e --- .../notification/row/NotificationContentInflater.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 69f45db40c65..c5608d64def3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -342,7 +342,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder * Cancel any pending content view frees from {@link #freeNotificationView} for the provided * content views. * - * @param row top level notification row containing the content views + * @param row top level notification row containing the content views * @param contentViews content views to cancel pending frees on */ private void cancelContentViewFrees( @@ -478,6 +478,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_HEADS_UP)); setRemoteViewsInflaterFactory(result.newPublicView, notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_PUBLIC)); + if (android.app.Flags.notificationsRedesignAppIcons()) { + setRemoteViewsInflaterFactory(result.mNewGroupHeaderView, + notifLayoutInflaterFactoryProvider.provide(row, FLAG_GROUP_SUMMARY_HEADER)); + setRemoteViewsInflaterFactory(result.mNewMinimizedGroupHeaderView, + notifLayoutInflaterFactoryProvider.provide(row, + FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)); + } } private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews, @@ -516,6 +523,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder logger.logAsyncTaskProgress(entry, "contracted view applied"); result.inflatedContentView = v; } + @Override public RemoteViews getRemoteView() { return result.newContentView; @@ -1406,6 +1414,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder @VisibleForTesting abstract static class ApplyCallback { public abstract void setResultView(View v); + public abstract RemoteViews getRemoteView(); } -- GitLab From 0023b25cc6bf3109f0315381a18ea242714f6418 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Fri, 11 Oct 2024 17:28:58 +0200 Subject: [PATCH 161/441] Notif redesign: Account for low ram icon size These sizes are technically the same by default, but we should still respect the contract of the declared dimensions. Bug: 371174789 Test: cannot test, will be unit tested in follow-up Flag: android.app.notifications_redesign_app_icons Change-Id: Ic23f2c4132ef25585a8546ed02901820fdf2ca6d --- .../notification/row/icon/AppIconProvider.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt index 13779886e321..24b5cf1aa33b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row.icon +import android.app.ActivityManager import android.app.Flags import android.content.Context import android.content.pm.PackageManager.NameNotFoundException @@ -41,12 +42,16 @@ interface AppIconProvider { @SysUISingleton class AppIconProviderImpl @Inject constructor(private val sysuiContext: Context) : AppIconProvider { private val iconFactory: BaseIconFactory - get() = - BaseIconFactory( - sysuiContext, - sysuiContext.resources.configuration.densityDpi, - sysuiContext.resources.getDimensionPixelSize(R.dimen.notification_icon_circle_size), - ) + get() { + val isLowRam = ActivityManager.isLowRamDeviceStatic() + val res = sysuiContext.resources + val iconSize: Int = + res.getDimensionPixelSize( + if (isLowRam) R.dimen.notification_small_icon_size_low_ram + else R.dimen.notification_small_icon_size + ) + return BaseIconFactory(sysuiContext, res.configuration.densityDpi, iconSize) + } override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable { val icon = context.packageManager.getApplicationIcon(packageName) -- GitLab From a571f1c927f3b0ca6febb1149a3e7c56910dd83e Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Tue, 15 Oct 2024 13:21:53 +0100 Subject: [PATCH 162/441] Remove inconsistent @Deprecated behavior in *removed.txt files Previously, `@Deprecated` was handled differently in `*removed.txt` files compared to `*current.txt`. In the former `@Deprecated` on a class was not propagated to its class members or nested classes but in the latter it was. This change removes that inconsistency by making it behave the same way in the `*removed.txt` files as it already does in the `*current.txt` files. Bug: 373584764 Test: m checkapi Change-Id: I828c94e7c7094bae2a619c9a4367688fce1b8c61 --- core/api/removed.txt | 60 +++++++++--------- core/api/system-removed.txt | 122 ++++++++++++++++++------------------ 2 files changed, 91 insertions(+), 91 deletions(-) diff --git a/core/api/removed.txt b/core/api/removed.txt index 3c7c0d6e6ea1..a3cab291a1de 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -82,12 +82,12 @@ package android.database { package android.graphics { @Deprecated public class AvoidXfermode extends android.graphics.Xfermode { - ctor public AvoidXfermode(int, int, android.graphics.AvoidXfermode.Mode); + ctor @Deprecated public AvoidXfermode(int, int, android.graphics.AvoidXfermode.Mode); } - public enum AvoidXfermode.Mode { - enum_constant public static final android.graphics.AvoidXfermode.Mode AVOID; - enum_constant public static final android.graphics.AvoidXfermode.Mode TARGET; + @Deprecated public enum AvoidXfermode.Mode { + enum_constant @Deprecated public static final android.graphics.AvoidXfermode.Mode AVOID; + enum_constant @Deprecated public static final android.graphics.AvoidXfermode.Mode TARGET; } public class Canvas { @@ -102,9 +102,9 @@ package android.graphics { } @Deprecated public class LayerRasterizer extends android.graphics.Rasterizer { - ctor public LayerRasterizer(); - method public void addLayer(android.graphics.Paint, float, float); - method public void addLayer(android.graphics.Paint); + ctor @Deprecated public LayerRasterizer(); + method @Deprecated public void addLayer(android.graphics.Paint, float, float); + method @Deprecated public void addLayer(android.graphics.Paint); } public class Paint { @@ -118,7 +118,7 @@ package android.graphics { } @Deprecated public class PixelXorXfermode extends android.graphics.Xfermode { - ctor public PixelXorXfermode(int); + ctor @Deprecated public PixelXorXfermode(int); } public class Rasterizer { @@ -170,14 +170,14 @@ package android.media.tv { package android.net { @Deprecated public class NetworkBadging { - method @NonNull public static android.graphics.drawable.Drawable getWifiIcon(@IntRange(from=0, to=4) int, int, @Nullable android.content.res.Resources.Theme); - field public static final int BADGING_4K = 30; // 0x1e - field public static final int BADGING_HD = 20; // 0x14 - field public static final int BADGING_NONE = 0; // 0x0 - field public static final int BADGING_SD = 10; // 0xa + method @Deprecated @NonNull public static android.graphics.drawable.Drawable getWifiIcon(@IntRange(from=0, to=4) int, int, @Nullable android.content.res.Resources.Theme); + field @Deprecated public static final int BADGING_4K = 30; // 0x1e + field @Deprecated public static final int BADGING_HD = 20; // 0x14 + field @Deprecated public static final int BADGING_NONE = 0; // 0x0 + field @Deprecated public static final int BADGING_SD = 10; // 0xa } - @IntDef({0x0, 0xa, 0x14, 0x1e}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NetworkBadging.Badging { + @Deprecated @IntDef({0x0, 0xa, 0x14, 0x1e}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NetworkBadging.Badging { } public final class Proxy { @@ -304,14 +304,14 @@ package android.provider { @Deprecated public static final class ContactsContract.RawContacts.StreamItems implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemsColumns { field @Deprecated public static final String CONTENT_DIRECTORY = "stream_items"; - field public static final String _COUNT = "_count"; - field public static final String _ID = "_id"; + field @Deprecated public static final String _COUNT = "_count"; + field @Deprecated public static final String _ID = "_id"; } @Deprecated public static final class ContactsContract.StreamItemPhotos implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemPhotosColumns { field @Deprecated public static final String PHOTO = "photo"; - field public static final String _COUNT = "_count"; - field public static final String _ID = "_id"; + field @Deprecated public static final String _COUNT = "_count"; + field @Deprecated public static final String _ID = "_id"; } @Deprecated protected static interface ContactsContract.StreamItemPhotosColumns { @@ -332,16 +332,16 @@ package android.provider { field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item"; field @Deprecated public static final android.net.Uri CONTENT_URI; field @Deprecated public static final String MAX_ITEMS = "max_items"; - field public static final String _COUNT = "_count"; - field public static final String _ID = "_id"; + field @Deprecated public static final String _COUNT = "_count"; + field @Deprecated public static final String _ID = "_id"; } @Deprecated public static final class ContactsContract.StreamItems.StreamItemPhotos implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemPhotosColumns { field @Deprecated public static final String CONTENT_DIRECTORY = "photo"; field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/stream_item_photo"; field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item_photo"; - field public static final String _COUNT = "_count"; - field public static final String _ID = "_id"; + field @Deprecated public static final String _COUNT = "_count"; + field @Deprecated public static final String _ID = "_id"; } @Deprecated protected static interface ContactsContract.StreamItemsColumns { @@ -447,14 +447,14 @@ package android.text.style { package android.util { @Deprecated public class FloatMath { - method public static float ceil(float); - method public static float cos(float); - method public static float exp(float); - method public static float floor(float); - method public static float hypot(float, float); - method public static float pow(float, float); - method public static float sin(float); - method public static float sqrt(float); + method @Deprecated public static float ceil(float); + method @Deprecated public static float cos(float); + method @Deprecated public static float exp(float); + method @Deprecated public static float floor(float); + method @Deprecated public static float hypot(float, float); + method @Deprecated public static float pow(float, float); + method @Deprecated public static float sin(float); + method @Deprecated public static float sqrt(float); } } diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index bbfa0ec3f3c2..78b9994e8fa1 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -7,8 +7,8 @@ package android.app { } @Deprecated public abstract static class AppOpsManager.AppOpsCollector extends android.app.AppOpsManager.OnOpNotedCallback { - ctor public AppOpsManager.AppOpsCollector(); - method @NonNull public java.util.concurrent.Executor getAsyncNotedExecutor(); + ctor @Deprecated public AppOpsManager.AppOpsCollector(); + method @Deprecated @NonNull public java.util.concurrent.Executor getAsyncNotedExecutor(); } public class Notification implements android.os.Parcelable { @@ -207,7 +207,7 @@ package android.service.translation { @Deprecated public static interface TranslationService.OnTranslationResultCallback { method @Deprecated public void onError(); - method public void onTranslationSuccess(@NonNull android.view.translation.TranslationResponse); + method @Deprecated public void onTranslationSuccess(@NonNull android.view.translation.TranslationResponse); } } @@ -261,64 +261,64 @@ package android.telephony.ims { } @Deprecated public final class SipDelegateImsConfiguration implements android.os.Parcelable { - method public boolean containsKey(@NonNull String); - method @NonNull public android.os.PersistableBundle copyBundle(); - method public int describeContents(); - method public boolean getBoolean(@NonNull String, boolean); - method public int getInt(@NonNull String, int); - method @Nullable public String getString(@NonNull String); - method public long getVersion(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final String IPTYPE_IPV4 = "IPV4"; - field public static final String IPTYPE_IPV6 = "IPV6"; - field public static final String KEY_SIP_CONFIG_AUTHENTICATION_HEADER_STRING = "sip_config_auhentication_header_string"; - field public static final String KEY_SIP_CONFIG_AUTHENTICATION_NONCE_STRING = "sip_config_authentication_nonce_string"; - field public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING = "sip_config_cellular_network_info_header_string"; - field public static final String KEY_SIP_CONFIG_HOME_DOMAIN_STRING = "sip_config_home_domain_string"; - field public static final String KEY_SIP_CONFIG_IMEI_STRING = "sip_config_imei_string"; - field public static final String KEY_SIP_CONFIG_IPTYPE_STRING = "sip_config_iptype_string"; - field public static final String KEY_SIP_CONFIG_IS_COMPACT_FORM_ENABLED_BOOL = "sip_config_is_compact_form_enabled_bool"; - field public static final String KEY_SIP_CONFIG_IS_GRUU_ENABLED_BOOL = "sip_config_is_gruu_enabled_bool"; - field public static final String KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL = "sip_config_is_ipsec_enabled_bool"; - field public static final String KEY_SIP_CONFIG_IS_KEEPALIVE_ENABLED_BOOL = "sip_config_is_keepalive_enabled_bool"; - field public static final String KEY_SIP_CONFIG_IS_NAT_ENABLED_BOOL = "sip_config_is_nat_enabled_bool"; - field public static final String KEY_SIP_CONFIG_MAX_PAYLOAD_SIZE_ON_UDP_INT = "sip_config_udp_max_payload_size_int"; - field public static final String KEY_SIP_CONFIG_PATH_HEADER_STRING = "sip_config_path_header_string"; - field public static final String KEY_SIP_CONFIG_P_ACCESS_NETWORK_INFO_HEADER_STRING = "sip_config_p_access_network_info_header_string"; - field public static final String KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING = "sip_config_p_associated_uri_header_string"; - field public static final String KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING = "sip_config_p_last_access_network_info_header_string"; - field public static final String KEY_SIP_CONFIG_SECURITY_VERIFY_HEADER_STRING = "sip_config_security_verify_header_string"; - field public static final String KEY_SIP_CONFIG_SERVER_DEFAULT_IPADDRESS_STRING = "sip_config_server_default_ipaddress_string"; - field public static final String KEY_SIP_CONFIG_SERVER_DEFAULT_PORT_INT = "sip_config_server_default_port_int"; - field public static final String KEY_SIP_CONFIG_SERVER_IPSEC_CLIENT_PORT_INT = "sip_config_server_ipsec_client_port_int"; - field public static final String KEY_SIP_CONFIG_SERVER_IPSEC_OLD_CLIENT_PORT_INT = "sip_config_server_ipsec_old_client_port_int"; - field public static final String KEY_SIP_CONFIG_SERVER_IPSEC_SERVER_PORT_INT = "sip_config_server_ipsec_server_port_int"; - field public static final String KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING = "sip_config_service_route_header_string"; - field public static final String KEY_SIP_CONFIG_TRANSPORT_TYPE_STRING = "sip_config_protocol_type_string"; - field public static final String KEY_SIP_CONFIG_UE_DEFAULT_IPADDRESS_STRING = "sip_config_ue_default_ipaddress_string"; - field public static final String KEY_SIP_CONFIG_UE_DEFAULT_PORT_INT = "sip_config_ue_default_port_int"; - field public static final String KEY_SIP_CONFIG_UE_IPSEC_CLIENT_PORT_INT = "sip_config_ue_ipsec_client_port_int"; - field public static final String KEY_SIP_CONFIG_UE_IPSEC_OLD_CLIENT_PORT_INT = "sip_config_ue_ipsec_old_client_port_int"; - field public static final String KEY_SIP_CONFIG_UE_IPSEC_SERVER_PORT_INT = "sip_config_ue_ipsec_server_port_int"; - field public static final String KEY_SIP_CONFIG_UE_PRIVATE_USER_ID_STRING = "sip_config_ue_private_user_id_string"; - field public static final String KEY_SIP_CONFIG_UE_PUBLIC_GRUU_STRING = "sip_config_ue_public_gruu_string"; - field public static final String KEY_SIP_CONFIG_UE_PUBLIC_IPADDRESS_WITH_NAT_STRING = "sip_config_ue_public_ipaddress_with_nat_string"; - field public static final String KEY_SIP_CONFIG_UE_PUBLIC_PORT_WITH_NAT_INT = "sip_config_ue_public_port_with_nat_int"; - field public static final String KEY_SIP_CONFIG_UE_PUBLIC_USER_ID_STRING = "sip_config_ue_public_user_id_string"; - field public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING = "sip_config_uri_user_part_string"; - field public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING = "sip_config_sip_user_agent_header_string"; - field public static final String SIP_TRANSPORT_TCP = "TCP"; - field public static final String SIP_TRANSPORT_UDP = "UDP"; - } - - public static final class SipDelegateImsConfiguration.Builder { - ctor public SipDelegateImsConfiguration.Builder(int); - ctor public SipDelegateImsConfiguration.Builder(@NonNull android.telephony.ims.SipDelegateImsConfiguration); - method @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addBoolean(@NonNull String, boolean); - method @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addInt(@NonNull String, int); - method @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addString(@NonNull String, @NonNull String); - method @NonNull public android.telephony.ims.SipDelegateImsConfiguration build(); + method @Deprecated public boolean containsKey(@NonNull String); + method @Deprecated @NonNull public android.os.PersistableBundle copyBundle(); + method @Deprecated public int describeContents(); + method @Deprecated public boolean getBoolean(@NonNull String, boolean); + method @Deprecated public int getInt(@NonNull String, int); + method @Deprecated @Nullable public String getString(@NonNull String); + method @Deprecated public long getVersion(); + method @Deprecated public void writeToParcel(@NonNull android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator CREATOR; + field @Deprecated public static final String IPTYPE_IPV4 = "IPV4"; + field @Deprecated public static final String IPTYPE_IPV6 = "IPV6"; + field @Deprecated public static final String KEY_SIP_CONFIG_AUTHENTICATION_HEADER_STRING = "sip_config_auhentication_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_AUTHENTICATION_NONCE_STRING = "sip_config_authentication_nonce_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING = "sip_config_cellular_network_info_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_HOME_DOMAIN_STRING = "sip_config_home_domain_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_IMEI_STRING = "sip_config_imei_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_IPTYPE_STRING = "sip_config_iptype_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_IS_COMPACT_FORM_ENABLED_BOOL = "sip_config_is_compact_form_enabled_bool"; + field @Deprecated public static final String KEY_SIP_CONFIG_IS_GRUU_ENABLED_BOOL = "sip_config_is_gruu_enabled_bool"; + field @Deprecated public static final String KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL = "sip_config_is_ipsec_enabled_bool"; + field @Deprecated public static final String KEY_SIP_CONFIG_IS_KEEPALIVE_ENABLED_BOOL = "sip_config_is_keepalive_enabled_bool"; + field @Deprecated public static final String KEY_SIP_CONFIG_IS_NAT_ENABLED_BOOL = "sip_config_is_nat_enabled_bool"; + field @Deprecated public static final String KEY_SIP_CONFIG_MAX_PAYLOAD_SIZE_ON_UDP_INT = "sip_config_udp_max_payload_size_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_PATH_HEADER_STRING = "sip_config_path_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_P_ACCESS_NETWORK_INFO_HEADER_STRING = "sip_config_p_access_network_info_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING = "sip_config_p_associated_uri_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING = "sip_config_p_last_access_network_info_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_SECURITY_VERIFY_HEADER_STRING = "sip_config_security_verify_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVER_DEFAULT_IPADDRESS_STRING = "sip_config_server_default_ipaddress_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVER_DEFAULT_PORT_INT = "sip_config_server_default_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVER_IPSEC_CLIENT_PORT_INT = "sip_config_server_ipsec_client_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVER_IPSEC_OLD_CLIENT_PORT_INT = "sip_config_server_ipsec_old_client_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVER_IPSEC_SERVER_PORT_INT = "sip_config_server_ipsec_server_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING = "sip_config_service_route_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_TRANSPORT_TYPE_STRING = "sip_config_protocol_type_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_DEFAULT_IPADDRESS_STRING = "sip_config_ue_default_ipaddress_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_DEFAULT_PORT_INT = "sip_config_ue_default_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_IPSEC_CLIENT_PORT_INT = "sip_config_ue_ipsec_client_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_IPSEC_OLD_CLIENT_PORT_INT = "sip_config_ue_ipsec_old_client_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_IPSEC_SERVER_PORT_INT = "sip_config_ue_ipsec_server_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_PRIVATE_USER_ID_STRING = "sip_config_ue_private_user_id_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_PUBLIC_GRUU_STRING = "sip_config_ue_public_gruu_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_PUBLIC_IPADDRESS_WITH_NAT_STRING = "sip_config_ue_public_ipaddress_with_nat_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_PUBLIC_PORT_WITH_NAT_INT = "sip_config_ue_public_port_with_nat_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_PUBLIC_USER_ID_STRING = "sip_config_ue_public_user_id_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING = "sip_config_uri_user_part_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING = "sip_config_sip_user_agent_header_string"; + field @Deprecated public static final String SIP_TRANSPORT_TCP = "TCP"; + field @Deprecated public static final String SIP_TRANSPORT_UDP = "UDP"; + } + + @Deprecated public static final class SipDelegateImsConfiguration.Builder { + ctor @Deprecated public SipDelegateImsConfiguration.Builder(int); + ctor @Deprecated public SipDelegateImsConfiguration.Builder(@NonNull android.telephony.ims.SipDelegateImsConfiguration); + method @Deprecated @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addBoolean(@NonNull String, boolean); + method @Deprecated @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addInt(@NonNull String, int); + method @Deprecated @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addString(@NonNull String, @NonNull String); + method @Deprecated @NonNull public android.telephony.ims.SipDelegateImsConfiguration build(); } } -- GitLab From 8fd307a2e09e353ee309aeced4f19b68d0fb847b Mon Sep 17 00:00:00 2001 From: Oluwarotimi Adesina Date: Tue, 15 Oct 2024 11:31:51 +0000 Subject: [PATCH 163/441] Update Runtime metadata property config Flag: android.app.appfunctions.flags.enable_app_function_manager Test: Existing Bug: 357551503 Change-Id: Ifcb9ade3421e5f9f196eef68aa4e9b76bd6aa1dd --- .../appfunctions/AppFunctionRuntimeMetadata.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java index 08ecced234a9..06d95f5270c3 100644 --- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java @@ -137,8 +137,10 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { .TOKENIZER_TYPE_VERBATIM) .build()) .addProperty( - new AppSearchSchema.BooleanPropertyConfig.Builder(PROPERTY_ENABLED) + new AppSearchSchema.LongPropertyConfig.Builder(PROPERTY_ENABLED) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setIndexingType( + AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE) .build()) .addProperty( new AppSearchSchema.StringPropertyConfig.Builder( @@ -212,19 +214,14 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { } /** - * Sets an indicator specifying if the function is enabled or not. This would override the - * default enabled state in the static metadata ({@link - * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to null - * to clear the override. - * TODO(369683073) Replace the tristate Boolean with IntDef EnabledState. + * Sets an indicator specifying the function enabled state. */ @NonNull public Builder setEnabled(@EnabledState int enabledState) { if (enabledState != APP_FUNCTION_STATE_DEFAULT && enabledState != APP_FUNCTION_STATE_ENABLED && enabledState != APP_FUNCTION_STATE_DISABLED) { - throw new IllegalArgumentException( - "Value of EnabledState is unsupported."); + throw new IllegalArgumentException("Value of EnabledState is unsupported."); } setPropertyLong(PROPERTY_ENABLED, enabledState); return this; -- GitLab From 636e07d9ed2338c6c759c2441eb159743a5245f1 Mon Sep 17 00:00:00 2001 From: omarmt Date: Tue, 15 Oct 2024 11:38:56 +0000 Subject: [PATCH 164/441] Cleanup SwipeToSceneTest Test: Just a refactor Bug: 370949877 Flag: com.android.systemui.scene_container Change-Id: Idc56338a67b397b5479c3b9b8a1a227ff0e84105 --- .../animation/scene/SwipeToSceneTest.kt | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 28d0a473935d..1711f3145cae 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -102,26 +102,22 @@ class SwipeToSceneTest { modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName), ) { scene( - SceneA, + key = SceneA, userActions = if (swipesEnabled()) - mapOf( - Swipe.Left to SceneB, - Swipe.Down to TestScenes.SceneC, - Swipe.Up to SceneB, - ) + mapOf(Swipe.Left to SceneB, Swipe.Down to SceneC, Swipe.Up to SceneB) else emptyMap(), ) { Box(Modifier.fillMaxSize()) } scene( - SceneB, + key = SceneB, userActions = if (swipesEnabled()) mapOf(Swipe.Right to SceneA) else emptyMap(), ) { Box(Modifier.fillMaxSize()) } scene( - TestScenes.SceneC, + key = SceneC, userActions = if (swipesEnabled()) mapOf( @@ -196,7 +192,7 @@ class SwipeToSceneTest { // Drag is in progress, so currentScene = SceneA and progress = 56dp / LayoutHeight transition = assertThat(layoutState.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) assertThat(transition).hasCurrentScene(SceneA) assertThat(transition).hasProgress(56.dp / LayoutHeight) assertThat(transition).isInitiatedByUserInput() @@ -206,15 +202,15 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { up() } transition = assertThat(layoutState.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(TestScenes.SceneC) - assertThat(transition).hasCurrentScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) + assertThat(transition).hasCurrentScene(SceneC) assertThat(transition).hasProgress(56.dp / LayoutHeight) assertThat(transition).isInitiatedByUserInput() // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test @@ -271,20 +267,20 @@ class SwipeToSceneTest { // We should be animating to C (currentScene = SceneC). transition = assertThat(layoutState.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(TestScenes.SceneC) - assertThat(transition).hasCurrentScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) + assertThat(transition).hasCurrentScene(SceneC) assertThat(transition).hasProgress(55.dp / LayoutHeight) // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test fun multiPointerSwipe() { // Start at scene C. - val layoutState = layoutState(TestScenes.SceneC) + val layoutState = layoutState(SceneC) // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is // detected as a drag event. @@ -295,7 +291,7 @@ class SwipeToSceneTest { } assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) // Swipe down with two fingers. rule.onRoot().performTouchInput { @@ -307,7 +303,7 @@ class SwipeToSceneTest { // We are transitioning to B because we used 2 fingers. val transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasFromScene(SceneC) assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only @@ -315,13 +311,13 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { repeat(2) { i -> up(pointerId = i) } } rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test fun defaultEdgeSwipe() { // Start at scene C. - val layoutState = layoutState(TestScenes.SceneC) + val layoutState = layoutState(SceneC) // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is // detected as a drag event. @@ -332,7 +328,7 @@ class SwipeToSceneTest { } assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) // Swipe down from the top edge. rule.onRoot().performTouchInput { @@ -342,7 +338,7 @@ class SwipeToSceneTest { // We are transitioning to B (and not A) because we started from the top edge. var transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasFromScene(SceneC) assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only @@ -350,7 +346,7 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { up() } rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) // Swipe right from the left edge. rule.onRoot().performTouchInput { @@ -360,7 +356,7 @@ class SwipeToSceneTest { // We are transitioning to B (and not A) because we started from the left edge. transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasFromScene(SceneC) assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only @@ -368,7 +364,7 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { up() } rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test @@ -434,7 +430,7 @@ class SwipeToSceneTest { // We should still correctly compute that we are swiping down to scene C. var transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasToScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) // Release the finger, animating back to scene A. rule.onRoot().performTouchInput { up() } -- GitLab From 6e547dac0fd1db3ea43ed6fb0be8105801899c72 Mon Sep 17 00:00:00 2001 From: omarmt Date: Mon, 7 Oct 2024 15:19:09 +0000 Subject: [PATCH 165/441] STL should ignore mouse wheel interactions Mouse wheel interactions are not supported properly at the moment, we always assume that onPreFling is called at the end of the gesture. To avoid unexpected behaviors this gesture will now be ignored. Test: atest MultiPointerDraggableTest Test: atest SwipeToSceneTest Bug: 371984715 Flag: com.android.systemui.scene_container Change-Id: I07dde1328144792d381813a76ac5ae15cb72b3d9 --- .../animation/scene/DraggableHandler.kt | 28 +++++- .../animation/scene/MultiPointerDraggable.kt | 21 ++++- .../animation/scene/DraggableHandlerTest.kt | 28 +++++- .../animation/scene/SwipeToSceneTest.kt | 87 +++++++++++++++++++ 4 files changed, 155 insertions(+), 9 deletions(-) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 085157ac72b9..e5ef79b37b88 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -630,7 +630,13 @@ internal class NestedScrollHandlerImpl( return@PriorityNestedScrollConnection false } - _lastPointersInfo = pointersInfoOwner.pointersInfo() + val pointersInfo = pointersInfoOwner.pointersInfo() + + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + _lastPointersInfo = pointersInfo // If the current swipe transition is *not* closed to 0f or 1f, then we want the // scroll events to intercept the current transition to continue the scene @@ -649,7 +655,12 @@ internal class NestedScrollHandlerImpl( val isZeroOffset = if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f - _lastPointersInfo = pointersInfoOwner.pointersInfo() + val pointersInfo = pointersInfoOwner.pointersInfo() + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + _lastPointersInfo = pointersInfo val canStart = when (behavior) { @@ -684,7 +695,12 @@ internal class NestedScrollHandlerImpl( // We could start an overscroll animation canChangeScene = false - _lastPointersInfo = pointersInfoOwner.pointersInfo() + val pointersInfo = pointersInfoOwner.pointersInfo() + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + _lastPointersInfo = pointersInfo val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable) if (canStart) { @@ -707,6 +723,12 @@ internal class NestedScrollHandlerImpl( onScroll = { offsetAvailable -> val controller = dragController ?: error("Should be called after onStart") + val pointersInfo = pointersInfoOwner.pointersInfo() + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection 0f + } + // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is // initiated in a nested child. controller.onDrag(delta = offsetAvailable) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index aa70a0ce156b..8613f6da0f62 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope @@ -184,6 +185,7 @@ internal class MultiPointerDraggableNode( private var startedPosition: Offset? = null private var pointersDown: Int = 0 + private var isMouseWheel: Boolean = false internal fun pointersInfo(): PointersInfo { return PointersInfo( @@ -191,6 +193,7 @@ internal class MultiPointerDraggableNode( startedPosition = startedPosition, // We could have 0 pointers during fling or for other reasons. pointersDown = pointersDown.coerceAtLeast(1), + isMouseWheel = isMouseWheel, ) } @@ -202,8 +205,15 @@ internal class MultiPointerDraggableNode( // [requireAncestorPointersInfoOwner], to our descendants. while (currentContext.isActive) { // During the Initial pass, we receive the event after our ancestors. - val changes = awaitPointerEvent(PointerEventPass.Initial).changes + val pointerEvent = awaitPointerEvent(PointerEventPass.Initial) + + // Ignore cursor has entered the input region. + // This will only be sent after the cursor is hovering when in the input region. + if (pointerEvent.type == PointerEventType.Enter) continue + + val changes = pointerEvent.changes pointersDown = changes.countDown() + isMouseWheel = pointerEvent.type == PointerEventType.Scroll when { // There are no more pointers down. @@ -223,7 +233,8 @@ internal class MultiPointerDraggableNode( // The first pointer down, startedPosition was not set. startedPosition == null -> { - val firstPointerDown = changes.single() + // Mouse wheel could start with multiple pointer down + val firstPointerDown = changes.first() velocityPointerId = firstPointerDown.id velocityTracker.resetTracking() velocityTracker.addPointerInputChange(firstPointerDown) @@ -647,4 +658,8 @@ internal fun interface PointersInfoOwner { fun pointersInfo(): PointersInfo } -internal data class PointersInfo(val startedPosition: Offset?, val pointersDown: Int) +internal data class PointersInfo( + val startedPosition: Offset?, + val pointersDown: Int, + val isMouseWheel: Boolean, +) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index ecef6be49df8..202105bd6c3c 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -128,7 +128,7 @@ class DraggableHandlerTest { val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal) var pointerInfoOwner: () -> PointersInfo = { - PointersInfo(startedPosition = Offset.Zero, pointersDown = 1) + PointersInfo(startedPosition = Offset.Zero, pointersDown = 1, isMouseWheel = false) } fun nestedScrollConnection( @@ -1188,7 +1188,9 @@ class DraggableHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Drag from the **top** of the screen - pointerInfoOwner = { PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1) } + pointerInfoOwner = { + PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = false) + } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1206,7 +1208,11 @@ class DraggableHandlerTest { // Drag from the **bottom** of the screen pointerInfoOwner = { - PointersInfo(startedPosition = Offset(0f, SCREEN_SIZE), pointersDown = 1) + PointersInfo( + startedPosition = Offset(0f, SCREEN_SIZE), + pointersDown = 1, + isMouseWheel = false, + ) } assertIdle(currentScene = SceneC) @@ -1220,6 +1226,22 @@ class DraggableHandlerTest { ) } + @Test + fun ignoreMouseWheel() = runGestureTest { + // Start at scene C. + navigateToSceneC() + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) + + // Use mouse wheel + pointerInfoOwner = { + PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = true) + } + assertIdle(currentScene = SceneC) + + nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) + assertIdle(currentScene = SceneC) + } + @Test fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { // Swipe up from the middle to transition to scene B. diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 1711f3145cae..3001505d0e02 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -17,6 +17,8 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -39,12 +41,14 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.ScrollWheel import androidx.compose.ui.test.assertPositionInRootIsEqualTo import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performMouseInput import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeRight import androidx.compose.ui.test.swipeUp @@ -497,6 +501,89 @@ class SwipeToSceneTest { assertThat(layoutState.currentTransition).isNotNull() } + @Test + fun mouseWheel_pointerInputApi_ignoredByStl() { + val layoutState = layoutState() + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + TestContent(layoutState) + } + + rule.onRoot().performMouseInput { + enter(middle) + scroll(touchSlop, ScrollWheel.Vertical) + } + + // Mouse wheel scroll are ignored + assertThat(layoutState.currentTransition).isNull() + } + + @Test + fun mouseWheel_scrollableCannotScroll_ignoredByStl() { + val layoutState = layoutState() + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(layoutState, Modifier.size(LayoutWidth, LayoutHeight)) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Box( + Modifier.fillMaxSize() + // A scrollable that does not consume the scroll gesture + .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) + ) + } + scene(SceneB) { Box(Modifier.fillMaxSize()) } + } + } + + rule.onRoot().performMouseInput { + enter(middle) + scroll(touchSlop, ScrollWheel.Vertical) + } + + // Mouse wheel scroll are ignored + assertThat(layoutState.currentTransition).isNull() + } + + @Test + fun mouseWheel_scrollableConsume_ignoredByStl() { + val layoutState = layoutState() + var touchSlop = 0f + var lastScroll = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(layoutState, Modifier.size(LayoutWidth, LayoutHeight)) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Box( + Modifier.fillMaxSize() + // A scrollable that consumes the scroll gesture + .scrollable( + rememberScrollableState { + lastScroll = it + it + }, + Orientation.Vertical, + ) + ) + } + scene(SceneB) { Box(Modifier.fillMaxSize()) } + } + } + + rule.onRoot().performMouseInput { + enter(middle) + scroll(touchSlop * 10, ScrollWheel.Vertical) + } + + // Mouse wheel scroll are ignored + assertThat(layoutState.currentTransition).isNull() + + // Mouse wheel scroll can still be consumed by the scrollable + assertThat(lastScroll).isNotEqualTo(0f) + assertThat(touchSlop).isNotEqualTo(0f) + } + @Test fun transitionKey() { val transitionkey = TransitionKey(debugName = "foo") -- GitLab From 8d0896c32dd3bafc315059202d3998663f7a4981 Mon Sep 17 00:00:00 2001 From: Daniel Chapin Date: Tue, 15 Oct 2024 13:25:43 +0000 Subject: [PATCH 166/441] Revert "camera2 jni: nativeReadValues() can use camera_metadata_ro_entry instead of camera_metadata_entry" This reverts commit 3d5cfaacb78e41bc0892c1ab57a825872e3ac5be. Reason for revert: DF Blocking bug: b/373532818 Change-Id: Ie1a401779eb2c1ebc01c55d5b044bc58e62b4517 --- ...android_hardware_camera2_CameraMetadata.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp index 68d49cda191d..041fed74c573 100644 --- a/core/jni/android_hardware_camera2_CameraMetadata.cpp +++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp @@ -336,7 +336,7 @@ static void CameraMetadata_swap(JNIEnv *env, jclass thiz, jlong ptr, jlong other static jbyteArray CameraMetadata_readValues(JNIEnv *env, jclass thiz, jint tag, jlong ptr) { ALOGV("%s (tag = %d)", __FUNCTION__, tag); - const CameraMetadata *metadata = CameraMetadata_getPointerThrow(env, ptr); + CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, ptr); if (metadata == NULL) return NULL; const camera_metadata_t *metaBuffer = metadata->getAndLock(); @@ -349,14 +349,16 @@ static jbyteArray CameraMetadata_readValues(JNIEnv *env, jclass thiz, jint tag, } size_t tagSize = Helpers::getTypeSize(tagType); - camera_metadata_ro_entry entry = metadata->find(tag); + camera_metadata_entry entry = metadata->find(tag); if (entry.count == 0) { - if (!metadata->exists(tag)) { - ALOGV("%s: Tag %d does not have any entries", __FUNCTION__, tag); - } else { - ALOGV("%s: Tag %d had an entry, but it had 0 data", __FUNCTION__, tag); - } - return NULL; + if (!metadata->exists(tag)) { + ALOGV("%s: Tag %d does not have any entries", __FUNCTION__, tag); + return NULL; + } else { + // OK: we will return a 0-sized array. + ALOGV("%s: Tag %d had an entry, but it had 0 data", __FUNCTION__, + tag); + } } jsize byteCount = entry.count * tagSize; -- GitLab From 6d6c196d0b614f88b4a38d0ad6ebeb365b02ee24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Kurucz?= Date: Tue, 15 Oct 2024 12:56:29 +0000 Subject: [PATCH 167/441] Dump ENR appear animation states The linked bug might be caused by concurrent appear animations started for the same row, and one of them not being finalized. Dump the states of the appear animation to help investigate. Bug: 356958336 Test: dumpsysui NotificationStackScrollLayout Flag: EXEMPT changing dumps only Change-Id: Id77ba47ecf9aae0d1d885d13f5ff937ab378591d --- .../notification/row/ActivatableNotificationView.java | 9 +++++++++ .../notification/row/ExpandableNotificationRow.java | 1 + 2 files changed, 10 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 560028cb5640..4c7d90c12839 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -830,4 +830,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView }); } } + + protected void dumpAppearAnimationProperties(IndentingPrintWriter pw, String[] args) { + pw.print("AppearAnimation: "); + pw.print("mDrawingAppearAnimation", mDrawingAppearAnimation); + pw.print("mAppearAnimationFraction", mAppearAnimationFraction); + pw.print("mIsHeadsUpAnimation", mIsHeadsUpAnimation); + pw.print("mTargetPoint", mTargetPoint); + pw.println(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 49153d1d8eef..33dbbc233fa9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -3883,6 +3883,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView dumpHeights(pw); } showingLayout.dump(pw, args); + dumpAppearAnimationProperties(pw, args); dumpCustomOutline(pw, args); dumpClipping(pw, args); if (getViewState() != null) { -- GitLab From ceb7595d079645737d581b7920936b3754bcbf48 Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Mon, 14 Oct 2024 22:05:29 +0000 Subject: [PATCH 168/441] Remove pieces of device entry flag - Piece #4 Flag has shipped Bug: 279440316 Test: atest SystemUITests Flag: com.android.systemui.device_entry_udfps_refactor Change-Id: I660011af8d5ae9dcefb6c51635503003fa060c9b --- ...tificationPanelViewControllerBaseTest.java | 14 +++--- .../NotificationPanelViewControllerTest.java | 6 --- .../NotificationPanelViewController.java | 17 +++---- .../statusbar/phone/CentralSurfacesImpl.java | 31 ++++--------- .../phone/CentralSurfacesImplTest.java | 45 ------------------- 5 files changed, 19 insertions(+), 94 deletions(-) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 0454317b5f04..06d19d7f9822 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -68,13 +68,13 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.LatencyTracker; +import com.android.keyguard.EmptyLockIconViewController; 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; -import com.android.keyguard.LegacyLockIconViewController; import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; @@ -271,6 +271,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardUserSwitcherController mKeyguardUserSwitcherController; @Mock protected KeyguardStatusViewComponent mKeyguardStatusViewComponent; @Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; + @Mock protected EmptyLockIconViewController mLockIconViewController; @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController; @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController; @@ -285,7 +286,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected AmbientState mAmbientState; @Mock protected UserManager mUserManager; @Mock protected UiEventLogger mUiEventLogger; - @Mock protected LegacyLockIconViewController mLockIconViewController; @Mock protected KeyguardViewConfigurator mKeyguardViewConfigurator; @Mock protected KeyguardRootView mKeyguardRootView; @Mock protected View mKeyguardRootViewChild; @@ -397,7 +397,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); mMainDispatcher = getMainDispatcher(); KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps = @@ -687,6 +686,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(longPressHandlingView.getResources()).thenReturn(longPressHandlingViewRes); when(longPressHandlingViewRes.getString(anyInt())).thenReturn(""); + when(mKeyguardRootView.findViewById(anyInt())).thenReturn(mKeyguardRootViewChild); + when(mKeyguardViewConfigurator.getKeyguardRootView()).thenReturn(mKeyguardRootView); + mNotificationPanelViewController = new NotificationPanelViewController( mView, mMainHandler, @@ -852,7 +854,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel(); mNotificationPanelViewController.cancelHeightAnimator(); leakedAnimators = mNotificationPanelViewController.mTestSetOfAnimatorsUsed.stream() - .filter(Animator::isRunning).toList(); + .filter(Animator::isRunning).toList(); mNotificationPanelViewController.mTestSetOfAnimatorsUsed.forEach(Animator::cancel); } if (mMainHandler != null) { @@ -869,11 +871,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0); when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(stackBottom); when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(stackBottom); - when(mLockIconViewController.getTop()).thenReturn((float) (stackBottom - lockIconPadding)); - when(mKeyguardRootViewChild.getTop()).thenReturn((int) (stackBottom - lockIconPadding)); - when(mKeyguardRootView.findViewById(anyInt())).thenReturn(mKeyguardRootViewChild); - when(mKeyguardViewConfigurator.getKeyguardRootView()).thenReturn(mKeyguardRootView); when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding)) .thenReturn(indicationPadding); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 43dbb40d7721..ec75972aecfe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -217,7 +217,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(5); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(5); } @@ -235,7 +234,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); } @@ -253,7 +251,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); } @@ -271,7 +268,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(2); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(2); } @@ -289,7 +285,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); } @@ -389,7 +384,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void alternateBouncerVisible_onTouchEvent_notHandled() { - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); // GIVEN alternate bouncer is visible when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 757e37e9341f..d652d5bec27b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -123,7 +123,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; @@ -1859,16 +1858,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */ private float getLockIconPadding() { float lockIconPadding = 0f; - if (DeviceEntryUdfpsRefactor.isEnabled()) { - View deviceEntryIconView = mKeyguardViewConfigurator.getKeyguardRootView() - .findViewById(R.id.device_entry_icon_view); - if (deviceEntryIconView != null) { - lockIconPadding = mNotificationStackScrollLayoutController.getBottom() - - deviceEntryIconView.getTop(); - } - } else if (mLockIconViewController.getTop() != 0f) { + View deviceEntryIconView = mKeyguardViewConfigurator.getKeyguardRootView() + .findViewById(R.id.device_entry_icon_view); + if (deviceEntryIconView != null) { lockIconPadding = mNotificationStackScrollLayoutController.getBottom() - - mLockIconViewController.getTop(); + - deviceEntryIconView.getTop(); } return lockIconPadding; } @@ -5059,8 +5053,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return false; } - if (DeviceEntryUdfpsRefactor.isEnabled() - && mAlternateBouncerInteractor.isVisibleState()) { + if (mAlternateBouncerInteractor.isVisibleState()) { // never send touches to shade if the alternate bouncer is showing return false; } 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 cb4454d88b2d..3d7cd9c9fbcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -128,7 +128,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.emergency.EmergencyGesture; import com.android.systemui.emergency.EmergencyGestureModule.EmergencyGestureIntentFactory; import com.android.systemui.flags.FeatureFlags; @@ -2825,23 +2824,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.setExpansionAffectsAlpha(!unlocking); if (mAlternateBouncerInteractor.isVisibleState()) { - if (DeviceEntryUdfpsRefactor.isEnabled()) { - if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) - && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED - || mTransitionToFullShadeProgress > 0f)) { - // Assume scrim state for shade is already correct and do nothing - } else { - // Safeguard which prevents the scrim from being stuck in the wrong state - mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); - } + if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) + && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED + || mTransitionToFullShadeProgress > 0f)) { + // Assume scrim state for shade is already correct and do nothing } else { - if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) - && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED - || mTransitionToFullShadeProgress > 0f)) { - mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE); - } else { - mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED); - } + // Safeguard which prevents the scrim from being stuck in the wrong state + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); } // This will cancel the keyguardFadingAway animation if it is running. We need to do // this as otherwise it can remain pending and leave keyguard in a weird state. @@ -3168,12 +3157,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { public void onDozeAmountChanged(float linear, float eased) { if (!lightRevealMigration() && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { - if (DeviceEntryUdfpsRefactor.isEnabled()) { - // If wakeAndUnlocking, this is handled in AuthRippleInteractor - if (!mBiometricUnlockController.isWakeAndUnlock()) { - mLightRevealScrim.setRevealAmount(1f - linear); - } - } else { + // If wakeAndUnlocking, this is handled in AuthRippleInteractor + if (!mBiometricUnlockController.isWakeAndUnlock()) { mLightRevealScrim.setRevealAmount(1f - linear); } } 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 40b8cdafa813..44d81a7abfe5 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 @@ -21,7 +21,6 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED; import static android.provider.Settings.Global.HEADS_UP_ON; -import static com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR; import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE; import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION; import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; @@ -859,34 +858,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testSetDozingNotUnlocking_transitionToAuthScrimmed_cancelKeyguardFadingAway() { - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true); - - mCentralSurfaces.updateScrimController(); - - verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE)); - verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway(); - } - - @Test - @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testOccludingQSNotExpanded_flagOff_transitionToAuthScrimmed() { - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // GIVEN device occluded and panel is NOT expanded - mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE - when(mKeyguardStateController.isOccluded()).thenReturn(true); - when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false); - - mCentralSurfaces.updateScrimController(); - - verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED)); - } - - @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void testNotOccluding_QSNotExpanded_flagOn_doesNotTransitionScrimState() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); @@ -902,7 +873,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void testNotOccluding_QSExpanded_flagOn_doesTransitionScrimStateToKeyguard() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); @@ -917,21 +887,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { verify(mScrimController, never()).legacyTransitionTo(eq(ScrimState.KEYGUARD)); } - @Test - @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testOccludingQSExpanded_transitionToAuthScrimmedShade() { - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // GIVEN device occluded and qs IS expanded - mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE - when(mKeyguardStateController.isOccluded()).thenReturn(true); - when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true); - - mCentralSurfaces.updateScrimController(); - - verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE)); - } - @Test public void testEnteringGlanceableHub_updatesScrim() { // Transition to the glanceable hub. -- GitLab From 368301d40241dec744ae10798255e340c5485659 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Tue, 15 Oct 2024 13:28:06 +0000 Subject: [PATCH 169/441] Add protologging on setRequestedVisibleTypes call Bug: 352538294 Flag: android.view.inputmethod.refactor_insets_controller Change-Id: Ide4f9b8d994904c168aab33ded589059997190d1 --- core/java/android/view/InsetsController.java | 4 ++ .../java/android/view/ViewProtoLogGroups.java | 42 +++++++++++++++++++ core/java/android/view/ViewRootImpl.java | 12 ++++++ 3 files changed, 58 insertions(+) create mode 100644 core/java/android/view/ViewProtoLogGroups.java diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index d08873c56e6a..59c66532fe0b 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -22,6 +22,7 @@ import static android.view.InsetsControllerProto.CONTROL; import static android.view.InsetsControllerProto.STATE; import static android.view.InsetsSource.ID_IME; import static android.view.InsetsSource.ID_IME_CAPTION_BAR; +import static android.view.ViewProtoLogGroups.IME_INSETS_CONTROLLER; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.LAST; import static android.view.WindowInsets.Type.all; @@ -69,6 +70,7 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.SoftInputShowHideReason; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.function.TriFunction; import java.io.PrintWriter; @@ -1920,6 +1922,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final @InsetsType int requestedVisibleTypes = (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask); if (mRequestedVisibleTypes != requestedVisibleTypes) { + ProtoLog.d(IME_INSETS_CONTROLLER, "Setting requestedVisibleTypes to %d (was %d)", + requestedVisibleTypes, mRequestedVisibleTypes); mRequestedVisibleTypes = requestedVisibleTypes; } } diff --git a/core/java/android/view/ViewProtoLogGroups.java b/core/java/android/view/ViewProtoLogGroups.java new file mode 100644 index 000000000000..099f76189a50 --- /dev/null +++ b/core/java/android/view/ViewProtoLogGroups.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.NonNull; +import android.view.inputmethod.Flags; + +import com.android.internal.protolog.ProtoLogGroup; + +import java.util.UUID; + +/** + * Defines logging groups for ProtoLog. + * + * This file is used by the ProtoLogTool to generate optimized logging code. All of its dependencies + * must be included in services.core.wm.protologgroups build target. + * + * @hide + */ +final class ViewProtoLogGroups { + final static ProtoLogGroup IME_INSETS_CONTROLLER = new ProtoLogGroup( + "IME_INSETS_CONTROLLER", "InsetsController", Flags.refactorInsetsController()); + + final static ProtoLogGroup[] ALL_GROUPS = { + IME_INSETS_CONTROLLER + }; +} + diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3be9a821a463..182ed1ebad59 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -279,6 +279,7 @@ import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneFallbackEventHandler; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FastPrintWriter; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; @@ -1282,6 +1283,8 @@ public final class ViewRootImpl implements ViewParent, mIsStylusPointerIconEnabled = InputSettings.isStylusPointerIconEnabled(mContext); + initializeProtoLogInProcess(); + String processorOverrideName = context.getResources().getString( R.string.config_inputEventCompatProcessorOverrideClassName); if (processorOverrideName.isEmpty()) { @@ -13403,4 +13406,13 @@ public final class ViewRootImpl implements ViewParent, mCurrentColorMode = colorMode; } + + private static boolean sProtoLogInitialized = false; + + private void initializeProtoLogInProcess() { + if (!sProtoLogInitialized) { + ProtoLog.init(ViewProtoLogGroups.ALL_GROUPS); + sProtoLogInitialized = true; + } + } } -- GitLab From 16a8cea63b2f51cc677824077ad6ef03a965c2d1 Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Tue, 15 Oct 2024 12:42:16 +0000 Subject: [PATCH 170/441] Remove pieces of device entry flag - Piece #5 Flag has shipped Bug: 279440316 Test: atest SystemUITests Flag: com.android.systemui.device_entry_udfps_refactor Change-Id: I46d4511ac49620b859a7e408dff3eef2363330b6 --- ...DeviceEntrySideFpsOverlayInteractorTest.kt | 42 +++++------ .../AlternateBouncerWindowViewModelTest.kt | 36 ++-------- .../repository/KeyguardBouncerRepository.kt | 15 ---- .../interactor/AlternateBouncerInteractor.kt | 72 +++---------------- .../systemui/deviceentry/DeviceEntryModule.kt | 19 +---- .../DeviceEntrySideFpsOverlayInteractor.kt | 19 +---- .../FakeKeyguardBouncerRepository.kt | 6 -- .../AlternateBouncerInteractorKosmos.kt | 24 +------ ...viceEntrySideFpsOverlayInteractorKosmos.kt | 4 +- 9 files changed, 36 insertions(+), 201 deletions(-) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt index 2a2a82d53671..b5ea305544ff 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt @@ -39,7 +39,6 @@ import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsReposi import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -100,18 +99,14 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { FakeTrustRepository(), testScope.backgroundScope, mSelectedUserInteractor, - faceAuthInteractor + faceAuthInteractor, ) alternateBouncerInteractor = AlternateBouncerInteractor( - mock(StatusBarStateController::class.java), - mock(KeyguardStateController::class.java), bouncerRepository, FakeFingerprintPropertyRepository(), - biometricSettingsRepository, FakeSystemClock(), - keyguardUpdateMonitor, { mock(DeviceEntryBiometricsAllowedInteractor::class.java) }, { mock(KeyguardInteractor::class.java) }, { mock(KeyguardTransitionInteractor::class.java) }, @@ -121,13 +116,12 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { underTest = DeviceEntrySideFpsOverlayInteractor( - testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, - keyguardUpdateMonitor + keyguardUpdateMonitor, ) } @@ -142,7 +136,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = true, isAnimatingAway = false, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isTrue() } @@ -158,7 +152,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = false, isAnimatingAway = false, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -169,13 +163,12 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { testScope.runTest { underTest = DeviceEntrySideFpsOverlayInteractor( - testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, - keyguardUpdateMonitor + keyguardUpdateMonitor, ) val showIndicatorForDeviceEntry by @@ -185,7 +178,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { updateBouncerScene( isActive = true, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isTrue() } @@ -196,13 +189,12 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { testScope.runTest { underTest = DeviceEntrySideFpsOverlayInteractor( - testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, - keyguardUpdateMonitor + keyguardUpdateMonitor, ) val showIndicatorForDeviceEntry by @@ -212,7 +204,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { updateBouncerScene( isActive = false, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -228,7 +220,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = true, isAnimatingAway = false, fpsDetectionRunning = false, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -245,7 +237,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = true, isAnimatingAway = false, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = false + isUnlockingWithFpAllowed = false, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -261,7 +253,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { updateBouncerScene( isActive = true, fpsDetectionRunning = false, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -277,7 +269,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { updateBouncerScene( isActive = true, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = false + isUnlockingWithFpAllowed = false, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -294,7 +286,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = true, isAnimatingAway = true, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -325,7 +317,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = true, isAnimatingAway = false, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) // Another request to show indicator for deviceEntryFingerprintAuthRepository update @@ -355,7 +347,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { .thenReturn(isUnlockingWithFpAllowed) mContext.orCreateTestableResources.addOverride( R.bool.config_show_sidefps_hint_on_bouncer, - true + true, ) } @@ -366,7 +358,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { ) { kosmos.sceneInteractor.changeScene( if (isActive) Scenes.Bouncer else Scenes.Lockscreen, - "reason" + "reason", ) whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) @@ -375,7 +367,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { .thenReturn(isUnlockingWithFpAllowed) mContext.orCreateTestableResources.addOverride( R.bool.config_show_sidefps_hint_on_bouncer, - true + true, ) runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt index c1bd37811787..5d9548057bae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue @@ -34,7 +33,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @ExperimentalCoroutinesApi @RunWith(AndroidJUnit4::class) @@ -49,7 +47,6 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { @Test fun alternateBouncerTransition_alternateBouncerWindowRequiredTrue() = testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) fingerprintPropertyRepository.supportsUdfps() @@ -64,28 +61,7 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { assertThat(alternateBouncerWindowRequired).isTrue() transitionRepository.sendTransitionSteps( - listOf( - stepFromAlternateBouncer(1.0f, TransitionState.FINISHED), - ), - testScope, - ) - assertThat(alternateBouncerWindowRequired).isFalse() - } - - @Test - fun deviceEntryUdfpsFlagDisabled_alternateBouncerWindowRequiredFalse() = - testScope.runTest { - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - val alternateBouncerWindowRequired by - collectLastValue(underTest.alternateBouncerWindowRequired) - fingerprintPropertyRepository.supportsUdfps() - transitionRepository.sendTransitionSteps( - listOf( - stepFromAlternateBouncer(0f, TransitionState.STARTED), - stepFromAlternateBouncer(.4f), - stepFromAlternateBouncer(.6f), - stepFromAlternateBouncer(1f), - ), + listOf(stepFromAlternateBouncer(1.0f, TransitionState.FINISHED)), testScope, ) assertThat(alternateBouncerWindowRequired).isFalse() @@ -94,7 +70,6 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { @Test fun lockscreenTransition_alternateBouncerWindowRequiredFalse() = testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) fingerprintPropertyRepository.supportsUdfps() @@ -113,7 +88,6 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { @Test fun rearFps_alternateBouncerWindowRequiredFalse() = testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) fingerprintPropertyRepository.supportsRearFps() @@ -131,7 +105,7 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { private fun stepFromAlternateBouncer( value: Float, - state: TransitionState = TransitionState.RUNNING + state: TransitionState = TransitionState.RUNNING, ): TransitionStep { return step( from = KeyguardState.ALTERNATE_BOUNCER, @@ -143,7 +117,7 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { private fun stepFromDozingToLockscreen( value: Float, - state: TransitionState = TransitionState.RUNNING + state: TransitionState = TransitionState.RUNNING, ): TransitionStep { return step( from = KeyguardState.DOZING, @@ -157,14 +131,14 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { from: KeyguardState, to: KeyguardState, value: Float, - transitionState: TransitionState + transitionState: TransitionState, ): TransitionStep { return TransitionStep( from = from, to = to, value = value, transitionState = transitionState, - ownerName = "AlternateBouncerViewModelTest" + ownerName = "AlternateBouncerViewModelTest", ) } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt index f1c3f949ffba..22b2888a51a9 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt @@ -24,7 +24,6 @@ import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.log.dagger.BouncerTableLog import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable @@ -88,7 +87,6 @@ interface KeyguardBouncerRepository { val showMessage: StateFlow val resourceUpdateRequests: StateFlow val alternateBouncerVisible: StateFlow - val alternateBouncerUIAvailable: StateFlow /** Last shown security mode of the primary bouncer (ie: pin/pattern/password/SIM) */ val lastShownSecurityMode: StateFlow @@ -126,8 +124,6 @@ interface KeyguardBouncerRepository { fun setAlternateVisible(isVisible: Boolean) - fun setAlternateBouncerUIAvailable(isAvailable: Boolean) - fun setLastShownSecurityMode(securityMode: KeyguardSecurityModel.SecurityMode) } @@ -199,9 +195,6 @@ constructor( private val _alternateBouncerVisible = MutableStateFlow(false) override val alternateBouncerVisible = _alternateBouncerVisible.asStateFlow() override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE - private val _alternateBouncerUIAvailable = MutableStateFlow(false) - override val alternateBouncerUIAvailable: StateFlow = - _alternateBouncerUIAvailable.asStateFlow() init { setUpLogging() @@ -220,11 +213,6 @@ constructor( _alternateBouncerVisible.value = isVisible } - override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - _alternateBouncerUIAvailable.value = isAvailable - } - override fun setPrimaryShow(isShowing: Boolean) { _primaryBouncerShow.value = isShowing } @@ -320,9 +308,6 @@ constructor( resourceUpdateRequests .logDiffsForTable(buffer, "", "ResourceUpdateRequests", false) .launchIn(applicationScope) - alternateBouncerUIAvailable - .logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false) - .launchIn(applicationScope) alternateBouncerVisible .logDiffsForTable(buffer, "", "AlternateBouncerVisible", false) .launchIn(applicationScope) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt index 0949ea4d7797..9c2a10a444e2 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt @@ -17,22 +17,17 @@ package com.android.systemui.bouncer.domain.interactor import android.util.Log -import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor -import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.time.SystemClock import dagger.Lazy @@ -55,13 +50,9 @@ import kotlinx.coroutines.flow.stateIn class AlternateBouncerInteractor @Inject constructor( - private val statusBarStateController: StatusBarStateController, - private val keyguardStateController: KeyguardStateController, private val bouncerRepository: KeyguardBouncerRepository, fingerprintPropertyRepository: FingerprintPropertyRepository, - private val biometricSettingsRepository: BiometricSettingsRepository, private val systemClock: SystemClock, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val deviceEntryBiometricsAllowedInteractor: Lazy, private val keyguardInteractor: Lazy, @@ -73,17 +64,10 @@ constructor( val isVisible: Flow = bouncerRepository.alternateBouncerVisible private val alternateBouncerUiAvailableFromSource: HashSet = HashSet() val alternateBouncerSupported: StateFlow = - if (DeviceEntryUdfpsRefactor.isEnabled) { - fingerprintPropertyRepository.sensorType - .map { sensorType -> sensorType.isUdfps() || sensorType.isPowerButton() } - .stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = false, - ) - } else { - bouncerRepository.alternateBouncerUIAvailable - } + fingerprintPropertyRepository.sensorType + .map { sensorType -> sensorType.isUdfps() || sensorType.isPowerButton() } + .stateIn(scope = scope, started = SharingStarted.Eagerly, initialValue = false) + private val isDozingOrAod: Flow = anyOf( keyguardTransitionInteractor.get().transitionValue(KeyguardState.DOZING).map { @@ -106,7 +90,7 @@ constructor( combine( keyguardTransitionInteractor.get().currentKeyguardState, sceneInteractor.get().currentScene, - ::Pair + ::Pair, ) .flatMapLatest { (currentKeyguardState, transitionState) -> if (currentKeyguardState == KeyguardState.GONE) { @@ -122,7 +106,7 @@ constructor( .isFingerprintAuthCurrentlyAllowed, keyguardInteractor.get().isKeyguardDismissible, bouncerRepository.primaryBouncerShow, - isDozingOrAod + isDozingOrAod, ) { fingerprintAllowed, keyguardDismissible, @@ -141,36 +125,16 @@ constructor( } .distinctUntilChanged() .onEach { Log.d(TAG, "canShowAlternateBouncer changed to $it") } - .stateIn( - scope = scope, - started = WhileSubscribed(), - initialValue = false, - ) + .stateIn(scope = scope, started = WhileSubscribed(), initialValue = false) /** * Always shows the alternate bouncer. Requesters must check [canShowAlternateBouncer]` before * calling this. */ fun forceShow() { - if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) { - show() - return - } bouncerRepository.setAlternateVisible(true) } - /** - * Sets the correct bouncer states to show the alternate bouncer if it can show. - * - * @return whether alternateBouncer is visible - * @deprecated use [forceShow] and manually check [canShowAlternateBouncer] beforehand - */ - fun show(): Boolean { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint()) - return isVisibleState() - } - /** * Sets the correct bouncer states to hide the bouncer. Should only be called through * StatusBarKeyguardViewManager until ScrimController is refactored to use @@ -189,28 +153,8 @@ constructor( return bouncerRepository.alternateBouncerVisible.value } - fun setAlternateBouncerUIAvailable(isAvailable: Boolean, token: String) { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - if (isAvailable) { - alternateBouncerUiAvailableFromSource.add(token) - } else { - alternateBouncerUiAvailableFromSource.remove(token) - } - bouncerRepository.setAlternateBouncerUIAvailable( - alternateBouncerUiAvailableFromSource.isNotEmpty() - ) - } - fun canShowAlternateBouncerForFingerprint(): Boolean { - if (DeviceEntryUdfpsRefactor.isEnabled) { - return canShowAlternateBouncer.value - } - return alternateBouncerSupported.value && - biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value && - !keyguardUpdateMonitor.isFingerprintLockedOut && - !keyguardStateController.isUnlocked && - !statusBarStateController.isDozing && - !bouncerRepository.primaryBouncerShow.value + return canShowAlternateBouncer.value } /** diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt index b8c03c071572..c464a66ea0c3 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt @@ -17,25 +17,17 @@ package com.android.systemui.deviceentry import com.android.keyguard.EmptyLockIconViewController -import com.android.keyguard.LegacyLockIconViewController import com.android.keyguard.LockIconViewController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import dagger.Lazy import dagger.Module import dagger.Provides import dagger.multibindings.Multibinds -@Module( - includes = - [ - DeviceEntryRepositoryModule::class, - FaceWakeUpTriggersConfigModule::class, - ], -) +@Module(includes = [DeviceEntryRepositoryModule::class, FaceWakeUpTriggersConfigModule::class]) abstract class DeviceEntryModule { /** * A set of DeviceEntryIconTransitions. Ensures that this can be injected even if it's empty. @@ -46,14 +38,9 @@ abstract class DeviceEntryModule { @Provides @SysUISingleton fun provideLockIconViewController( - legacyLockIconViewController: Lazy, - emptyLockIconViewController: Lazy, + emptyLockIconViewController: Lazy ): LockIconViewController { - return if (DeviceEntryUdfpsRefactor.isEnabled) { - emptyLockIconViewController.get() - } else { - legacyLockIconViewController.get() - } + return emptyLockIconViewController.get() } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt index 96260770d89f..03cf1a4b3e9a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt @@ -23,15 +23,12 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor -import com.android.systemui.keyguard.data.repository.BiometricType import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -41,7 +38,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch /** * Encapsulates business logic for device entry events that impact the side fingerprint sensor @@ -51,7 +47,6 @@ import kotlinx.coroutines.launch class DeviceEntrySideFpsOverlayInteractor @Inject constructor( - @Application private val applicationScope: CoroutineScope, @Application private val context: Context, deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, private val sceneInteractor: SceneInteractor, @@ -60,18 +55,6 @@ constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, ) { - init { - if (!DeviceEntryUdfpsRefactor.isEnabled) { - applicationScope.launch { - deviceEntryFingerprintAuthRepository.availableFpSensorType.collect { sensorType -> - if (sensorType == BiometricType.SIDE_FINGERPRINT) { - alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG) - } - } - } - } - } - private val isSideFpsIndicatorOnPrimaryBouncerEnabled: Boolean get() = context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer) @@ -90,7 +73,7 @@ constructor( primaryBouncerInteractor.startingDisappearAnimation.filterNotNull(), // Bouncer scene visibility changes. isBouncerSceneActive, - deviceEntryFingerprintAuthRepository.shouldUpdateIndicatorVisibility.filter { it } + deviceEntryFingerprintAuthRepository.shouldUpdateIndicatorVisibility.filter { it }, ) .map { isBouncerActive() && diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt index baaf60447cf9..703e2d1db200 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt @@ -49,8 +49,6 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos private val _isAlternateBouncerVisible = MutableStateFlow(false) override val alternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow() override var lastAlternateBouncerVisibleTime: Long = 0L - private val _isAlternateBouncerUIAvailable = MutableStateFlow(false) - override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow() override val lastShownSecurityMode: MutableStateFlow = MutableStateFlow(KeyguardSecurityModel.SecurityMode.Invalid) override var bouncerDismissActionModel: BouncerDismissActionModel? = null @@ -63,10 +61,6 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos _isAlternateBouncerVisible.value = isVisible } - override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) { - _isAlternateBouncerUIAvailable.value = isAvailable - } - override fun setPrimaryShow(isShowing: Boolean) { _primaryBouncerShow.value = isShowing } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt index 63323b239a78..8bbb8a0d320e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt @@ -16,33 +16,23 @@ package com.android.systemui.bouncer.domain.interactor -import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor -import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.statusbar.statusBarStateController import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.statusbar.policy.keyguardStateController -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.systemClock val Kosmos.alternateBouncerInteractor: AlternateBouncerInteractor by Kosmos.Fixture { AlternateBouncerInteractor( - statusBarStateController = statusBarStateController, - keyguardStateController = keyguardStateController, bouncerRepository = keyguardBouncerRepository, fingerprintPropertyRepository = fingerprintPropertyRepository, - biometricSettingsRepository = biometricSettingsRepository, systemClock = systemClock, - keyguardUpdateMonitor = keyguardUpdateMonitor, deviceEntryBiometricsAllowedInteractor = { deviceEntryBiometricsAllowedInteractor }, keyguardInteractor = { keyguardInteractor }, keyguardTransitionInteractor = { keyguardTransitionInteractor }, @@ -54,21 +44,9 @@ val Kosmos.alternateBouncerInteractor: AlternateBouncerInteractor by fun Kosmos.givenCanShowAlternateBouncer() { this.givenAlternateBouncerSupported() this.keyguardBouncerRepository.setPrimaryShow(false) - this.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) - this.biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true) this.deviceEntryFaceAuthRepository.setLockedOut(false) - whenever(this.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) - whenever(this.keyguardStateController.isUnlocked).thenReturn(false) } fun Kosmos.givenAlternateBouncerSupported() { - if (DeviceEntryUdfpsRefactor.isEnabled) { - this.fingerprintPropertyRepository.supportsUdfps() - } else { - this.keyguardBouncerRepository.setAlternateBouncerUIAvailable(true) - } -} - -fun Kosmos.givenCannotShowAlternateBouncer() { - this.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + this.fingerprintPropertyRepository.supportsUdfps() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorKosmos.kt index 4ccee6f52fa2..2aa27444a058 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorKosmos.kt @@ -22,18 +22,16 @@ import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor val Kosmos.deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor by Kosmos.Fixture { DeviceEntrySideFpsOverlayInteractor( - applicationScope = testScope.backgroundScope, context = applicationContext, deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository, sceneInteractor = sceneInteractor, primaryBouncerInteractor = primaryBouncerInteractor, alternateBouncerInteractor = alternateBouncerInteractor, - keyguardUpdateMonitor = keyguardUpdateMonitor + keyguardUpdateMonitor = keyguardUpdateMonitor, ) } -- GitLab From 33be6d8261b72b9fcb1925d11a8c67d6c2fc5fc9 Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Tue, 15 Oct 2024 12:44:40 +0000 Subject: [PATCH 171/441] Remove pieces of device entry flag - Final piece Flag has shipped Bug: 279440316 Test: atest SystemUITests Flag: com.android.systemui.device_entry_udfps_refactor Change-Id: I422274b0cbbe0fe4f8d58a359c5f3ebb6e3884b3 --- packages/SystemUI/Android.bp | 4 - .../res/layout/status_bar_expanded.xml | 6 - .../res/layout/udfps_keyguard_view_legacy.xml | 25 - packages/SystemUI/res/layout/udfps_view.xml | 31 - .../keyguard/EmptyLockIconViewController.kt | 8 +- .../LegacyLockIconViewController.java | 843 ------------------ .../com/android/keyguard/LockIconView.java | 263 ------ .../keyguard/LockIconViewController.kt | 8 +- .../biometrics/AuthRippleController.kt | 60 +- .../android/systemui/biometrics/UdfpsView.kt | 117 --- .../FaceHelpMessageDeferralInteractor.kt | 5 +- .../shared/DeviceEntryUdfpsRefactor.kt | 53 -- .../binder/KeyguardPreviewClockViewBinder.kt | 15 +- .../scene/shared/flag/SceneContainerFlag.kt | 5 +- .../LegacyLockIconViewControllerBaseTest.java | 245 ----- .../LegacyLockIconViewControllerTest.java | 414 --------- ...ockIconViewControllerWithCoroutinesTest.kt | 149 ---- .../biometrics/AuthRippleControllerTest.kt | 351 -------- .../systemui/biometrics/UdfpsViewTest.kt | 109 --- .../systemui/flags/EnableSceneContainer.kt | 2 - 20 files changed, 36 insertions(+), 2677 deletions(-) delete mode 100644 packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml delete mode 100644 packages/SystemUI/res/layout/udfps_view.xml delete mode 100644 packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java delete mode 100644 packages/SystemUI/src/com/android/keyguard/LockIconView.java delete mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryUdfpsRefactor.kt delete mode 100644 packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java delete mode 100644 packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java delete mode 100644 packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 28635318236d..9c8ff80bc30f 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -318,9 +318,6 @@ filegroup { "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt", "tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt", "tests/src/**/keyguard/ClockEventControllerTest.kt", - "tests/src/**/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt", - "tests/src/**/keyguard/LegacyLockIconViewControllerBaseTest.kt", - "tests/src/**/keyguard/LegacyLockIconViewControllerTest.java", "tests/src/**/systemui/animation/TransitionAnimatorTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt", @@ -417,7 +414,6 @@ filegroup { "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt", "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt", "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java", - "tests/src/**/keyguard/LegacyLockIconViewControllerBaseTest.java", "tests/src/**/keyguard/CarrierTextManagerTest.java", "tests/src/**/systemui/ScreenDecorationsTest.java", "tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt", diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index e21466671363..77fbb642f664 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -128,12 +128,6 @@ - - - diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml deleted file mode 100644 index 530d752732c1..000000000000 --- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml deleted file mode 100644 index 257d238f5c54..000000000000 --- a/packages/SystemUI/res/layout/udfps_view.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - diff --git a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt b/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt index b792db354b36..306d68217e50 100644 --- a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt +++ b/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt @@ -17,6 +17,7 @@ package com.android.keyguard import android.view.MotionEvent +import android.view.View import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.res.R @@ -34,11 +35,10 @@ import javax.inject.Inject @SysUISingleton class EmptyLockIconViewController @Inject -constructor( - private val keyguardRootView: Lazy, -) : LockIconViewController { +constructor(private val keyguardRootView: Lazy) : LockIconViewController { private val deviceEntryIconViewId = R.id.device_entry_icon_view - override fun setLockIconView(lockIconView: LockIconView) { + + override fun setLockIconView(lockIconView: View) { // no-op } diff --git a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java deleted file mode 100644 index 03b13fe47c10..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java +++ /dev/null @@ -1,843 +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.keyguard; - -import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; -import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; - -import static com.android.keyguard.LockIconView.ICON_FINGERPRINT; -import static com.android.keyguard.LockIconView.ICON_LOCK; -import static com.android.keyguard.LockIconView.ICON_UNLOCK; -import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; -import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; -import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; -import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricSourceType; -import android.os.VibrationAttributes; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.MathUtils; -import android.view.HapticFeedbackConstants; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeInfo; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; - -import com.android.systemui.Dumpable; -import com.android.systemui.biometrics.AuthController; -import com.android.systemui.biometrics.AuthRippleController; -import com.android.systemui.biometrics.UdfpsController; -import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.keyguard.shared.model.KeyguardState; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlag; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import dagger.Lazy; - -import kotlinx.coroutines.ExperimentalCoroutinesApi; - -import java.io.PrintWriter; -import java.util.Objects; -import java.util.function.Consumer; - -import javax.inject.Inject; - -/** - * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen. - * - * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock - * icon will show a set distance from the bottom of the device. - */ -@SysUISingleton -public class LegacyLockIconViewController implements Dumpable, LockIconViewController { - private static final String TAG = "LockIconViewController"; - private static final float sDefaultDensity = - (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT; - private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36); - private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); - - private static final long FADE_OUT_DURATION_MS = 250L; - - private final long mLongPressTimeout; - @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @NonNull private final KeyguardViewController mKeyguardViewController; - @NonNull private final StatusBarStateController mStatusBarStateController; - @NonNull private final KeyguardStateController mKeyguardStateController; - @NonNull private final FalsingManager mFalsingManager; - @NonNull private final AuthController mAuthController; - @NonNull private final AccessibilityManager mAccessibilityManager; - @NonNull private final ConfigurationController mConfigurationController; - @NonNull private final DelayableExecutor mExecutor; - private boolean mUdfpsEnrolled; - private Resources mResources; - private Context mContext; - @NonNull private CharSequence mUnlockedLabel; - @NonNull private CharSequence mLockedLabel; - @NonNull private final VibratorHelper mVibrator; - @Nullable private final AuthRippleController mAuthRippleController; - @NonNull private final FeatureFlags mFeatureFlags; - @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; - @NonNull private final KeyguardTransitionInteractor mTransitionInteractor; - @NonNull private final KeyguardInteractor mKeyguardInteractor; - @NonNull private final View.AccessibilityDelegate mAccessibilityDelegate; - @NonNull private final Lazy mDeviceEntryInteractor; - - // Tracks the velocity of a touch to help filter out the touches that move too fast. - private VelocityTracker mVelocityTracker; - // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. - private int mActivePointerId = -1; - - private boolean mIsDozing; - private boolean mIsActiveDreamLockscreenHosted; - private boolean mIsBouncerShowing; - private boolean mRunningFPS; - private boolean mCanDismissLockScreen; - private int mStatusBarState; - private boolean mIsKeyguardShowing; - private Runnable mLongPressCancelRunnable; - - private boolean mUdfpsSupported; - private float mHeightPixels; - private float mWidthPixels; - private int mBottomPaddingPx; - private int mDefaultPaddingPx; - - private boolean mShowUnlockIcon; - private boolean mShowLockIcon; - - // for udfps when strong auth is required or unlocked on AOD - private boolean mShowAodLockIcon; - private boolean mShowAodUnlockedIcon; - private final int mMaxBurnInOffsetX; - private final int mMaxBurnInOffsetY; - private float mInterpolatedDarkAmount; - - private boolean mDownDetected; - private final Rect mSensorTouchLocation = new Rect(); - private LockIconView mView; - - @VisibleForTesting - final Consumer mDozeTransitionCallback = (Float value) -> { - mInterpolatedDarkAmount = value; - mView.setDozeAmount(value); - updateBurnInOffsets(); - }; - - @VisibleForTesting - final Consumer mIsDozingCallback = (Boolean isDozing) -> { - mIsDozing = isDozing; - updateBurnInOffsets(); - updateVisibility(); - }; - - @VisibleForTesting - final Consumer mIsActiveDreamLockscreenHostedCallback = - (Boolean isLockscreenHosted) -> { - mIsActiveDreamLockscreenHosted = isLockscreenHosted; - updateVisibility(); - }; - - @Inject - public LegacyLockIconViewController( - @NonNull StatusBarStateController statusBarStateController, - @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, - @NonNull KeyguardViewController keyguardViewController, - @NonNull KeyguardStateController keyguardStateController, - @NonNull FalsingManager falsingManager, - @NonNull AuthController authController, - @NonNull DumpManager dumpManager, - @NonNull AccessibilityManager accessibilityManager, - @NonNull ConfigurationController configurationController, - @NonNull @Main DelayableExecutor executor, - @NonNull VibratorHelper vibrator, - @Nullable AuthRippleController authRippleController, - @NonNull @Main Resources resources, - @NonNull KeyguardTransitionInteractor transitionInteractor, - @NonNull KeyguardInteractor keyguardInteractor, - @NonNull FeatureFlags featureFlags, - PrimaryBouncerInteractor primaryBouncerInteractor, - Context context, - Lazy deviceEntryInteractor - ) { - mStatusBarStateController = statusBarStateController; - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mAuthController = authController; - mKeyguardViewController = keyguardViewController; - mKeyguardStateController = keyguardStateController; - mFalsingManager = falsingManager; - mAccessibilityManager = accessibilityManager; - mConfigurationController = configurationController; - mExecutor = executor; - mVibrator = vibrator; - mAuthRippleController = authRippleController; - mTransitionInteractor = transitionInteractor; - mKeyguardInteractor = keyguardInteractor; - mFeatureFlags = featureFlags; - mPrimaryBouncerInteractor = primaryBouncerInteractor; - - mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); - mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); - mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button); - mLockedLabel = resources.getString(R.string.accessibility_lock_icon); - mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress); - dumpManager.registerDumpable(TAG, this); - mResources = resources; - mContext = context; - mDeviceEntryInteractor = deviceEntryInteractor; - - mAccessibilityDelegate = new View.AccessibilityDelegate() { - private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint = - new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_CLICK, - mResources.getString(R.string.accessibility_authenticate_hint)); - private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityEnterHint = - new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_CLICK, - mResources.getString(R.string.accessibility_enter_hint)); - public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(v, info); - if (isActionable()) { - if (mShowLockIcon) { - info.addAction(mAccessibilityAuthenticateHint); - } else if (mShowUnlockIcon) { - info.addAction(mAccessibilityEnterHint); - } - } - } - }; - } - - /** Sets the LockIconView to the controller and rebinds any that depend on it. */ - @SuppressLint("ClickableViewAccessibility") - @Override - public void setLockIconView(LockIconView lockIconView) { - mView = lockIconView; - mView.setAccessibilityDelegate(mAccessibilityDelegate); - - if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) { - collectFlow(mView, mTransitionInteractor.transitionValue(KeyguardState.AOD), - mDozeTransitionCallback); - collectFlow(mView, mKeyguardInteractor.isDozing(), mIsDozingCallback); - } - - if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) { - collectFlow(mView, mKeyguardInteractor.isActiveDreamLockscreenHosted(), - mIsActiveDreamLockscreenHostedCallback); - } - - updateIsUdfpsEnrolled(); - updateConfiguration(); - updateKeyguardShowing(); - - mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); - mIsDozing = mStatusBarStateController.isDozing(); - mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount(); - mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); - mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); - mStatusBarState = mStatusBarStateController.getState(); - - updateColors(); - mDownDetected = false; - updateBurnInOffsets(); - updateVisibility(); - - updateAccessibility(); - - lockIconView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View view) { - registerCallbacks(); - } - - @Override - public void onViewDetachedFromWindow(View view) { - unregisterCallbacks(); - } - }); - - if (lockIconView.isAttachedToWindow()) { - registerCallbacks(); - } - - lockIconView.setOnTouchListener((view, motionEvent) -> onTouchEvent(motionEvent)); - } - - private void registerCallbacks() { - mConfigurationController.addCallback(mConfigurationListener); - mAuthController.addCallback(mAuthControllerCallback); - mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); - mStatusBarStateController.addCallback(mStatusBarStateListener); - mKeyguardStateController.addCallback(mKeyguardStateCallback); - mAccessibilityManager.addAccessibilityStateChangeListener( - mAccessibilityStateChangeListener); - - } - - private void unregisterCallbacks() { - mAuthController.removeCallback(mAuthControllerCallback); - mConfigurationController.removeCallback(mConfigurationListener); - mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); - mStatusBarStateController.removeCallback(mStatusBarStateListener); - mKeyguardStateController.removeCallback(mKeyguardStateCallback); - mAccessibilityManager.removeAccessibilityStateChangeListener( - mAccessibilityStateChangeListener); - - } - - private void updateAccessibility() { - if (mAccessibilityManager.isEnabled()) { - mView.setOnClickListener(mA11yClickListener); - } else { - mView.setOnClickListener(null); - } - } - - @Override - public float getTop() { - return mView.getLocationTop(); - } - - @Override - public float getBottom() { - return mView.getLocationBottom(); - } - - private void updateVisibility() { - if (!mIsKeyguardShowing && !mIsDozing) { - mView.setVisibility(View.INVISIBLE); - return; - } - - if (mIsKeyguardShowing && mIsActiveDreamLockscreenHosted) { - mView.setVisibility(View.INVISIBLE); - return; - } - - boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon - && !mShowAodUnlockedIcon && !mShowAodLockIcon; - mShowLockIcon = !mCanDismissLockScreen && isLockScreen() - && (!mUdfpsEnrolled || !mRunningFPS); - mShowUnlockIcon = mCanDismissLockScreen && isLockScreen(); - mShowAodUnlockedIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen; - mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen; - - 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); - } else if (mShowUnlockIcon) { - 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_UNLOCK, false); - mView.setContentDescription(mUnlockedLabel); - mView.setVisibility(View.VISIBLE); - } else if (mShowAodUnlockedIcon) { - mView.updateIcon(ICON_UNLOCK, true); - mView.setContentDescription(mUnlockedLabel); - mView.setVisibility(View.VISIBLE); - } else if (mShowAodLockIcon) { - mView.updateIcon(ICON_LOCK, true); - mView.setContentDescription(mLockedLabel); - mView.setVisibility(View.VISIBLE); - } else { - mView.clearIcon(); - mView.setVisibility(View.INVISIBLE); - mView.setContentDescription(null); - } - - boolean accessibilityEnabled = - !mPrimaryBouncerInteractor.isAnimatingAway() && mView.isVisibleToUser(); - mView.setImportantForAccessibility( - accessibilityEnabled ? View.IMPORTANT_FOR_ACCESSIBILITY_YES - : View.IMPORTANT_FOR_ACCESSIBILITY_NO); - - if (!Objects.equals(prevContentDescription, mView.getContentDescription()) - && mView.getContentDescription() != null && accessibilityEnabled) { - mView.announceForAccessibility(mView.getContentDescription()); - } - } - - private boolean isLockScreen() { - return !mIsDozing - && !mIsBouncerShowing - && mStatusBarState == StatusBarState.KEYGUARD; - } - - private void updateKeyguardShowing() { - mIsKeyguardShowing = mKeyguardStateController.isShowing() - && !mKeyguardStateController.isKeyguardGoingAway(); - } - - private void updateColors() { - mView.updateColorAndBackgroundVisibility(); - } - - private void updateConfiguration() { - WindowManager windowManager = mContext.getSystemService(WindowManager.class); - Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); - mWidthPixels = bounds.right; - if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { - // Assumed to be initially neglected as there are no left or right insets in portrait - // However, on landscape, these insets need to included when calculating the midpoint - WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets(); - mWidthPixels -= insets.getSystemWindowInsetLeft() + insets.getSystemWindowInsetRight(); - } - mHeightPixels = bounds.bottom; - mBottomPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom); - mDefaultPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_padding); - mUnlockedLabel = mResources.getString( - R.string.accessibility_unlock_button); - mLockedLabel = mResources.getString(R.string.accessibility_lock_icon); - updateLockIconLocation(); - } - - private void updateLockIconLocation() { - final float scaleFactor = mAuthController.getScaleFactor(); - final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor); - if (KeyguardBottomAreaRefactor.isEnabled() || MigrateClocksToBlueprint.isEnabled()) { - // positioning in this case is handled by [DefaultDeviceEntrySection] - mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding, - scaledPadding); - } else { - if (mUdfpsSupported) { - mView.setCenterLocation(mAuthController.getUdfpsLocation(), - mAuthController.getUdfpsRadius(), scaledPadding); - } else { - mView.setCenterLocation( - new Point((int) mWidthPixels / 2, - (int) (mHeightPixels - - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), - sLockIconRadiusPx * scaleFactor, scaledPadding); - } - } - } - - @Override - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("mUdfpsSupported: " + mUdfpsSupported); - pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled); - pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing); - pw.println(); - pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); - pw.println(" mShowLockIcon: " + mShowLockIcon); - pw.println(" mShowAodUnlockedIcon: " + mShowAodUnlockedIcon); - pw.println(); - pw.println(" mIsDozing: " + mIsDozing); - pw.println(" isFlagEnabled(DOZING_MIGRATION_1): " - + mFeatureFlags.isEnabled(DOZING_MIGRATION_1)); - pw.println(" mIsBouncerShowing: " + mIsBouncerShowing); - pw.println(" mRunningFPS: " + mRunningFPS); - pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen); - pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState)); - pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount); - pw.println(" mSensorTouchLocation: " + mSensorTouchLocation); - pw.println(" mDefaultPaddingPx: " + mDefaultPaddingPx); - pw.println(" mIsActiveDreamLockscreenHosted: " + mIsActiveDreamLockscreenHosted); - - if (mView != null) { - mView.dump(pw, args); - } - } - - /** Every minute, update the aod icon's burn in offset */ - @Override - public void dozeTimeTick() { - updateBurnInOffsets(); - } - - private void updateBurnInOffsets() { - float offsetX = MathUtils.lerp(0f, - getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) - - mMaxBurnInOffsetX, mInterpolatedDarkAmount); - float offsetY = MathUtils.lerp(0f, - getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) - - mMaxBurnInOffsetY, mInterpolatedDarkAmount); - - mView.setTranslationX(offsetX); - mView.setTranslationY(offsetY); - } - - private void updateIsUdfpsEnrolled() { - boolean wasUdfpsSupported = mUdfpsSupported; - boolean wasUdfpsEnrolled = mUdfpsEnrolled; - - mUdfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported(); - mView.setUseBackground(mUdfpsSupported); - - mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); - if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) { - updateVisibility(); - } - } - - private StatusBarStateController.StateListener mStatusBarStateListener = - new StatusBarStateController.StateListener() { - @Override - public void onDozeAmountChanged(float linear, float eased) { - if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) { - mInterpolatedDarkAmount = eased; - mView.setDozeAmount(eased); - updateBurnInOffsets(); - } - } - - @Override - public void onDozingChanged(boolean isDozing) { - if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) { - mIsDozing = isDozing; - updateBurnInOffsets(); - updateVisibility(); - } - } - - @Override - public void onStateChanged(int statusBarState) { - mStatusBarState = statusBarState; - updateVisibility(); - } - }; - - private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = - new KeyguardUpdateMonitorCallback() { - @Override - public void onKeyguardBouncerStateChanged(boolean bouncer) { - mIsBouncerShowing = bouncer; - updateVisibility(); - } - - @Override - public void onBiometricRunningStateChanged(boolean running, - BiometricSourceType biometricSourceType) { - final boolean wasRunningFps = mRunningFPS; - - if (biometricSourceType == FINGERPRINT) { - mRunningFPS = running; - } - - if (wasRunningFps != mRunningFPS) { - updateVisibility(); - } - } - }; - - private final KeyguardStateController.Callback mKeyguardStateCallback = - new KeyguardStateController.Callback() { - @Override - public void onUnlockedChanged() { - mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); - updateKeyguardShowing(); - updateVisibility(); - } - - @Override - public void onKeyguardShowingChanged() { - // Reset values in case biometrics were removed (ie: pin/pattern/password => swipe). - // If biometrics were removed, local vars mCanDismissLockScreen and - // mUserUnlockedWithBiometric may not be updated. - mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); - - // reset mIsBouncerShowing state in case it was preemptively set - // onLongPress - mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); - - updateKeyguardShowing(); - updateVisibility(); - } - - @Override - public void onKeyguardFadingAwayChanged() { - updateKeyguardShowing(); - updateVisibility(); - } - }; - - private final ConfigurationController.ConfigurationListener mConfigurationListener = - new ConfigurationController.ConfigurationListener() { - @Override - public void onUiModeChanged() { - updateColors(); - } - - @Override - public void onThemeChanged() { - updateColors(); - } - - @Override - public void onConfigChanged(Configuration newConfig) { - updateConfiguration(); - updateColors(); - } - }; - - /** - * Handles the touch if {@link #isActionable()} is true. - * Subsequently, will trigger {@link #onLongPress()} if a touch is continuously in the lock icon - * area for {@link #mLongPressTimeout} ms. - * - * Touch speed debouncing mimics logic from the velocity tracker in {@link UdfpsController}. - */ - private boolean onTouchEvent(MotionEvent event) { - if (!actionableDownEventStartedOnView(event)) { - cancelTouches(); - return false; - } - - switch(event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_HOVER_ENTER: - if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) { - vibrateOnTouchExploration(); - } - - // The pointer that causes ACTION_DOWN is always at index 0. - // We need to persist its ID to track it during ACTION_MOVE that could include - // data for many other pointers because of multi-touch support. - mActivePointerId = event.getPointerId(0); - if (mVelocityTracker == null) { - // To simplify the lifecycle of the velocity tracker, make sure it's never null - // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. - mVelocityTracker = VelocityTracker.obtain(); - } else { - // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new - // ACTION_DOWN, in that case we should just reuse the old instance. - mVelocityTracker.clear(); - } - mVelocityTracker.addMovement(event); - - mDownDetected = true; - mLongPressCancelRunnable = mExecutor.executeDelayed( - this::onLongPress, mLongPressTimeout); - break; - case MotionEvent.ACTION_MOVE: - case MotionEvent.ACTION_HOVER_MOVE: - mVelocityTracker.addMovement(event); - // Compute pointer velocity in pixels per second. - mVelocityTracker.computeCurrentVelocity(1000); - float velocity = computePointerSpeed(mVelocityTracker, - mActivePointerId); - if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS - && exceedsVelocityThreshold(velocity)) { - Log.v(TAG, "lock icon long-press rescheduled due to " - + "high pointer velocity=" + velocity); - mLongPressCancelRunnable.run(); - mLongPressCancelRunnable = mExecutor.executeDelayed( - this::onLongPress, mLongPressTimeout); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_HOVER_EXIT: - cancelTouches(); - break; - } - - return true; - } - - /** - * Calculate the pointer speed given a velocity tracker and the pointer id. - * This assumes that the velocity tracker has already been passed all relevant motion events. - */ - private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { - final float vx = tracker.getXVelocity(pointerId); - final float vy = tracker.getYVelocity(pointerId); - return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); - } - - /** - * Whether the velocity exceeds the acceptable UDFPS debouncing threshold. - */ - private static boolean exceedsVelocityThreshold(float velocity) { - return velocity > 750f; - } - - private boolean actionableDownEventStartedOnView(MotionEvent event) { - if (!isActionable()) { - return false; - } - - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - return true; - } - - return mDownDetected; - } - - @ExperimentalCoroutinesApi - @VisibleForTesting - protected void onLongPress() { - cancelTouches(); - if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { - Log.v(TAG, "lock icon long-press rejected by the falsing manager."); - return; - } - - // pre-emptively set to true to hide view - mIsBouncerShowing = true; - if (!DeviceEntryUdfpsRefactor.isEnabled() - && mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { - mAuthRippleController.showUnlockRipple(FINGERPRINT); - } - updateVisibility(); - - // play device entry haptic (consistent with UDFPS controller longpress) - vibrateOnLongPress(); - - if (SceneContainerFlag.isEnabled()) { - mDeviceEntryInteractor.get().attemptDeviceEntry(); - } else { - mKeyguardViewController.showPrimaryBouncer(/* scrim */ true); - } - } - - - private void cancelTouches() { - mDownDetected = false; - if (mLongPressCancelRunnable != null) { - mLongPressCancelRunnable.run(); - } - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - private boolean isActionable() { - if (mIsBouncerShowing) { - Log.v(TAG, "lock icon long-press ignored, bouncer already showing."); - // a long press gestures from AOD may have already triggered the bouncer to show, - // so this touch is no longer actionable - return false; - } - return mUdfpsSupported || mShowUnlockIcon; - } - - /** - * Set the alpha of this view. - */ - @Override - public void setAlpha(float alpha) { - mView.setAlpha(alpha); - } - - private void updateUdfpsConfig() { - // must be called from the main thread since it may update the views - mExecutor.execute(() -> { - updateIsUdfpsEnrolled(); - updateConfiguration(); - }); - } - - @VisibleForTesting - void vibrateOnTouchExploration() { - mVibrator.performHapticFeedback( - mView, - HapticFeedbackConstants.CONTEXT_CLICK - ); - } - - @VisibleForTesting - void vibrateOnLongPress() { - mVibrator.performHapticFeedback(mView, UdfpsController.LONG_PRESS); - } - - private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { - @Override - public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) { - if (modality == TYPE_FINGERPRINT) { - updateUdfpsConfig(); - } - } - - @Override - public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) { - if (modality == TYPE_FINGERPRINT) { - updateUdfpsConfig(); - } - } - - @Override - public void onUdfpsLocationChanged(UdfpsOverlayParams udfpsOverlayParams) { - updateUdfpsConfig(); - } - }; - - /** - * Whether the lock icon will handle a touch while dozing. - */ - @Override - public boolean willHandleTouchWhileDozing(MotionEvent event) { - // is in lock icon area - mView.getHitRect(mSensorTouchLocation); - final boolean inLockIconArea = - mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) - && mView.getVisibility() == View.VISIBLE; - - return inLockIconArea && actionableDownEventStartedOnView(event); - } - - private final View.OnClickListener mA11yClickListener = v -> onLongPress(); - - private final AccessibilityManager.AccessibilityStateChangeListener - mAccessibilityStateChangeListener = enabled -> updateAccessibility(); -} diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java deleted file mode 100644 index ff6a3d0cc6f0..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ /dev/null @@ -1,263 +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.keyguard; - -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; - -import com.android.internal.graphics.ColorUtils; -import com.android.settingslib.Utils; -import com.android.systemui.Dumpable; -import com.android.systemui.res.R; - -import java.io.PrintWriter; - -/** - * A view positioned under the notification shade. - */ -public class LockIconView extends FrameLayout implements Dumpable { - @IntDef({ICON_NONE, ICON_LOCK, ICON_FINGERPRINT, ICON_UNLOCK}) - public @interface IconType {} - - public static final int ICON_NONE = -1; - public static final int ICON_LOCK = 0; - public static final int ICON_FINGERPRINT = 1; - public static final int ICON_UNLOCK = 2; - - private @IconType int mIconType; - private boolean mAod; - - @NonNull private final RectF mSensorRect; - @NonNull private Point mLockIconCenter = new Point(0, 0); - private float mRadius; - private int mLockIconPadding; - - private ImageView mLockIcon; - private ImageView mBgView; - - private int mLockIconColor; - private boolean mUseBackground = false; - private float mDozeAmount = 0f; - - @SuppressLint("ClickableViewAccessibility") - public LockIconView(Context context, AttributeSet attrs) { - super(context, attrs); - mSensorRect = new RectF(); - - addBgImageView(context, attrs); - addLockIconImageView(context, attrs); - } - - void setDozeAmount(float dozeAmount) { - mDozeAmount = dozeAmount; - updateColorAndBackgroundVisibility(); - } - - void updateColorAndBackgroundVisibility() { - if (mUseBackground && mLockIcon.getDrawable() != null) { - mLockIconColor = ColorUtils.blendARGB( - Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary), - Color.WHITE, - mDozeAmount); - int backgroundColor = Utils.getColorAttrDefaultColor(getContext(), - com.android.internal.R.attr.colorSurface); - mBgView.setImageTintList(ColorStateList.valueOf(backgroundColor)); - mBgView.setAlpha(1f - mDozeAmount); - mBgView.setVisibility(View.VISIBLE); - } else { - mLockIconColor = ColorUtils.blendARGB( - Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColorAccent), - Color.WHITE, - mDozeAmount); - mBgView.setVisibility(View.GONE); - } - - mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor)); - } - - /** - * Whether or not to render the lock icon background. Mainly used for UDPFS. - */ - public void setUseBackground(boolean useBackground) { - mUseBackground = useBackground; - updateColorAndBackgroundVisibility(); - } - - /** - * Set the location of the lock icon. - */ - public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) { - mLockIconCenter = center; - mRadius = radius; - mLockIconPadding = drawablePadding; - - mLockIcon.setPadding(mLockIconPadding, mLockIconPadding, mLockIconPadding, - mLockIconPadding); - - mSensorRect.set(mLockIconCenter.x - mRadius, - mLockIconCenter.y - mRadius, - mLockIconCenter.x + mRadius, - mLockIconCenter.y + mRadius); - - final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); - if (lp != null) { - lp.width = (int) (mSensorRect.right - mSensorRect.left); - lp.height = (int) (mSensorRect.bottom - mSensorRect.top); - lp.topMargin = (int) mSensorRect.top; - lp.setMarginStart((int) mSensorRect.left); - setLayoutParams(lp); - } - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - float getLocationTop() { - Rect r = new Rect(); - mLockIcon.getGlobalVisibleRect(r); - return r.top; - } - - float getLocationBottom() { - Rect r = new Rect(); - mLockIcon.getGlobalVisibleRect(r); - return r.bottom; - - } - - /** - * Updates the icon its default state where no visual is shown. - */ - public void clearIcon() { - updateIcon(ICON_NONE, false); - } - - /** - * Transition the current icon to a new state - * @param icon type (ie: lock icon, unlock icon, fingerprint icon) - * @param aod whether to use the aod icon variant (some icons don't have aod variants and will - * therefore show no icon) - */ - public void updateIcon(@IconType int icon, boolean aod) { - mIconType = icon; - mAod = aod; - - mLockIcon.setImageState(getLockIconState(mIconType, mAod), true); - } - - public ImageView getLockIcon() { - return mLockIcon; - } - - private void addLockIconImageView(Context context, AttributeSet attrs) { - mLockIcon = new ImageView(context, attrs); - mLockIcon.setId(R.id.lock_icon); - mLockIcon.setScaleType(ImageView.ScaleType.CENTER_CROP); - mLockIcon.setImageDrawable(context.getDrawable(R.drawable.super_lock_icon)); - addView(mLockIcon); - LayoutParams lp = (LayoutParams) mLockIcon.getLayoutParams(); - lp.height = MATCH_PARENT; - lp.width = MATCH_PARENT; - lp.gravity = Gravity.CENTER; - mLockIcon.setLayoutParams(lp); - } - - private void addBgImageView(Context context, AttributeSet attrs) { - mBgView = new ImageView(context, attrs); - mBgView.setId(R.id.lock_icon_bg); - mBgView.setImageDrawable(context.getDrawable(R.drawable.fingerprint_bg)); - mBgView.setVisibility(View.INVISIBLE); - addView(mBgView); - LayoutParams lp = (LayoutParams) mBgView.getLayoutParams(); - lp.height = MATCH_PARENT; - lp.width = MATCH_PARENT; - mBgView.setLayoutParams(lp); - } - - private static int[] getLockIconState(@IconType int icon, boolean aod) { - if (icon == ICON_NONE) { - return new int[0]; - } - - int[] lockIconState = new int[2]; - switch (icon) { - case ICON_LOCK: - lockIconState[0] = android.R.attr.state_first; - break; - case ICON_FINGERPRINT: - lockIconState[0] = android.R.attr.state_middle; - break; - case ICON_UNLOCK: - lockIconState[0] = android.R.attr.state_last; - break; - } - - if (aod) { - lockIconState[1] = android.R.attr.state_single; - } else { - lockIconState[1] = -android.R.attr.state_single; - } - - return lockIconState; - } - - private String typeToString(@IconType int type) { - switch (type) { - case ICON_NONE: - return "none"; - case ICON_LOCK: - return "lock"; - case ICON_FINGERPRINT: - return "fingerprint"; - case ICON_UNLOCK: - return "unlock"; - } - - return "invalid"; - } - - @Override - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("Lock Icon View Parameters:"); - pw.println(" Center in px (x, y)= (" - + mLockIconCenter.x + ", " + mLockIconCenter.y + ")"); - pw.println(" Radius in pixels: " + mRadius); - pw.println(" Drawable padding: " + mLockIconPadding); - pw.println(" mIconType=" + typeToString(mIconType)); - pw.println(" mAod=" + mAod); - pw.println("Lock Icon View actual measurements:"); - pw.println(" topLeft= (" + getX() + ", " + getY() + ")"); - pw.println(" width=" + getWidth() + " height=" + getHeight()); - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt index 10d5a0cc3dd5..c5012b01dd3e 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt @@ -17,13 +17,19 @@ package com.android.keyguard import android.view.MotionEvent +import android.view.View /** Controls the [LockIconView]. */ interface LockIconViewController { - fun setLockIconView(lockIconView: LockIconView) + fun setLockIconView(lockIconView: View) + fun getTop(): Float + fun getBottom(): Float + fun dozeTimeTick() + fun setAlpha(alpha: Float) + fun willHandleTouchWhileDozing(event: MotionEvent): Boolean } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index c95a94e5e388..f6cc72431db0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -37,11 +37,9 @@ import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.log.core.LogLevel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.statusbar.CircleReveal @@ -87,7 +85,7 @@ constructor( private val lightRevealScrim: LightRevealScrim, private val authRippleInteractor: AuthRippleInteractor, private val facePropertyRepository: FacePropertyRepository, - rippleView: AuthRippleView? + rippleView: AuthRippleView?, ) : ViewController(rippleView), CoreStartable, @@ -108,15 +106,13 @@ constructor( } init { - if (DeviceEntryUdfpsRefactor.isEnabled) { - rippleView?.repeatWhenAttached { - repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) { - authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource -> - if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) { - showUnlockRippleInternal(BiometricSourceType.FINGERPRINT) - } else { - showUnlockRippleInternal(BiometricSourceType.FACE) - } + rippleView?.repeatWhenAttached { + repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) { + authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource -> + if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) { + showUnlockRippleInternal(BiometricSourceType.FINGERPRINT) + } else { + showUnlockRippleInternal(BiometricSourceType.FACE) } } } @@ -134,29 +130,8 @@ constructor( keyguardStateController.addCallback(this) wakefulnessLifecycle.addObserver(this) commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } - if (!DeviceEntryUdfpsRefactor.isEnabled) { - biometricUnlockController.addListener(biometricModeListener) - } } - private val biometricModeListener = - object : BiometricUnlockController.BiometricUnlockEventsListener { - override fun onBiometricUnlockedWithKeyguardDismissal( - biometricSourceType: BiometricSourceType? - ) { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - if (biometricSourceType != null) { - showUnlockRippleInternal(biometricSourceType) - } else { - logger.log( - TAG, - LogLevel.ERROR, - "Unexpected scenario where biometricSourceType is null" - ) - } - } - } - @VisibleForTesting public override fun onViewDetached() { udfpsController?.removeCallback(udfpsControllerCallback) @@ -166,17 +141,10 @@ constructor( keyguardStateController.removeCallback(this) wakefulnessLifecycle.removeObserver(this) commandRegistry.unregisterCommand("auth-ripple") - biometricUnlockController.removeListener(biometricModeListener) notificationShadeWindowController.setForcePluginOpen(false, this) } - @Deprecated("Update authRippleInteractor.showUnlockRipple instead of calling this.") - fun showUnlockRipple(biometricSourceType: BiometricSourceType) { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - showUnlockRippleInternal(biometricSourceType) - } - private fun showUnlockRippleInternal(biometricSourceType: BiometricSourceType) { val keyguardNotShowing = !keyguardStateController.isShowing val unlockNotAllowed = @@ -197,8 +165,8 @@ constructor( 0, Math.max( Math.max(it.x, displayMetrics.widthPixels - it.x), - Math.max(it.y, displayMetrics.heightPixels - it.y) - ) + Math.max(it.y, displayMetrics.heightPixels - it.y), + ), ) logger.showingUnlockRippleAt(it.x, it.y, "FP sensor radius: $udfpsRadius") showUnlockedRipple() @@ -213,8 +181,8 @@ constructor( 0, Math.max( Math.max(it.x, displayMetrics.widthPixels - it.x), - Math.max(it.y, displayMetrics.heightPixels - it.y) - ) + Math.max(it.y, displayMetrics.heightPixels - it.y), + ), ) logger.showingUnlockRippleAt(it.x, it.y, "Face unlock ripple") showUnlockedRipple() @@ -322,7 +290,7 @@ constructor( override fun onBiometricAuthenticated( userId: Int, biometricSourceType: BiometricSourceType, - isStrongBiometric: Boolean + isStrongBiometric: Boolean, ) { if (biometricSourceType == BiometricSourceType.FINGERPRINT) { mView.fadeDwellRipple() @@ -337,7 +305,7 @@ constructor( override fun onBiometricAcquired( biometricSourceType: BiometricSourceType, - acquireInfo: Int + acquireInfo: Int, ) { if ( biometricSourceType == BiometricSourceType.FINGERPRINT && diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt deleted file mode 100644 index 76bcd6e2863b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt +++ /dev/null @@ -1,117 +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.systemui.biometrics - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.graphics.RectF -import android.util.AttributeSet -import android.util.Log -import android.view.MotionEvent -import android.widget.FrameLayout -import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams -import com.android.systemui.doze.DozeReceiver - -private const val TAG = "UdfpsView" - -/** - * The main view group containing all UDFPS animations. - */ -class UdfpsView( - context: Context, - attrs: AttributeSet? -) : FrameLayout(context, attrs), DozeReceiver { - // sensorRect may be bigger than the sensor. True sensor dimensions are defined in - // overlayParams.sensorBounds - var sensorRect = Rect() - private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null - private val debugTextPaint = Paint().apply { - isAntiAlias = true - color = Color.BLUE - textSize = 32f - } - - /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */ - var animationViewController: UdfpsAnimationViewController<*>? = null - - /** Parameters that affect the position and size of the overlay. */ - var overlayParams = UdfpsOverlayParams() - - /** Debug message. */ - var debugMessage: String? = null - set(value) { - field = value - postInvalidate() - } - - /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */ - var isDisplayConfigured: Boolean = false - private set - - fun setUdfpsDisplayModeProvider(udfpsDisplayModeProvider: UdfpsDisplayModeProvider?) { - mUdfpsDisplayMode = udfpsDisplayModeProvider - } - - // Don't propagate any touch events to the child views. - override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { - return (animationViewController == null || !animationViewController!!.shouldPauseAuth()) - } - - override fun dozeTimeTick() { - animationViewController?.dozeTimeTick() - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - super.onLayout(changed, left, top, right, bottom) - - // Updates sensor rect in relation to the overlay view - animationViewController?.onSensorRectUpdated(RectF(sensorRect)) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - Log.v(TAG, "onAttachedToWindow") - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - Log.v(TAG, "onDetachedFromWindow") - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - if (!isDisplayConfigured) { - if (!debugMessage.isNullOrEmpty()) { - canvas.drawText(debugMessage!!, 0f, 160f, debugTextPaint) - } - } - } - - fun configureDisplay(onDisplayConfigured: Runnable) { - isDisplayConfigured = true - animationViewController?.onDisplayConfiguring() - mUdfpsDisplayMode?.enable(onDisplayConfigured) - } - - fun unconfigureDisplay() { - isDisplayConfigured = false - animationViewController?.onDisplayUnconfigured() - mUdfpsDisplayMode?.disable(null /* onDisabled */) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt index ffe392a52c9f..88daa5de8816 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt @@ -21,7 +21,6 @@ import android.hardware.face.FaceManager import com.android.systemui.biometrics.FaceHelpMessageDeferralFactory import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus import javax.inject.Inject @@ -55,9 +54,7 @@ constructor( faceAuthInteractor.authenticationStatus.filterIsInstance() init { - if (DeviceEntryUdfpsRefactor.isEnabled) { - startUpdatingFaceHelpMessageDeferral() - } + startUpdatingFaceHelpMessageDeferral() } /** diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryUdfpsRefactor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryUdfpsRefactor.kt deleted file mode 100644 index b5d5803ca6fb..000000000000 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryUdfpsRefactor.kt +++ /dev/null @@ -1,53 +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.deviceentry.shared - -import com.android.systemui.Flags -import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils - -/** Helper for reading or using the device entry udfps refactor flag state. */ -@Suppress("NOTHING_TO_INLINE") -object DeviceEntryUdfpsRefactor { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR - - /** A token used for dependency declaration */ - val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) - - /** Is the refactor enabled */ - @JvmStatic - inline val isEnabled - get() = Flags.deviceEntryUdfpsRefactor() - - /** - * Called to ensure code is only run when the flag is enabled. This protects users from the - * unintended behaviors caused by accidentally running new logic, while also crashing on an eng - * build to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) - - /** - * Called to ensure code is only run when the flag is disabled. This will throw an exception if - * the flag is enabled to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt index 2d225562081a..e52ba4f8a51b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt @@ -129,15 +129,19 @@ object KeyguardPreviewClockViewBinder { constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0) val largeClockTopMargin = SystemBarUtils.getStatusBarHeight(context) + - context.resources.getDimensionPixelSize( - customR.dimen.small_clock_padding_top - ) + + context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) + context.resources.getDimensionPixelSize( R.dimen.keyguard_smartspace_top_offset ) + getDimen(context, DATE_WEATHER_VIEW_HEIGHT) + getDimen(context, ENHANCED_SMARTSPACE_HEIGHT) - connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin) + connect( + customR.id.lockscreen_clock_view_large, + TOP, + PARENT_ID, + TOP, + largeClockTopMargin, + ) connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START) connect( customR.id.lockscreen_clock_view_large, @@ -146,12 +150,11 @@ object KeyguardPreviewClockViewBinder { ConstraintSet.END, ) - // In preview, we'll show UDFPS icon for UDFPS devices and nothing for non-UDFPS // devices, but we need position of device entry icon to constrain clock if (getConstraint(lockId) != null) { connect(customR.id.lockscreen_clock_view_large, BOTTOM, lockId, TOP) - } else { + } else { // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection val bottomPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt index 7b6b0f614cc2..6097ef53f8df 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt @@ -20,7 +20,6 @@ package com.android.systemui.scene.shared.flag import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.Flags.sceneContainer -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils import com.android.systemui.keyguard.KeyguardBottomAreaRefactor @@ -42,8 +41,7 @@ object SceneContainerFlag { KeyguardWmStateRefactor.isEnabled && MigrateClocksToBlueprint.isEnabled && NotificationThrottleHun.isEnabled && - PredictiveBackSysUiFlag.isEnabled && - DeviceEntryUdfpsRefactor.isEnabled + PredictiveBackSysUiFlag.isEnabled // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer @@ -58,7 +56,6 @@ object SceneContainerFlag { MigrateClocksToBlueprint.token, NotificationThrottleHun.token, PredictiveBackSysUiFlag.token, - DeviceEntryUdfpsRefactor.token, // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer ) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java deleted file mode 100644 index c51aa04fc7b6..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java +++ /dev/null @@ -1,245 +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.keyguard; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; -import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; -import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.AnimatedStateListDrawable; -import android.util.Pair; -import android.view.View; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; -import android.widget.ImageView; - -import com.android.systemui.Flags; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.biometrics.AuthController; -import com.android.systemui.biometrics.AuthRippleController; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; -import com.android.systemui.doze.util.BurnInHelperKt; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; -import com.android.systemui.kosmos.KosmosJavaAdapter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlag; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.After; -import org.junit.Before; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.MockitoSession; -import org.mockito.quality.Strictness; - -public class LegacyLockIconViewControllerBaseTest extends SysuiTestCase { - protected static final String UNLOCKED_LABEL = "unlocked"; - protected static final String LOCKED_LABEL = "locked"; - protected static final int PADDING = 10; - - protected MockitoSession mStaticMockSession; - - protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); - protected @Mock DeviceEntryInteractor mDeviceEntryInteractor; - protected @Mock LockIconView mLockIconView; - protected @Mock ImageView mLockIcon; - protected @Mock AnimatedStateListDrawable mIconDrawable; - protected @Mock Context mContext; - protected @Mock Resources mResources; - protected @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager; - protected @Mock StatusBarStateController mStatusBarStateController; - protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; - protected @Mock KeyguardViewController mKeyguardViewController; - protected @Mock KeyguardStateController mKeyguardStateController; - protected @Mock FalsingManager mFalsingManager; - protected @Mock AuthController mAuthController; - protected @Mock DumpManager mDumpManager; - protected @Mock AccessibilityManager mAccessibilityManager; - protected @Mock ConfigurationController mConfigurationController; - protected @Mock VibratorHelper mVibrator; - protected @Mock AuthRippleController mAuthRippleController; - protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock()); - protected FakeFeatureFlags mFeatureFlags; - - protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor; - - protected LegacyLockIconViewController mUnderTest; - - // Capture listeners so that they can be used to send events - @Captor protected ArgumentCaptor mAttachCaptor = - ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); - - @Captor protected ArgumentCaptor mKeyguardStateCaptor = - ArgumentCaptor.forClass(KeyguardStateController.Callback.class); - protected KeyguardStateController.Callback mKeyguardStateCallback; - - @Captor protected ArgumentCaptor mStatusBarStateCaptor = - ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); - protected StatusBarStateController.StateListener mStatusBarStateListener; - - @Captor protected ArgumentCaptor mAuthControllerCallbackCaptor; - protected AuthController.Callback mAuthControllerCallback; - - @Captor protected ArgumentCaptor - mKeyguardUpdateMonitorCallbackCaptor = - ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); - protected KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback; - - @Captor protected ArgumentCaptor mPointCaptor; - - @Before - public void setUp() throws Exception { - mStaticMockSession = mockitoSession() - .mockStatic(BurnInHelperKt.class) - .strictness(Strictness.LENIENT) - .startMocking(); - MockitoAnnotations.initMocks(this); - - setupLockIconViewMocks(); - when(mContext.getResources()).thenReturn(mResources); - when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager); - Rect windowBounds = new Rect(0, 0, 800, 1200); - when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds); - when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL); - when(mResources.getString(R.string.accessibility_lock_icon)).thenReturn(LOCKED_LABEL); - when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable); - when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING); - when(mAuthController.getScaleFactor()).thenReturn(1f); - - when(mKeyguardStateController.isShowing()).thenReturn(true); - when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false); - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); - - if (!SceneContainerFlag.isEnabled()) { - mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); - //TODO move this to use @DisableFlags annotation if needed - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - } - - mFeatureFlags = new FakeFeatureFlags(); - mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); - mFeatureFlags.set(LOCKSCREEN_ENABLE_LANDSCAPE, false); - - mUnderTest = new LegacyLockIconViewController( - mStatusBarStateController, - mKeyguardUpdateMonitor, - mKeyguardViewController, - mKeyguardStateController, - mFalsingManager, - mAuthController, - mDumpManager, - mAccessibilityManager, - mConfigurationController, - mDelayableExecutor, - mVibrator, - mAuthRippleController, - mResources, - mKosmos.getKeyguardTransitionInteractor(), - KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(), - mFeatureFlags, - mPrimaryBouncerInteractor, - mContext, - () -> mDeviceEntryInteractor - ); - } - - @After - public void tearDown() { - mStaticMockSession.finishMocking(); - } - - protected Pair setupUdfps() { - when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true); - final Point udfpsLocation = new Point(50, 75); - final float radius = 33f; - when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation); - when(mAuthController.getUdfpsRadius()).thenReturn(radius); - - return new Pair(radius, udfpsLocation); - } - - protected void setupShowLockIcon() { - when(mKeyguardStateController.isShowing()).thenReturn(true); - when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false); - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarStateController.getDozeAmount()).thenReturn(0f); - when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false); - } - - protected void captureAuthControllerCallback() { - verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture()); - mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue(); - } - - protected void captureKeyguardStateCallback() { - verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture()); - mKeyguardStateCallback = mKeyguardStateCaptor.getValue(); - } - - protected void captureStatusBarStateListener() { - verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture()); - mStatusBarStateListener = mStatusBarStateCaptor.getValue(); - } - - protected void captureKeyguardUpdateMonitorCallback() { - verify(mKeyguardUpdateMonitor).registerCallback( - mKeyguardUpdateMonitorCallbackCaptor.capture()); - mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue(); - } - - protected void setupLockIconViewMocks() { - when(mLockIconView.getResources()).thenReturn(mResources); - when(mLockIconView.getContext()).thenReturn(mContext); - when(mLockIconView.getLockIcon()).thenReturn(mLockIcon); - } - - protected void resetLockIconView() { - reset(mLockIconView); - setupLockIconViewMocks(); - } - - protected void init(boolean useDozeMigrationFlag) { - mFeatureFlags.set(DOZING_MIGRATION_1, useDozeMigrationFlag); - mUnderTest.setLockIconView(mLockIconView); - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java deleted file mode 100644 index c1ba39e89cf9..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java +++ /dev/null @@ -1,414 +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 com.android.keyguard; - -import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; - -import static com.android.keyguard.LockIconView.ICON_LOCK; -import static com.android.keyguard.LockIconView.ICON_UNLOCK; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.graphics.Point; -import android.hardware.biometrics.BiometricSourceType; -import android.testing.TestableLooper; -import android.util.Pair; -import android.view.HapticFeedbackConstants; -import android.view.View; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.biometrics.UdfpsController; -import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; -import com.android.systemui.doze.util.BurnInHelperKt; -import com.android.systemui.flags.EnableSceneContainer; -import com.android.systemui.statusbar.StatusBarState; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -@TestableLooper.RunWithLooper -public class LegacyLockIconViewControllerTest extends LegacyLockIconViewControllerBaseTest { - - @Override - public void setUp() throws Exception { - super.setUp(); - when(mLockIconView.isAttachedToWindow()).thenReturn(true); - } - - @Test - public void testUpdateFingerprintLocationOnInit() { - // GIVEN fp sensor location is available pre-attached - Pair udfps = setupUdfps(); // first = radius, second = udfps location - - // WHEN lock icon view controller is initialized and attached - init(/* useMigrationFlag= */false); - - // THEN lock icon view location is updated to the udfps location with UDFPS radius - verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), - eq(PADDING)); - } - - @Test - public void testUpdatePaddingBasedOnResolutionScale() { - // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5 - Pair udfps = setupUdfps(); // first = radius, second = udfps location - when(mAuthController.getScaleFactor()).thenReturn(5f); - - // WHEN lock icon view controller is initialized and attached - init(/* useMigrationFlag= */false); - - // THEN lock icon view location is updated with the scaled radius - verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), - eq(PADDING * 5)); - } - - @Test - public void testUpdateLockIconLocationOnAuthenticatorsRegistered() { - // GIVEN fp sensor location is not available pre-init - when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); - when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); - init(/* useMigrationFlag= */false); - resetLockIconView(); // reset any method call counts for when we verify method calls later - - // GIVEN fp sensor location is available post-attached - captureAuthControllerCallback(); - Pair udfps = setupUdfps(); - - // WHEN all authenticators are registered - mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT); - mDelayableExecutor.runAllReady(); - - // THEN lock icon view location is updated with the same coordinates as auth controller vals - verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), - eq(PADDING)); - } - - @Test - public void testUpdateLockIconLocationOnUdfpsLocationChanged() { - // GIVEN fp sensor location is not available pre-init - when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); - when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); - init(/* useMigrationFlag= */false); - resetLockIconView(); // reset any method call counts for when we verify method calls later - - // GIVEN fp sensor location is available post-attached - captureAuthControllerCallback(); - Pair udfps = setupUdfps(); - - // WHEN udfps location changes - mAuthControllerCallback.onUdfpsLocationChanged(new UdfpsOverlayParams()); - mDelayableExecutor.runAllReady(); - - // THEN lock icon view location is updated with the same coordinates as auth controller vals - verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), - eq(PADDING)); - } - - @Test - public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() { - // GIVEN Udpfs sensor location is available - setupUdfps(); - - // WHEN the view is attached - init(/* useMigrationFlag= */false); - - // THEN the lock icon view background should be enabled - verify(mLockIconView).setUseBackground(true); - } - - @Test - public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() { - // GIVEN Udfps sensor location is not supported - when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); - - // WHEN the view is attached - init(/* useMigrationFlag= */false); - - // THEN the lock icon view background should be disabled - verify(mLockIconView).setUseBackground(false); - } - - @Test - public void testLockIconStartState() { - // GIVEN lock icon state - setupShowLockIcon(); - - // WHEN lock icon controller is initialized - init(/* useMigrationFlag= */false); - - // THEN the lock icon should show - verify(mLockIconView).updateIcon(ICON_LOCK, false); - } - - @Test - public void testLockIcon_updateToUnlock() { - // GIVEN starting state for the lock icon - setupShowLockIcon(); - - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureKeyguardStateCallback(); - reset(mLockIconView); - - // WHEN the unlocked state changes to canDismissLockScreen=true - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - mKeyguardStateCallback.onUnlockedChanged(); - - // THEN the unlock should show - verify(mLockIconView).updateIcon(ICON_UNLOCK, false); - } - - @Test - public void testLockIcon_clearsIconWhenUnlocked() { - // GIVEN udfps not enrolled - setupUdfps(); - when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false); - - // GIVEN starting state for the lock icon - setupShowLockIcon(); - when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); - - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureStatusBarStateListener(); - reset(mLockIconView); - - // WHEN the dozing state changes - mStatusBarStateListener.onDozingChanged(false /* isDozing */); - - // THEN the icon is cleared - verify(mLockIconView).clearIcon(); - } - - @Test - public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() { - // GIVEN udfps enrolled - setupUdfps(); - when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); - - // GIVEN starting state for the lock icon - setupShowLockIcon(); - - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureStatusBarStateListener(); - reset(mLockIconView); - - // WHEN the dozing state changes - mStatusBarStateListener.onDozingChanged(true /* isDozing */); - - // THEN the AOD lock icon should show - verify(mLockIconView).updateIcon(ICON_LOCK, true); - } - - @Test - public void testBurnInOffsetsUpdated_onDozeAmountChanged() { - // GIVEN udfps enrolled - setupUdfps(); - when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); - - // GIVEN burn-in offset = 5 - int burnInOffset = 5; - when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset); - - // GIVEN starting state for the lock icon (keyguard) - setupShowLockIcon(); - init(/* useMigrationFlag= */false); - captureStatusBarStateListener(); - reset(mLockIconView); - - // WHEN dozing updates - mStatusBarStateListener.onDozingChanged(true /* isDozing */); - mStatusBarStateListener.onDozeAmountChanged(1f, 1f); - - // THEN the view's translation is updated to use the AoD burn-in offsets - verify(mLockIconView).setTranslationY(burnInOffset); - verify(mLockIconView).setTranslationX(burnInOffset); - reset(mLockIconView); - - // WHEN the device is no longer dozing - mStatusBarStateListener.onDozingChanged(false /* isDozing */); - mStatusBarStateListener.onDozeAmountChanged(0f, 0f); - - // THEN the view is updated to NO translation (no burn-in offsets anymore) - verify(mLockIconView).setTranslationY(0); - verify(mLockIconView).setTranslationX(0); - } - - @Test - public void lockIconShows_afterUnlockStateChanges() { - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureKeyguardStateCallback(); - captureKeyguardUpdateMonitorCallback(); - - // GIVEN user has unlocked with a biometric auth (ie: face auth) - // and biometric running state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); - mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, - BiometricSourceType.FACE); - reset(mLockIconView); - - // WHEN the unlocked state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); - mKeyguardStateCallback.onUnlockedChanged(); - - // THEN the lock icon is shown - verify(mLockIconView).setContentDescription(LOCKED_LABEL); - } - - @Test - public void lockIconAccessibility_notVisibleToUser() { - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureKeyguardStateCallback(); - captureKeyguardUpdateMonitorCallback(); - - // GIVEN user has unlocked with a biometric auth (ie: face auth) - // and biometric running state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); - mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, - BiometricSourceType.FACE); - reset(mLockIconView); - when(mLockIconView.isVisibleToUser()).thenReturn(false); - - // WHEN the unlocked state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); - mKeyguardStateCallback.onUnlockedChanged(); - - // THEN the lock icon is shown - verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - - @Test - public void lockIconAccessibility_bouncerAnimatingAway() { - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureKeyguardStateCallback(); - captureKeyguardUpdateMonitorCallback(); - - // GIVEN user has unlocked with a biometric auth (ie: face auth) - // and biometric running state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); - mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, - BiometricSourceType.FACE); - reset(mLockIconView); - when(mLockIconView.isVisibleToUser()).thenReturn(true); - when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true); - - // WHEN the unlocked state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); - mKeyguardStateCallback.onUnlockedChanged(); - - // THEN the lock icon is shown - verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - - @Test - public void lockIconAccessibility_bouncerNotAnimatingAway_viewVisible() { - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureKeyguardStateCallback(); - captureKeyguardUpdateMonitorCallback(); - - // GIVEN user has unlocked with a biometric auth (ie: face auth) - // and biometric running state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); - mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, - BiometricSourceType.FACE); - reset(mLockIconView); - when(mLockIconView.isVisibleToUser()).thenReturn(true); - when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(false); - - // WHEN the unlocked state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); - mKeyguardStateCallback.onUnlockedChanged(); - - // THEN the lock icon is shown - verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - @Test - public void playHaptic_onTouchExploration_performHapticFeedback() { - // WHEN request to vibrate on touch exploration - mUnderTest.vibrateOnTouchExploration(); - - // THEN performHapticFeedback is used - verify(mVibrator).performHapticFeedback(any(), eq(HapticFeedbackConstants.CONTEXT_CLICK)); - } - - @Test - public void playHaptic_onLongPress_performHapticFeedback() { - // WHEN request to vibrate on long press - mUnderTest.vibrateOnLongPress(); - - // THEN uses perform haptic feedback - verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); - } - - @Test - public void longPress_showBouncer_sceneContainerNotEnabled() { - init(/* useMigrationFlag= */ false); - when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false); - - // WHEN longPress - mUnderTest.onLongPress(); - - // THEN show primary bouncer via keyguard view controller, not scene container - verify(mKeyguardViewController).showPrimaryBouncer(anyBoolean()); - verify(mDeviceEntryInteractor, never()).attemptDeviceEntry(); - } - - @Test - @EnableSceneContainer - public void longPress_showBouncer() { - init(/* useMigrationFlag= */ false); - when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false); - - // WHEN longPress - mUnderTest.onLongPress(); - - // THEN show primary bouncer - verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean()); - verify(mDeviceEntryInteractor).attemptDeviceEntry(); - } - - @Test - @EnableSceneContainer - public void longPress_falsingTriggered_doesNotShowBouncer() { - init(/* useMigrationFlag= */ false); - when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true); - - // WHEN longPress - mUnderTest.onLongPress(); - - // THEN don't show primary bouncer - verify(mDeviceEntryInteractor, never()).attemptDeviceEntry(); - verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt deleted file mode 100644 index 2fd3cb0f0592..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt +++ /dev/null @@ -1,149 +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.keyguard - -import android.view.View -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.keyguard.LockIconView.ICON_LOCK -import com.android.systemui.doze.util.getBurnInOffset -import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED -import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.util.mockito.whenever -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.anyBoolean -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.reset -import org.mockito.Mockito.verify - -@RunWith(AndroidJUnit4::class) -@SmallTest -class LegacyLockIconViewControllerWithCoroutinesTest : LegacyLockIconViewControllerBaseTest() { - - /** After migration, replaces LockIconViewControllerTest version */ - @Test - fun testLockIcon_clearsIconWhenUnlocked() = - runBlocking(IMMEDIATE) { - // GIVEN udfps not enrolled - setupUdfps() - whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false) - - // GIVEN starting state for the lock icon - setupShowLockIcon() - whenever(mStatusBarStateController.state).thenReturn(StatusBarState.SHADE) - - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */ true) - reset(mLockIconView) - - // WHEN the dozing state changes - mUnderTest.mIsDozingCallback.accept(false) - // THEN the icon is cleared - verify(mLockIconView).clearIcon() - } - - /** After migration, replaces LockIconViewControllerTest version */ - @Test - fun testLockIcon_updateToAodLock_whenUdfpsEnrolled() = - runBlocking(IMMEDIATE) { - // GIVEN udfps enrolled - setupUdfps() - whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true) - - // GIVEN starting state for the lock icon - setupShowLockIcon() - - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */ true) - reset(mLockIconView) - - // WHEN the dozing state changes - mUnderTest.mIsDozingCallback.accept(true) - - // THEN the AOD lock icon should show - verify(mLockIconView).updateIcon(ICON_LOCK, true) - } - - /** After migration, replaces LockIconViewControllerTest version */ - @Test - fun testBurnInOffsetsUpdated_onDozeAmountChanged() = - runBlocking(IMMEDIATE) { - // GIVEN udfps enrolled - setupUdfps() - whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true) - - // GIVEN burn-in offset = 5 - val burnInOffset = 5 - whenever(getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset) - - // GIVEN starting state for the lock icon (keyguard) - setupShowLockIcon() - init(/* useMigrationFlag= */ true) - reset(mLockIconView) - - // WHEN dozing updates - mUnderTest.mIsDozingCallback.accept(true) - mUnderTest.mDozeTransitionCallback.accept(1f) - - // THEN the view's translation is updated to use the AoD burn-in offsets - verify(mLockIconView).setTranslationY(burnInOffset.toFloat()) - verify(mLockIconView).setTranslationX(burnInOffset.toFloat()) - reset(mLockIconView) - - // WHEN the device is no longer dozing - mUnderTest.mIsDozingCallback.accept(false) - mUnderTest.mDozeTransitionCallback.accept(0f) - - // THEN the view is updated to NO translation (no burn-in offsets anymore) - verify(mLockIconView).setTranslationY(0f) - verify(mLockIconView).setTranslationX(0f) - } - - @Test - fun testHideLockIconView_onLockscreenHostedDreamStateChanged() = - runBlocking(IMMEDIATE) { - // GIVEN starting state for the lock icon (keyguard) and wallpaper dream enabled - mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, true) - setupShowLockIcon() - init(/* useMigrationFlag= */ true) - reset(mLockIconView) - - // WHEN dream starts - mUnderTest.mIsActiveDreamLockscreenHostedCallback.accept( - true /* isActiveDreamLockscreenHosted */ - ) - - // THEN the lock icon is hidden - verify(mLockIconView).visibility = View.INVISIBLE - reset(mLockIconView) - - // WHEN the device is no longer dreaming - mUnderTest.mIsActiveDreamLockscreenHostedCallback.accept( - false /* isActiveDreamLockscreenHosted */ - ) - - // THEN lock icon is visible - verify(mLockIconView).visibility = View.VISIBLE - } - - companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt deleted file mode 100644 index 6dc4b10a57da..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ /dev/null @@ -1,351 +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 com.android.systemui.biometrics - -import android.graphics.Point -import android.hardware.biometrics.BiometricSourceType -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal -import android.testing.TestableLooper.RunWithLooper -import android.util.DisplayMetrics -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.keyguard.logging.KeyguardLogger -import com.android.systemui.Flags -import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository -import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor -import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.log.logcatLogBuffer -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.LightRevealScrim -import com.android.systemui.statusbar.NotificationShadeWindowController -import com.android.systemui.statusbar.commandline.CommandRegistry -import com.android.systemui.statusbar.phone.BiometricUnlockController -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.leak.RotationUtils -import com.android.systemui.util.mockito.any -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.After -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers -import org.mockito.ArgumentMatchers.eq -import org.mockito.Captor -import org.mockito.Mock -import org.mockito.Mockito.never -import org.mockito.Mockito.reset -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations -import org.mockito.MockitoSession -import org.mockito.quality.Strictness -import javax.inject.Provider - - -@ExperimentalCoroutinesApi -@SmallTest -@RunWith(AndroidJUnit4::class) -class AuthRippleControllerTest : SysuiTestCase() { - private lateinit var staticMockSession: MockitoSession - - private lateinit var controller: AuthRippleController - @Mock private lateinit var rippleView: AuthRippleView - @Mock private lateinit var commandRegistry: CommandRegistry - @Mock private lateinit var configurationController: ConfigurationController - @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock private lateinit var authController: AuthController - @Mock private lateinit var authRippleInteractor: AuthRippleInteractor - @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock - private lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock - private lateinit var notificationShadeWindowController: NotificationShadeWindowController - @Mock - private lateinit var biometricUnlockController: BiometricUnlockController - @Mock - private lateinit var udfpsControllerProvider: Provider - @Mock - private lateinit var udfpsController: UdfpsController - @Mock - private lateinit var statusBarStateController: StatusBarStateController - @Mock - private lateinit var lightRevealScrim: LightRevealScrim - @Mock - private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal - - private val facePropertyRepository = FakeFacePropertyRepository() - private val displayMetrics = DisplayMetrics() - - @Captor - private lateinit var biometricUnlockListener: - ArgumentCaptor - - @Before - fun setUp() { - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - MockitoAnnotations.initMocks(this) - staticMockSession = mockitoSession() - .mockStatic(RotationUtils::class.java) - .strictness(Strictness.LENIENT) - .startMocking() - - `when`(RotationUtils.getRotation(context)).thenReturn(RotationUtils.ROTATION_NONE) - `when`(authController.udfpsProps).thenReturn(listOf(fpSensorProp)) - `when`(udfpsControllerProvider.get()).thenReturn(udfpsController) - - controller = AuthRippleController( - context, - authController, - configurationController, - keyguardUpdateMonitor, - keyguardStateController, - wakefulnessLifecycle, - commandRegistry, - notificationShadeWindowController, - udfpsControllerProvider, - statusBarStateController, - displayMetrics, - KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)), - biometricUnlockController, - lightRevealScrim, - authRippleInteractor, - facePropertyRepository, - rippleView, - ) - controller.init() - } - - @After - fun tearDown() { - staticMockSession.finishMocking() - } - - @Test - fun testFingerprintTrigger_KeyguardShowing_Ripple() { - // GIVEN fp exists, keyguard is showing, unlocking with fp allowed - val fpsLocation = Point(5, 5) - `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) - controller.onViewAttached() - `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) - - // WHEN fingerprint authenticated - verify(biometricUnlockController).addListener(biometricUnlockListener.capture()) - biometricUnlockListener.value - .onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType.FINGERPRINT) - - // THEN update sensor location and show ripple - verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) - verify(rippleView).startUnlockedRipple(any()) - } - - @Test - fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() { - // GIVEN fp exists & unlocking with fp allowed - val fpsLocation = Point(5, 5) - `when`(authController.udfpsLocation).thenReturn(fpsLocation) - controller.onViewAttached() - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) - - // WHEN keyguard is NOT showing & fingerprint authenticated - `when`(keyguardStateController.isShowing).thenReturn(false) - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) - - // THEN no ripple - verify(rippleView, never()).startUnlockedRipple(any()) - } - - @Test - fun testFingerprintTrigger_biometricUnlockNotAllowed_NoRipple() { - // GIVEN fp exists & keyguard is showing - val fpsLocation = Point(5, 5) - `when`(authController.udfpsLocation).thenReturn(fpsLocation) - controller.onViewAttached() - `when`(keyguardStateController.isShowing).thenReturn(true) - - // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT))).thenReturn(false) - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) - - // THEN no ripple - verify(rippleView, never()).startUnlockedRipple(any()) - } - - @Test - fun testNullFaceSensorLocationDoesNothing() { - facePropertyRepository.setSensorLocation(null) - controller.onViewAttached() - - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FACE /* type */, - false /* isStrongBiometric */) - verify(rippleView, never()).startUnlockedRipple(any()) - } - - @Test - fun testNullFingerprintSensorLocationDoesNothing() { - `when`(authController.fingerprintSensorLocation).thenReturn(null) - controller.onViewAttached() - - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) - verify(rippleView, never()).startUnlockedRipple(any()) - } - - @Test - fun registersAndDeregisters() { - controller.onViewAttached() - val captor = ArgumentCaptor - .forClass(KeyguardStateController.Callback::class.java) - verify(keyguardStateController).addCallback(captor.capture()) - val captor2 = ArgumentCaptor - .forClass(WakefulnessLifecycle.Observer::class.java) - verify(wakefulnessLifecycle).addObserver(captor2.capture()) - controller.onViewDetached() - verify(keyguardStateController).removeCallback(any()) - verify(wakefulnessLifecycle).removeObserver(any()) - } - - @Test - @RunWithLooper(setAsMainLooper = true) - fun testAnimatorRunWhenWakeAndUnlock_fingerprint() { - mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION) - val fpsLocation = Point(5, 5) - `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) - controller.onViewAttached() - `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - BiometricSourceType.FINGERPRINT)).thenReturn(true) - `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) - - controller.showUnlockRipple(BiometricSourceType.FINGERPRINT) - assertTrue("reveal didn't start on keyguardFadingAway", - controller.startLightRevealScrimOnKeyguardFadingAway) - `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true) - controller.onKeyguardFadingAwayChanged() - assertFalse("reveal triggers multiple times", - controller.startLightRevealScrimOnKeyguardFadingAway) - } - - @Test - @RunWithLooper(setAsMainLooper = true) - fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() { - mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION) - val faceLocation = Point(5, 5) - facePropertyRepository.setSensorLocation(faceLocation) - controller.onViewAttached() - `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) - `when`(authController.isUdfpsFingerDown).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FACE))).thenReturn(true) - - controller.showUnlockRipple(BiometricSourceType.FACE) - assertTrue("reveal didn't start on keyguardFadingAway", - controller.startLightRevealScrimOnKeyguardFadingAway) - `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true) - controller.onKeyguardFadingAwayChanged() - assertFalse("reveal triggers multiple times", - controller.startLightRevealScrimOnKeyguardFadingAway) - } - - @Test - fun testUpdateRippleColor() { - controller.onViewAttached() - val captor = ArgumentCaptor - .forClass(ConfigurationController.ConfigurationListener::class.java) - verify(configurationController).addCallback(captor.capture()) - - reset(rippleView) - captor.value.onThemeChanged() - verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt()) - - reset(rippleView) - captor.value.onUiModeChanged() - verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt()) - } - - @Test - fun testUdfps_onFingerDown_runningForDeviceEntry_showDwellRipple() { - // GIVEN fingerprint detection is running on keyguard - `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true) - - // GIVEN view is already attached - controller.onViewAttached() - val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java) - verify(udfpsController).addCallback(captor.capture()) - - // GIVEN fp is updated to Point(5, 5) - val fpsLocation = Point(5, 5) - `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) - - // WHEN finger is down - captor.value.onFingerDown() - - // THEN update sensor location and show ripple - verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) - verify(rippleView).startDwellRipple(false) - } - - @Test - fun testUdfps_onFingerDown_notDeviceEntry_doesNotShowDwellRipple() { - // GIVEN fingerprint detection is NOT running on keyguard - `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(false) - - // GIVEN view is already attached - controller.onViewAttached() - val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java) - verify(udfpsController).addCallback(captor.capture()) - - // WHEN finger is down - captor.value.onFingerDown() - - // THEN doesn't show dwell ripple - verify(rippleView, never()).startDwellRipple(false) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt deleted file mode 100644 index 9fbe09619ff1..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ /dev/null @@ -1,109 +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 com.android.systemui.biometrics - -import android.hardware.biometrics.SensorLocationInternal -import android.testing.TestableLooper -import android.testing.ViewUtils -import android.view.LayoutInflater -import android.view.Surface -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.res.R -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor -import com.google.common.truth.Truth.assertThat -import org.junit.After -import org.junit.Before -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.Mockito.nullable -import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnit - -private const val SENSOR_X = 50 -private const val SENSOR_Y = 250 -private const val SENSOR_RADIUS = 10 - -@SmallTest -@RunWith(AndroidJUnit4::class) -@TestableLooper.RunWithLooper -class UdfpsViewTest : SysuiTestCase() { - - @JvmField @Rule - var rule = MockitoJUnit.rule() - - @Mock - lateinit var hbmProvider: UdfpsDisplayModeProvider - @Mock - lateinit var animationViewController: UdfpsAnimationViewController - - private lateinit var view: UdfpsView - - @Before - fun setup() { - context.setTheme(androidx.appcompat.R.style.Theme_AppCompat) - view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView - view.animationViewController = animationViewController - val sensorBounds = SensorLocationInternal("", SENSOR_X, SENSOR_Y, SENSOR_RADIUS).rect - view.overlayParams = UdfpsOverlayParams(sensorBounds, sensorBounds, 1920, - 1080, 1f, Surface.ROTATION_0) - view.setUdfpsDisplayModeProvider(hbmProvider) - ViewUtils.attachView(view) - } - - @After - fun cleanup() { - ViewUtils.detachView(view) - } - - // TODO: Add test to verify view is size of screen - - @Test - fun startAndStopIllumination() { - val onDone: Runnable = mock() - view.configureDisplay(onDone) - - val illuminator = withArgCaptor { - verify(hbmProvider).enable(capture()) - } - - assertThat(view.isDisplayConfigured).isTrue() - verify(animationViewController).onDisplayConfiguring() - verify(animationViewController, never()).onDisplayUnconfigured() - verify(onDone, never()).run() - - // fake illumination event - illuminator.run() - waitForLooper() - verify(onDone).run() - verify(hbmProvider, never()).disable(any()) - - view.unconfigureDisplay() - assertThat(view.isDisplayConfigured).isFalse() - verify(animationViewController).onDisplayUnconfigured() - verify(hbmProvider).disable(nullable(Runnable::class.java)) - } - - private fun waitForLooper() = TestableLooper.get(this).processAllMessages() -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt index c0152b26d7a3..41402badf141 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt @@ -17,7 +17,6 @@ package com.android.systemui.flags import android.platform.test.annotations.EnableFlags -import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT @@ -36,7 +35,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, FLAG_PREDICTIVE_BACK_SYSUI, FLAG_SCENE_CONTAINER, - FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, ) @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) -- GitLab From ee47f86be8d44ee2a98e0d26b4af6d480c62195f Mon Sep 17 00:00:00 2001 From: Peter Kalauskas Date: Thu, 26 Sep 2024 14:31:49 -0700 Subject: [PATCH 172/441] Coroutine tracing improvements Replace stack-walking implementation in RepeatWhenAttached with the one in the tracinglib library. Also, name a few of the flows manually. Test: Run demo app, capture trace, look for coroutine IDs Flag: com.android.systemui.coroutine_tracing Bug: 289353932 Change-Id: Iba51f42e2403fe3f4538929413b222c9f571b955 --- .../NotificationListViewModelTest.kt | 2 +- .../viewmodel/AuthMethodBouncerViewModel.kt | 2 +- .../data/repository/UsageStatsRepository.kt | 2 +- .../domain/interactor/CommunalInteractor.kt | 2 +- .../interactor/CommunalSceneInteractor.kt | 2 +- .../binder/CommunalAppWidgetHostViewBinder.kt | 4 +- .../viewmodel/ResizeableItemFrameViewModel.kt | 2 +- .../communal/util/WidgetViewFactory.kt | 2 +- .../widgets/WidgetInteractionHandler.kt | 2 +- .../android/systemui/coroutines/Tracing.kt | 27 ++++++++ .../data/repository/DisplayScopeRepository.kt | 4 +- .../homecontrols/HomeControlsDreamService.kt | 5 +- .../dagger/ContextualEducationModule.kt | 6 +- .../compose/ui/SliderHapticsViewModel.kt | 2 +- .../keyguard/CustomizationProvider.kt | 2 +- .../KeyguardTransitionRepository.kt | 2 +- .../interactor/FromAodTransitionInteractor.kt | 2 +- .../FromDreamingTransitionInteractor.kt | 2 +- .../FromGlanceableHubTransitionInteractor.kt | 2 +- .../FromGoneTransitionInteractor.kt | 2 +- .../FromLockscreenTransitionInteractor.kt | 2 +- .../KeyguardQuickAffordanceInteractor.kt | 2 +- .../AlternateBouncerMessageAreaViewBinder.kt | 2 +- .../binder/AlternateBouncerUdfpsViewBinder.kt | 2 +- .../ui/binder/AlternateBouncerViewBinder.kt | 2 +- .../ui/binder/DeviceEntryIconViewBinder.kt | 2 +- .../ui/binder/KeyguardBlueprintViewBinder.kt | 2 +- .../ui/binder/KeyguardBottomAreaViewBinder.kt | 2 +- .../ui/binder/KeyguardIndicationAreaBinder.kt | 2 +- .../ui/binder/KeyguardLongPressViewBinder.kt | 2 +- .../binder/KeyguardPreviewClockViewBinder.kt | 2 +- .../KeyguardPreviewSmartspaceViewBinder.kt | 2 +- .../KeyguardQuickAffordanceViewBinder.kt | 2 +- .../ui/binder/KeyguardRootViewBinder.kt | 2 +- .../ui/binder/KeyguardSettingsViewBinder.kt | 2 +- .../ui/binder/KeyguardSmartspaceViewBinder.kt | 2 +- .../binder/KeyguardSurfaceBehindViewBinder.kt | 2 +- .../ui/binder/LightRevealScrimViewBinder.kt | 2 +- ...owManagerLockscreenVisibilityViewBinder.kt | 2 +- .../ui/preview/KeyguardPreviewRenderer.kt | 4 +- .../preview/KeyguardRemotePreviewManager.kt | 2 +- ...yguardQuickAffordancesCombinedViewModel.kt | 2 + .../android/systemui/lifecycle/Hydrator.kt | 2 +- .../systemui/lifecycle/RepeatWhenAttached.kt | 62 +++---------------- .../MediaProjectionAppSelectorComponent.kt | 8 ++- .../gestural/domain/GestureInteractor.kt | 2 +- .../systemui/notetask/NoteTaskController.kt | 2 +- .../viewmodel/QSTileCoroutineScopeFactory.kt | 6 +- .../qs/tiles/dialog/InternetDialogManager.kt | 4 +- .../LocationTileUserActionInteractor.kt | 10 +-- .../interactor/ModesTileDataInteractor.kt | 4 +- .../saver/domain/DataSaverDialogDelegate.kt | 7 +-- .../ui/viewmodel/SceneContainerViewModel.kt | 2 +- .../systemui/screenshot/ActionExecutor.kt | 2 +- .../screenshot/ActionIntentExecutor.kt | 2 +- .../screenshot/ScreenshotProxyService.kt | 2 +- .../screenshot/ScreenshotSoundController.kt | 2 +- .../domain/interactor/ShadeInteractorImpl.kt | 6 ++ .../ui/viewmodel/ShadeUserActionsViewModel.kt | 2 +- .../ui/viewmodel/EmptyShadeViewModel.kt | 2 +- .../NotificationScrollViewBinder.kt | 30 ++++----- .../SharedNotificationContainerViewModel.kt | 10 +++ .../phone/KeyguardBypassController.kt | 2 +- .../ui/viewmodel/MobileIconsViewModel.kt | 6 +- .../util/kotlin/GlobalCoroutinesModule.kt | 11 ++-- .../systemui/util/settings/SettingsProxy.kt | 36 ++++++----- .../util/settings/UserSettingsProxy.kt | 34 +++++----- .../VolumeDialogSettingsButtonInteractor.kt | 4 +- 68 files changed, 197 insertions(+), 185 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index 8d678ef00b4a..bf144729dea3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -21,7 +21,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest -import com.android.app.tracing.coroutines.flow.map import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.DisableSceneContainer @@ -51,6 +50,7 @@ import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 4185aed3095c..148b9ea61e2c 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.bouncer.ui.viewmodel import android.annotation.StringRes -import com.android.app.tracing.coroutines.flow.collectLatest import com.android.systemui.authentication.domain.interactor.AuthenticationResult import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor @@ -28,6 +27,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow sealed class AuthMethodBouncerViewModel( diff --git a/packages/SystemUI/src/com/android/systemui/common/usagestats/data/repository/UsageStatsRepository.kt b/packages/SystemUI/src/com/android/systemui/common/usagestats/data/repository/UsageStatsRepository.kt index e3f1174d4a5f..a695163ed155 100644 --- a/packages/SystemUI/src/com/android/systemui/common/usagestats/data/repository/UsageStatsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/usagestats/data/repository/UsageStatsRepository.kt @@ -19,7 +19,7 @@ package com.android.systemui.common.usagestats.data.repository import android.app.usage.UsageEvents import android.app.usage.UsageEventsQuery import android.app.usage.UsageStatsManager -import com.android.app.tracing.coroutines.withContext +import com.android.app.tracing.coroutines.withContextTraced as withContext import com.android.systemui.common.usagestats.data.model.UsageStatsQuery import com.android.systemui.common.usagestats.shared.model.ActivityEventModel import com.android.systemui.common.usagestats.shared.model.ActivityEventModel.Lifecycle diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 3a04d026ee84..dc248059b3d4 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -23,7 +23,7 @@ import android.content.pm.UserInfo import android.os.UserHandle import android.os.UserManager import android.provider.Settings -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index 3826fb40ea3d..428b83ddbb3a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -16,7 +16,7 @@ package com.android.systemui.communal.domain.interactor -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt index ba96f4e56421..71bfe0c057e4 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt @@ -24,8 +24,7 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.compose.ui.unit.IntSize import androidx.core.view.doOnLayout -import com.android.app.tracing.coroutines.flow.flowOn -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalWidgetResizing import com.android.systemui.common.ui.view.onLayoutChanged import com.android.systemui.communal.domain.model.CommunalContentModel @@ -41,6 +40,7 @@ import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn object CommunalAppWidgetHostViewBinder { private const val TAG = "CommunalAppWidgetHostViewBinder" diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt index 87fcdd7b97ee..a519649a8f1c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt @@ -19,7 +19,7 @@ package com.android.systemui.communal.ui.viewmodel import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.runtime.snapshotFlow -import com.android.app.tracing.coroutines.coroutineScope +import com.android.app.tracing.coroutines.coroutineScopeTraced as coroutineScope import com.android.systemui.lifecycle.ExclusiveActivatable import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt index 07a7c7cba2fd..d5d3a927181b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt @@ -19,7 +19,7 @@ package com.android.systemui.communal.util import android.content.Context import android.os.Bundle import android.util.SizeF -import com.android.app.tracing.coroutines.withContext +import com.android.app.tracing.coroutines.withContextTraced as withContext import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate import com.android.systemui.communal.widgets.CommunalAppWidgetHost diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt index 542b98896986..c894267a61dd 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt @@ -21,7 +21,7 @@ import android.app.PendingIntent import android.content.Intent import android.view.View import android.widget.RemoteViews -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Flags.communalWidgetTrampolineFix import com.android.systemui.animation.ActivityTransitionAnimator diff --git a/packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt b/packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt new file mode 100644 index 000000000000..5b1c9c8b8020 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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.coroutines + +import com.android.app.tracing.coroutines.createCoroutineTracingContext +import kotlin.coroutines.CoroutineContext + +fun newTracingContext(name: String): CoroutineContext { + return createCoroutineTracingContext(name) { className -> + className.startsWith("com.android.systemui.util.kotlin.JavaAdapter") || + className.startsWith("com.android.systemui.lifecycle.RepeatWhenAttached") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt index 30624757ebe3..e3fce0007f26 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt @@ -17,8 +17,8 @@ package com.android.systemui.display.data.repository import android.view.Display -import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.systemui.CoreStartable +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.core.StatusBarConnectedDisplays @@ -69,7 +69,7 @@ constructor( backgroundApplicationScope } else { CoroutineScope( - backgroundDispatcher + createCoroutineTracingContext("DisplayScope$displayId") + backgroundDispatcher + newTracingContext("DisplayScope$displayId") ) } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt index 3992c3fb70b0..724f1c5323cf 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt @@ -22,6 +22,7 @@ import android.service.controls.ControlsProviderService import android.service.dreams.DreamService import android.window.TaskFragmentInfo import com.android.systemui.controls.settings.ControlsSettingsRepository +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dreams.DreamLogger import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor @@ -39,7 +40,6 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import com.android.app.tracing.coroutines.createCoroutineTracingContext class HomeControlsDreamService @Inject @@ -54,7 +54,8 @@ constructor( ) : DreamService() { private val serviceJob = SupervisorJob() - private val serviceScope = CoroutineScope(bgDispatcher + serviceJob + createCoroutineTracingContext("HomeControlsDreamService")) + private val serviceScope = + CoroutineScope(bgDispatcher + serviceJob + newTracingContext("HomeControlsDreamService")) private val logger = DreamLogger(logBuffer, TAG) private lateinit var taskFragmentComponent: TaskFragmentComponent private val wakeLock: WakeLock by lazy { diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt index 4caf95b707b1..7fa7da192ad0 100644 --- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt +++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt @@ -16,10 +16,10 @@ package com.android.systemui.education.dagger -import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.systemui.CoreStartable import com.android.systemui.Flags import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.data.repository.ContextualEducationRepository import com.android.systemui.education.data.repository.UserContextualEducationRepository @@ -57,7 +57,9 @@ interface ContextualEducationModule { fun provideEduDataStoreScope( @Background bgDispatcher: CoroutineDispatcher ): CoroutineScope { - return CoroutineScope(bgDispatcher + SupervisorJob() + createCoroutineTracingContext("EduDataStoreScope")) + return CoroutineScope( + bgDispatcher + SupervisorJob() + newTracingContext("EduDataStoreScope") + ) } @EduClock diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt index e396767ecf8e..1dbcb3dfe399 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt @@ -22,7 +22,7 @@ import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.unit.Velocity -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig import com.android.systemui.haptics.slider.SliderDragVelocityProvider import com.android.systemui.haptics.slider.SliderEventType diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt index 6df8355550a0..a94df091230d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -30,7 +30,7 @@ import android.net.Uri import android.os.Binder import android.os.Bundle import android.util.Log -import com.android.app.tracing.coroutines.runBlocking +import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking import com.android.systemui.SystemUIAppComponentFactoryBase import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback import com.android.systemui.dagger.qualifiers.Main diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index 690ae71aa56e..b7d0d453779f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -24,7 +24,7 @@ import android.annotation.SuppressLint import android.os.Trace import android.util.Log import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.withContext +import com.android.app.tracing.coroutines.withContextTraced as withContext import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.shared.model.KeyguardState diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 4cf9ec8890d4..9896365abff9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import android.util.Log import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 9a0a85823929..7b757657b1d9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -20,7 +20,7 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.DreamManager import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index 6b6a3dce630a..a6f0db5a86aa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index 58c8a0456241..606a7a988970 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 6d1d9cbd9aae..4d3727657f3e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import android.util.MathUtils import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.dagger.SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 2c3b481b9e16..26bf26b34a8a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -22,7 +22,7 @@ import android.app.admin.DevicePolicyManager import android.content.Context import android.content.Intent import android.util.Log -import com.android.app.tracing.coroutines.withContext +import com.android.app.tracing.coroutines.withContextTraced as withContext import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.widget.LockPatternUtils import com.android.keyguard.logging.KeyguardQuickAffordancesLogger diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt index fe5f632c0b6a..23b7b664d4f1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.ui.binder import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.AuthKeyguardMessageArea import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel import com.android.systemui.lifecycle.repeatWhenAttached diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt index 7ca2c2004452..6ef9863f1112 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt @@ -21,7 +21,7 @@ import android.content.res.ColorStateList import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index 1891af2d04b0..7a368999ecc9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -29,7 +29,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index a3f3342b9a6c..6c104a0a2470 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -28,7 +28,7 @@ import androidx.compose.ui.graphics.toArgb import androidx.core.view.isInvisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.common.ui.view.LongPressHandlingView import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 0470e086ad27..5bad0168fe05 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -22,7 +22,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.shared.model.KeyguardBlueprint 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 660a650fb916..3bdf7dac75b3 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 @@ -36,7 +36,7 @@ import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.settingslib.Utils import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.Expandable diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index ba94f45528c7..8b947a3bcb1e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -22,7 +22,7 @@ import android.view.ViewGroup import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel 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 76d3389c25b4..2fd9818a38f0 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 @@ -22,7 +22,7 @@ import android.view.accessibility.AccessibilityNodeInfo import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.common.ui.view.LongPressHandlingView import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel import com.android.systemui.lifecycle.repeatWhenAttached diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt index 2d225562081a..210f4cdaaaf9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt @@ -33,7 +33,7 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.policy.SystemBarUtils import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.shared.model.ClockSizeSetting diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt index 4b75b804e52b..baa681282a0b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt @@ -22,7 +22,7 @@ import android.view.View import androidx.core.view.isInvisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel import com.android.systemui.lifecycle.repeatWhenAttached diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index 27dd18d2b24e..cfd6481234ed 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -30,7 +30,7 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.logging.KeyguardQuickAffordancesLogger import com.android.settingslib.Utils import com.android.systemui.animation.Expandable diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index ea70fd02eed5..89bca0c6a373 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -39,7 +39,7 @@ import androidx.activity.setViewTreeOnBackPressedDispatcherOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD import com.android.keyguard.AuthInteractionProperties diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt index 4150ceb8aa31..79360370d937 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt @@ -22,7 +22,7 @@ import android.view.View import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.common.ui.binder.TextViewBinder diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt index 8b74f5dc791d..de4a1b03203c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt @@ -21,7 +21,7 @@ import androidx.constraintlayout.helper.widget.Layer import androidx.constraintlayout.widget.ConstraintLayout import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt index fd27dc39299b..3934917d13c4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt @@ -16,7 +16,7 @@ package com.android.systemui.keyguard.ui.binder -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt index b2ee68967878..2df17c39d90d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.ui.binder import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.LightRevealScrim diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt index ae46dd3a6ef3..b1ce47ee79e6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt @@ -16,7 +16,7 @@ package com.android.systemui.keyguard.ui.binder -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index dd8980dabb23..08d35a72ab12 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -47,7 +47,6 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.core.view.isInvisible -import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.internal.policy.SystemBarUtils import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch @@ -57,6 +56,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -189,7 +189,7 @@ constructor( CoroutineScope( applicationScope.coroutineContext + Job() + - createCoroutineTracingContext("KeyguardPreviewRenderer") + newTracingContext("KeyguardPreviewRenderer") ) disposables += DisposableHandle { coroutineScope.cancel() } clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData()) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt index 075a1d2893f7..9355200daec7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt @@ -25,7 +25,7 @@ import android.os.Messenger import android.util.ArrayMap import android.util.Log import androidx.annotation.VisibleForTesting -import com.android.app.tracing.coroutines.runBlocking +import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt index 0d55709e94d6..f765e60c1c70 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.annotation.VisibleForTesting import com.android.app.tracing.FlowTracing.traceEmissionCount +import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor @@ -141,6 +142,7 @@ constructor( fadeInAlpha, fadeOutAlpha, ) + .flowName("transitionAlpha") .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt index df1394bbfa5f..7c02f28c0696 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt @@ -19,7 +19,7 @@ package com.android.systemui.lifecycle import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshots.StateFactoryMarker -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.coroutines.traceCoroutine import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt index 555969859a1f..a86bfb18fb70 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt @@ -17,7 +17,6 @@ package com.android.systemui.lifecycle -import android.os.Trace import android.view.View import android.view.ViewTreeObserver import androidx.annotation.MainThread @@ -25,11 +24,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.lifecycleScope -import com.android.app.tracing.coroutines.createCoroutineTracingContext -import com.android.app.tracing.coroutines.traceCoroutine -import com.android.systemui.Flags.coroutineTracing +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.util.Assert -import com.android.systemui.util.Compile import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import kotlin.coroutines.CoroutineContext @@ -83,25 +79,13 @@ fun View.repeatWhenAttached( // default behavior. Instead, we want it to run on the view's UI thread since the user will // presumably want to call view methods that require being called from said UI thread. val lifecycleCoroutineContext = MAIN_DISPATCHER_SINGLETON + coroutineContext - val traceName = - if (Compile.IS_DEBUG && coroutineTracing()) { - inferTraceSectionName() - } else { - DEFAULT_TRACE_NAME - } var lifecycleOwner: ViewLifecycleOwner? = null val onAttachListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { Assert.isMainThread() lifecycleOwner?.onDestroy() - lifecycleOwner = - createLifecycleOwnerAndRun( - traceName, - view, - lifecycleCoroutineContext, - block, - ) + lifecycleOwner = createLifecycleOwnerAndRun(view, lifecycleCoroutineContext, block) } override fun onViewDetachedFromWindow(v: View) { @@ -112,13 +96,7 @@ fun View.repeatWhenAttached( addOnAttachStateChangeListener(onAttachListener) if (view.isAttachedToWindow) { - lifecycleOwner = - createLifecycleOwnerAndRun( - traceName, - view, - lifecycleCoroutineContext, - block, - ) + lifecycleOwner = createLifecycleOwnerAndRun(view, lifecycleCoroutineContext, block) } return DisposableHandle { @@ -131,14 +109,15 @@ fun View.repeatWhenAttached( } private fun createLifecycleOwnerAndRun( - nameForTrace: String, view: View, coroutineContext: CoroutineContext, block: suspend LifecycleOwner.(View) -> Unit, ): ViewLifecycleOwner { return ViewLifecycleOwner(view).apply { onCreate() - lifecycleScope.launch(coroutineContext) { traceCoroutine(nameForTrace) { block(view) } } + // TODO(b/370595466): Refactor to support installing CoroutineTracingContext on the + // top-level CoroutineScope used as the lifecycleScope + lifecycleScope.launch(coroutineContext) { block(view) } } } @@ -167,9 +146,7 @@ private fun createLifecycleOwnerAndRun( * └───────────────┴───────────────────┴──────────────┴─────────────────┘ * ``` */ -class ViewLifecycleOwner( - private val view: View, -) : LifecycleOwner { +class ViewLifecycleOwner(private val view: View) : LifecycleOwner { private val windowVisibleListener = ViewTreeObserver.OnWindowVisibilityChangeListener { updateState() } @@ -205,28 +182,6 @@ class ViewLifecycleOwner( } } -private fun isFrameInteresting(frame: StackWalker.StackFrame): Boolean = - frame.className != CURRENT_CLASS_NAME && frame.className != JAVA_ADAPTER_CLASS_NAME - -/** Get a name for the trace section include the name of the call site. */ -private fun inferTraceSectionName(): String { - try { - Trace.traceBegin(Trace.TRACE_TAG_APP, "RepeatWhenAttachedKt#inferTraceSectionName") - val interestingFrame = - StackWalker.getInstance().walk { stream -> - stream.filter(::isFrameInteresting).limit(5).findFirst() - } - return if (interestingFrame.isPresent) { - val f = interestingFrame.get() - "${f.className}#${f.methodName}:${f.lineNumber} [$DEFAULT_TRACE_NAME]" - } else { - DEFAULT_TRACE_NAME - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_APP) - } -} - /** * Runs the given [block] in a new coroutine when `this` [View]'s Window's [WindowLifecycleState] is * at least at [state] (or immediately after calling this function if the window is already at least @@ -368,8 +323,7 @@ private val ViewTreeObserver.isWindowVisible * an extension function, and plumbing dagger-injected instances for static usage has little * benefit. */ -private val MAIN_DISPATCHER_SINGLETON = - Dispatchers.Main + createCoroutineTracingContext("RepeatWhenAttached") +private val MAIN_DISPATCHER_SINGLETON = Dispatchers.Main + newTracingContext("RepeatWhenAttached") private const val DEFAULT_TRACE_NAME = "repeatWhenAttached" private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt" private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt" diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 544dbddeb3f0..f40ad065e5fc 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -16,13 +16,13 @@ package com.android.systemui.mediaprojection.appselector -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.app.Activity import android.content.ComponentName import android.content.Context import android.os.UserHandle import androidx.lifecycle.DefaultLifecycleObserver import com.android.launcher3.icons.IconFactory +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader @@ -134,7 +134,11 @@ interface MediaProjectionAppSelectorModule { @MediaProjectionAppSelector @MediaProjectionAppSelectorScope fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope = - CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("MediaProjectionAppSelectorScope")) + CoroutineScope( + applicationScope.coroutineContext + + SupervisorJob() + + newTracingContext("MediaProjectionAppSelectorScope") + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt index 96386e520d5a..0166176721c3 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.navigationbar.gestural.domain -import com.android.app.tracing.coroutines.flow.flowOn import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -35,6 +34,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 3aa9daac4866..d0f6f7961889 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -37,7 +37,7 @@ import android.os.UserManager import android.provider.Settings import android.widget.Toast import androidx.annotation.VisibleForTesting -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt index b8f4ab40bb1d..dde36289f139 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt @@ -16,7 +16,7 @@ package com.android.systemui.qs.tiles.base.viewmodel -import com.android.app.tracing.coroutines.createCoroutineTracingContext +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -28,5 +28,7 @@ class QSTileCoroutineScopeFactory constructor(@Application private val applicationScope: CoroutineScope) { fun create(): CoroutineScope = - CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("QSTileScope")) + CoroutineScope( + applicationScope.coroutineContext + SupervisorJob() + newTracingContext("QSTileScope") + ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt index ae56c2aad4e9..f6749715e1fd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt @@ -15,12 +15,12 @@ */ package com.android.systemui.qs.tiles.dialog -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.util.Log import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.phone.SystemUIDialog @@ -63,7 +63,7 @@ constructor( } return } else { - coroutineScope = CoroutineScope(bgDispatcher + createCoroutineTracingContext("InternetDialogScope")) + coroutineScope = CoroutineScope(bgDispatcher + newTracingContext("InternetDialogScope")) dialog = dialogFactory .create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt index ac75932b8fee..14115444fe49 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt @@ -16,9 +16,9 @@ package com.android.systemui.qs.tiles.impl.location.domain.interactor -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.content.Intent import android.provider.Settings +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.ActivityStarter @@ -53,9 +53,11 @@ constructor( val wasEnabled: Boolean = input.data.isEnabled if (keyguardController.isMethodSecure() && keyguardController.isShowing()) { activityStarter.postQSRunnableDismissingKeyguard { - CoroutineScope(applicationScope.coroutineContext + createCoroutineTracingContext("LocationTileScope")).launch { - locationController.setLocationEnabled(!wasEnabled) - } + CoroutineScope( + applicationScope.coroutineContext + + newTracingContext("LocationTileScope") + ) + .launch { locationController.setLocationEnabled(!wasEnabled) } } } else { withContext(coroutineContext) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt index 40591bf56e0a..cc14e71986f5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.tiles.impl.modes.domain.interactor import android.content.Context import android.os.UserHandle -import com.android.app.tracing.coroutines.flow.map +import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.asIcon import com.android.systemui.dagger.qualifiers.Background @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map class ModesTileDataInteractor @Inject @@ -59,6 +60,7 @@ constructor( fun tileData() = zenModeInteractor.activeModes .map { activeModes -> buildTileData(activeModes) } + .flowName("tileData") .flowOn(bgDispatcher) .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt index 468e180a6e41..f03c7521931c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt @@ -16,12 +16,12 @@ package com.android.systemui.qs.tiles.impl.saver.domain -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.content.Context import android.content.DialogInterface import android.content.SharedPreferences import android.os.Bundle import com.android.internal.R +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.DataSaverController @@ -45,9 +45,8 @@ class DataSaverDialogDelegate( setTitle(R.string.data_saver_enable_title) setMessage(R.string.data_saver_description) setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ -> - CoroutineScope(backgroundContext + createCoroutineTracingContext("DataSaverDialogScope")).launch { - dataSaverController.setDataSaverEnabled(true) - } + CoroutineScope(backgroundContext + newTracingContext("DataSaverDialogScope")) + .launch { dataSaverController.setDataSaverEnabled(true) } sharedPreferences .edit() diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index ce942fefa393..47254775618c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -19,7 +19,7 @@ package com.android.systemui.scene.ui.viewmodel import android.view.MotionEvent import android.view.View import androidx.compose.runtime.getValue -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.DefaultEdgeDetector import com.android.compose.animation.scene.ObservableTransitionState diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt index 54e0319da58f..17b1b6d91229 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt @@ -26,7 +26,7 @@ import android.os.UserHandle import android.util.Log import android.util.Pair import android.view.Window -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.app.ChooserActivity import com.android.systemui.dagger.qualifiers.Application import dagger.assisted.Assisted diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt index 9e622801a01b..7b01c36489fb 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -31,7 +31,7 @@ import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationTarget import android.view.WindowManager import android.view.WindowManagerGlobal -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.infra.ServiceConnector import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt index c8067df114fc..6df22f036273 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt @@ -21,7 +21,7 @@ import android.os.RemoteException import android.util.Log import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shade.ShadeExpansionStateManager diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt index d3a7fc4a3e4a..7aeec47241cb 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt @@ -18,7 +18,7 @@ package com.android.systemui.screenshot import android.media.MediaPlayer import android.util.Log -import com.android.app.tracing.coroutines.async +import com.android.app.tracing.coroutines.asyncTraced as async import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt index 949d2aa36bf3..460bfbbcb3ab 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade.domain.interactor +import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -62,17 +63,20 @@ constructor( override val isShadeEnabled: StateFlow = disableFlagsRepository.disableFlags .map { it.isShadeEnabled() } + .flowName("isShadeEnabled") .stateIn(scope, SharingStarted.Eagerly, initialValue = false) override val isQsEnabled: StateFlow = disableFlagsRepository.disableFlags .map { it.isQuickSettingsEnabled() } + .flowName("isQsEnabled") .stateIn(scope, SharingStarted.Eagerly, initialValue = false) override val isAnyFullyExpanded: StateFlow = anyExpansion .map { it >= 1f } .distinctUntilChanged() + .flowName("isAnyFullyExpanded") .stateIn(scope, SharingStarted.Eagerly, initialValue = false) override val isShadeFullyExpanded: Flow = @@ -81,6 +85,7 @@ constructor( override val isShadeAnyExpanded: StateFlow = baseShadeInteractor.shadeExpansion .map { it > 0 } + .flowName("isShadeAnyExpanded") .stateIn(scope, SharingStarted.Eagerly, false) override val isShadeFullyCollapsed: Flow = @@ -88,6 +93,7 @@ constructor( override val isUserInteracting: StateFlow = combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs } + .flowName("isUserInteracting") .stateIn(scope, SharingStarted.Eagerly, false) override val isShadeTouchable: Flow = diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt index 3113dc462e6a..4bdd36773655 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade.ui.viewmodel -import com.android.app.tracing.coroutines.flow.map import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction @@ -33,6 +32,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map /** * Models the UI state for the user actions that the user can perform to navigate to other scenes. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt index 695e088b5f8b..fbec6406e9d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel import android.content.Context import android.icu.text.MessageFormat -import com.android.app.tracing.coroutines.flow.flowOn import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager import com.android.systemui.modes.shared.ModesUi @@ -40,6 +39,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index 87d70ba12012..fb42ee7908b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -17,7 +17,8 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import android.util.Log -import com.android.app.tracing.coroutines.flow.filter +import com.android.app.tracing.coroutines.flow.collectTraced +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.view.onLayoutChanged import com.android.systemui.dagger.SysUISingleton @@ -37,7 +38,6 @@ import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.launch /** Binds the [NotificationScrollView]. */ @SysUISingleton @@ -79,39 +79,39 @@ constructor( launch { viewModel .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset) - .collect { view.setScrimClippingShape(it) } + .collectTraced { view.setScrimClippingShape(it) } } - launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } } - launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } } + launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } } + launch { viewModel.scrolledToTop.collectTraced { view.setScrolledToTop(it) } } launch { - viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } + viewModel.expandFraction.collectTraced { view.setExpandFraction(it.coerceIn(0f, 1f)) } } - launch { viewModel.qsExpandFraction.collect { view.setQsExpandFraction(it) } } + launch { viewModel.qsExpandFraction.collectTraced { view.setQsExpandFraction(it) } } launch { - viewModel.isShowingStackOnLockscreen.collect { + viewModel.isShowingStackOnLockscreen.collectTraced { view.setShowingStackOnLockscreen(it) } } launch { - viewModel.alphaForLockscreenFadeIn.collect { view.setAlphaForLockscreenFadeIn(it) } + viewModel.alphaForLockscreenFadeIn.collectTraced { view.setAlphaForLockscreenFadeIn(it) } } - launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } } - launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } } + launch { viewModel.isScrollable.collectTraced { view.setScrollingEnabled(it) } } + launch { viewModel.isDozing.collectTraced { isDozing -> view.setDozing(isDozing) } } launch { - viewModel.isPulsing.collect { isPulsing -> + viewModel.isPulsing.collectTraced { isPulsing -> view.setPulsing(isPulsing, viewModel.shouldAnimatePulse.value) } } launch { viewModel.shouldResetStackTop .filter { it } - .collect { view.setStackTop(-(view.getHeadsUpInset().toFloat())) } + .collectTraced { view.setStackTop(-(view.getHeadsUpInset().toFloat())) } } launch { - viewModel.shouldCloseGuts.filter { it }.collect { view.closeGutsOnSceneTouch() } + viewModel.shouldCloseGuts.filter { it }.collectTraced { view.closeGutsOnSceneTouch() } } - launch { viewModel.suppressHeightUpdates.collect { view.suppressHeightUpdates(it) } } + launch { viewModel.suppressHeightUpdates.collectTraced { view.suppressHeightUpdates(it) } } launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index f39af18afcea..878ae91391e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -20,6 +20,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.annotation.VisibleForTesting +import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.dagger.SysUISingleton @@ -158,6 +159,7 @@ constructor( ) { shadeExpansion, qsExpansion -> shadeExpansion || qsExpansion } + .flowName("isAnyExpanded") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -175,6 +177,7 @@ constructor( isAnyExpanded -> isShadeLocked && isAnyExpanded } + .flowName("isShadeLocked") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -227,6 +230,7 @@ constructor( ), keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f }, ) + .flowName("isOnLockscreen") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -239,6 +243,7 @@ constructor( combine(isOnLockscreen, isAnyExpanded) { isKeyguard, isAnyExpanded -> isKeyguard && !isAnyExpanded } + .flowName("isOnLockscreenWithoutShade") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -274,6 +279,7 @@ constructor( combine(isOnGlanceableHub, isAnyExpanded) { isGlanceableHub, isAnyExpanded -> isGlanceableHub && !isAnyExpanded } + .flowName("isOnGlanceableHubWithoutShade") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -288,6 +294,7 @@ constructor( isAnyExpanded -> isDreaming && !isAnyExpanded } + .flowName("isDreamingWithoutShade") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -345,6 +352,7 @@ constructor( } } } + .flowName("shadeCollapseFadeIn") .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), @@ -382,6 +390,7 @@ constructor( bounds.copy(top = top, isAnimated = animate) } } + .flowName("bounds") .stateIn( scope = applicationScope, started = SharingStarted.Lazily, @@ -495,6 +504,7 @@ constructor( // flatMapLatest below, the last value gets emitted, to avoid the randomness of `merge`. val alphaForTransitionsAndShade = merge(alphaForTransitions(viewState), alphaForShadeAndQsExpansion) + .flowName("alphaForTransitionsAndShade") .stateIn( // Use view-level scope instead of ApplicationScope, to prevent collection that // never stops diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index 316e1f13bc2b..a34ac2e11c2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -22,7 +22,7 @@ import android.content.res.Resources import android.hardware.biometrics.BiometricSourceType import android.provider.Settings import com.android.app.tracing.ListenersTracing.forEachTraced -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index bad6f80c3735..694a5e529ec4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel -import com.android.app.tracing.coroutines.createCoroutineTracingContext import androidx.annotation.VisibleForTesting +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlagsClassic @@ -29,8 +29,8 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.cancel @@ -116,7 +116,7 @@ constructor( private fun createViewModel(subId: Int): Pair { // Create a child scope so we can cancel it - val vmScope = scope.createChildScope(createCoroutineTracingContext("MobileIconViewModel")) + val vmScope = scope.createChildScope(newTracingContext("MobileIconViewModel")) val vm = MobileIconViewModel( subId, diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt index 2af84c7e46f0..579af73236f3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt @@ -16,10 +16,9 @@ package com.android.systemui.util.kotlin -import com.android.app.tracing.coroutines.createCoroutineTracingContext +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.dagger.qualifiers.Tracing import dagger.Module import dagger.Provides import javax.inject.Singleton @@ -27,7 +26,6 @@ import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi /** Providers for various application-wide coroutines-related constructs. */ @Module @@ -35,16 +33,15 @@ class GlobalCoroutinesModule { @Provides @Singleton @Application - fun applicationScope( - @Main dispatcherContext: CoroutineContext, - ): CoroutineScope = CoroutineScope(dispatcherContext + createCoroutineTracingContext("ApplicationScope")) + fun applicationScope(@Main dispatcherContext: CoroutineContext): CoroutineScope = + CoroutineScope(dispatcherContext + newTracingContext("ApplicationScope")) @Provides @Singleton @Main @Deprecated( "Use @Main CoroutineContext instead", - ReplaceWith("mainCoroutineContext()", "kotlin.coroutines.CoroutineContext") + ReplaceWith("mainCoroutineContext()", "kotlin.coroutines.CoroutineContext"), ) fun mainDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt index 4d9aaa6dc6b0..ea8709f7d65c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.util.settings -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.annotation.UserIdInt import android.content.ContentResolver import android.database.ContentObserver @@ -24,6 +23,7 @@ import android.provider.Settings.SettingNotFoundException import androidx.annotation.AnyThread import androidx.annotation.WorkerThread import com.android.app.tracing.TraceUtils.trace +import com.android.systemui.coroutines.newTracingContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -94,7 +94,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-A")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-A")).launch { registerContentObserverSync(getUriFor(name), settingsObserver) } @@ -109,9 +109,9 @@ interface SettingsProxy { fun registerContentObserverAsync( name: String, settingsObserver: ContentObserver, - @WorkerThread registered: Runnable + @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-B")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-B")).launch { registerContentObserverSync(getUriFor(name), settingsObserver) registered.run() } @@ -144,7 +144,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-C")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-C")).launch { registerContentObserverSync(uri, settingsObserver) } @@ -159,9 +159,9 @@ interface SettingsProxy { fun registerContentObserverAsync( uri: Uri, settingsObserver: ContentObserver, - @WorkerThread registered: Runnable + @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-D")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-D")).launch { registerContentObserverSync(uri, settingsObserver) registered.run() } @@ -175,7 +175,7 @@ interface SettingsProxy { fun registerContentObserverSync( name: String, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) = registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) /** @@ -204,9 +204,9 @@ interface SettingsProxy { fun registerContentObserverAsync( name: String, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-E")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-E")).launch { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) } @@ -222,9 +222,9 @@ interface SettingsProxy { name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - @WorkerThread registered: Runnable + @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-F")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-F")).launch { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) registered.run() } @@ -273,9 +273,9 @@ interface SettingsProxy { fun registerContentObserverAsync( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-G")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-G")).launch { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) } @@ -291,9 +291,9 @@ interface SettingsProxy { uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - @WorkerThread registered: Runnable + @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-H")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-H")).launch { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) registered.run() } @@ -330,7 +330,9 @@ interface SettingsProxy { */ @AnyThread fun unregisterContentObserverAsync(settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-I")).launch { unregisterContentObserver(settingsObserver) } + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-I")).launch { + unregisterContentObserver(settingsObserver) + } /** * Look up a name in the database. diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt index c820c07b61b1..c5deca214e28 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.util.settings -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.annotation.UserIdInt import android.annotation.WorkerThread import android.content.ContentResolver @@ -24,6 +23,7 @@ import android.net.Uri import android.os.UserHandle import android.provider.Settings.SettingNotFoundException import com.android.app.tracing.TraceUtils.trace +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow @@ -79,7 +79,7 @@ interface UserSettingsProxy : SettingsProxy { } override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-A")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-A")).launch { registerContentObserverForUserSync(uri, settingsObserver, userId) } @@ -88,7 +88,7 @@ interface UserSettingsProxy : SettingsProxy { override fun registerContentObserverSync( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } @@ -111,9 +111,9 @@ interface UserSettingsProxy : SettingsProxy { override fun registerContentObserverAsync( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-B")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-B")).launch { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } @@ -156,9 +156,9 @@ interface UserSettingsProxy : SettingsProxy { fun registerContentObserverForUserAsync( name: String, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-C")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-C")).launch { registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) } @@ -197,9 +197,9 @@ interface UserSettingsProxy : SettingsProxy { fun registerContentObserverForUserAsync( uri: Uri, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-D")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-D")).launch { registerContentObserverForUserSync(uri, settingsObserver, userHandle) } @@ -214,9 +214,9 @@ interface UserSettingsProxy : SettingsProxy { uri: Uri, settingsObserver: ContentObserver, userHandle: Int, - @WorkerThread registered: Runnable + @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-E")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-E")).launch { registerContentObserverForUserSync(uri, settingsObserver, userHandle) registered.run() } @@ -273,14 +273,14 @@ interface UserSettingsProxy : SettingsProxy { name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-F")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-F")).launch { registerContentObserverForUserSync( getUriFor(name), notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } } @@ -337,14 +337,14 @@ interface UserSettingsProxy : SettingsProxy { uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-G")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-G")).launch { registerContentObserverForUserSync( uri, notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt index 2dd0bdab93d1..5e0af634c786 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt @@ -17,7 +17,7 @@ package com.android.systemui.volume.dialog.settings.domain import android.app.ActivityManager -import com.android.app.tracing.coroutines.flow.map +import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.volume.Events import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog @@ -30,6 +30,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @VolumeDialogScope @@ -49,6 +50,7 @@ constructor( deviceProvisionedController.isCurrentUserSetup() && model.lockTaskModeState == ActivityManager.LOCK_TASK_MODE_NONE } + .flowName("VDSBI#isVisible") .stateIn(coroutineScope, SharingStarted.Eagerly, false) fun onButtonClicked() { -- GitLab From 2f641ffc8ce185bfc451b0d4fed49956b0fc48ad Mon Sep 17 00:00:00 2001 From: Jordan Demeulenaere Date: Tue, 15 Oct 2024 15:59:08 +0200 Subject: [PATCH 173/441] Fix crash in MutableSTLState.finishTransition() This CL fixes a rare crash that could happen when finishing a transition, because a replaced transition was not correctly removed from MutableSTLState.finishedTransitions. Bug: 373589988 Test: atest SceneTransitionLayoutTest Flag: EXEMPT crash fix Change-Id: Ib692aee3c1a70203917628da7dbae4b181a9db91 --- .../scene/SceneTransitionLayoutState.kt | 4 ++ .../scene/SceneTransitionLayoutStateTest.kt | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 2657d7cf8156..3c3c612c028b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -430,6 +430,10 @@ internal class MutableSceneTransitionLayoutStateImpl( // Replace the transition. transitionStates = transitionStates.subList(0, transitionStates.lastIndex) + transition + + // Make sure it is removed from the finishedTransitions set if it was already + // finished. + finishedTransitions.remove(currentState) } else { // Append the new transition. transitionStates = transitionStates + transition diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index f3a34884c756..f5bb5ba032c2 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -727,4 +727,45 @@ class SceneTransitionLayoutStateTest { // The previous job is cancelled and does not infinitely collect the progress. job.join() } + + @Test + fun replacedTransitionIsRemovedFromFinishedTransitions() = runTest { + val state = MutableSceneTransitionLayoutState(SceneA) + + val aToB = + transition( + SceneA, + SceneB, + onFreezeAndAnimate = { + // Do nothing, so that this transition stays in the transitionStates list and we + // can finish() it manually later. + }, + ) + val replacingAToB = transition(SceneB, SceneC) + val replacingBToC = transition(SceneB, SceneC, replacedTransition = replacingAToB) + + // Start A => B. + val aToBJob = state.startTransitionImmediately(animationScope = this, aToB) + + // Start B => C and immediately finish it. It will be flagged as finished in + // STLState.finishedTransitions given that A => B is not finished yet. + val bToCJob = state.startTransitionImmediately(animationScope = this, replacingAToB) + replacingAToB.finish() + bToCJob.join() + + // Start a new B => C that replaces the previously finished B => C. + val replacingBToCJob = + state.startTransitionImmediately(animationScope = this, replacingBToC) + + // Finish A => B. + aToB.finish() + aToBJob.join() + + // Finish the new B => C. + replacingBToC.finish() + replacingBToCJob.join() + + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneC) + } } -- GitLab From a698e5e7f9ca39d0b462f1f28fc0104687b5ea69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= Date: Fri, 11 Oct 2024 20:04:56 +0200 Subject: [PATCH 174/441] Build.VERSION_CODES_FULL: backfill older Android versions Add Build.VERSION_CODES_FULL constants for all existing Android versions. These are all considered major versions. Bug: 350458259 Test: m Flag: android.sdk.major_minor_versioning_scheme Change-Id: I32745fc91ef6d9b9db3eda0959936af14fc7c3ef --- core/api/current.txt | 35 ++++++ core/java/android/os/Build.java | 215 ++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) diff --git a/core/api/current.txt b/core/api/current.txt index 035f82388e6e..9abb8d56d1dc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32880,6 +32880,41 @@ package android.os { } @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static class Build.VERSION_CODES_FULL { + field public static final int BASE = 100000; // 0x186a0 + field public static final int BASE_1_1 = 200000; // 0x30d40 + field public static final int CUPCAKE = 300000; // 0x493e0 + field public static final int DONUT = 400000; // 0x61a80 + field public static final int ECLAIR = 500000; // 0x7a120 + field public static final int ECLAIR_0_1 = 600000; // 0x927c0 + field public static final int ECLAIR_MR1 = 700000; // 0xaae60 + field public static final int FROYO = 800000; // 0xc3500 + field public static final int GINGERBREAD = 900000; // 0xdbba0 + field public static final int GINGERBREAD_MR1 = 1000000; // 0xf4240 + field public static final int HONEYCOMB = 1100000; // 0x10c8e0 + field public static final int HONEYCOMB_MR1 = 1200000; // 0x124f80 + field public static final int HONEYCOMB_MR2 = 1300000; // 0x13d620 + field public static final int ICE_CREAM_SANDWICH = 1400000; // 0x155cc0 + field public static final int ICE_CREAM_SANDWICH_MR1 = 1500000; // 0x16e360 + field public static final int JELLY_BEAN = 1600000; // 0x186a00 + field public static final int JELLY_BEAN_MR1 = 1700000; // 0x19f0a0 + field public static final int JELLY_BEAN_MR2 = 1800000; // 0x1b7740 + field public static final int KITKAT = 1900000; // 0x1cfde0 + field public static final int KITKAT_WATCH = 2000000; // 0x1e8480 + field public static final int LOLLIPOP = 2100000; // 0x200b20 + field public static final int LOLLIPOP_MR1 = 2200000; // 0x2191c0 + field public static final int M = 2300000; // 0x231860 + field public static final int N = 2400000; // 0x249f00 + field public static final int N_MR1 = 2500000; // 0x2625a0 + field public static final int O = 2600000; // 0x27ac40 + field public static final int O_MR1 = 2700000; // 0x2932e0 + field public static final int P = 2800000; // 0x2ab980 + field public static final int Q = 2900000; // 0x2c4020 + field public static final int R = 3000000; // 0x2dc6c0 + field public static final int S = 3100000; // 0x2f4d60 + field public static final int S_V2 = 3200000; // 0x30d400 + field public static final int TIRAMISU = 3300000; // 0x325aa0 + field public static final int UPSIDE_DOWN_CAKE = 3400000; // 0x33e140 + field public static final int VANILLA_ICE_CREAM = 3500000; // 0x3567e0 } public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index d3ba73ed1421..a89483394611 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1278,6 +1278,41 @@ public class Build { /** @hide */ @IntDef(value = { + VERSION_CODES_FULL.BASE, + VERSION_CODES_FULL.BASE_1_1, + VERSION_CODES_FULL.CUPCAKE, + VERSION_CODES_FULL.DONUT, + VERSION_CODES_FULL.ECLAIR, + VERSION_CODES_FULL.ECLAIR_0_1, + VERSION_CODES_FULL.ECLAIR_MR1, + VERSION_CODES_FULL.FROYO, + VERSION_CODES_FULL.GINGERBREAD, + VERSION_CODES_FULL.GINGERBREAD_MR1, + VERSION_CODES_FULL.HONEYCOMB, + VERSION_CODES_FULL.HONEYCOMB_MR1, + VERSION_CODES_FULL.HONEYCOMB_MR2, + VERSION_CODES_FULL.ICE_CREAM_SANDWICH, + VERSION_CODES_FULL.ICE_CREAM_SANDWICH_MR1, + VERSION_CODES_FULL.JELLY_BEAN, + VERSION_CODES_FULL.JELLY_BEAN_MR1, + VERSION_CODES_FULL.JELLY_BEAN_MR2, + VERSION_CODES_FULL.KITKAT, + VERSION_CODES_FULL.KITKAT_WATCH, + VERSION_CODES_FULL.LOLLIPOP, + VERSION_CODES_FULL.LOLLIPOP_MR1, + VERSION_CODES_FULL.M, + VERSION_CODES_FULL.N, + VERSION_CODES_FULL.N_MR1, + VERSION_CODES_FULL.O, + VERSION_CODES_FULL.O_MR1, + VERSION_CODES_FULL.P, + VERSION_CODES_FULL.Q, + VERSION_CODES_FULL.R, + VERSION_CODES_FULL.S, + VERSION_CODES_FULL.S_V2, + VERSION_CODES_FULL.TIRAMISU, + VERSION_CODES_FULL.UPSIDE_DOWN_CAKE, + VERSION_CODES_FULL.VANILLA_ICE_CREAM, }) @Retention(RetentionPolicy.SOURCE) public @interface SdkIntFull {} @@ -1299,6 +1334,186 @@ public class Build { // Use the last 5 digits for the minor version. This allows the // minor version to be set to CUR_DEVELOPMENT. private static final int SDK_INT_MULTIPLIER = 100000; + + /** + * Android 1.0. + */ + public static final int BASE = VERSION_CODES.BASE * SDK_INT_MULTIPLIER; + + /** + * Android 2.0. + */ + public static final int BASE_1_1 = VERSION_CODES.BASE_1_1 * SDK_INT_MULTIPLIER; + + /** + * Android 3.0. + */ + public static final int CUPCAKE = VERSION_CODES.CUPCAKE * SDK_INT_MULTIPLIER; + + /** + * Android 4.0. + */ + public static final int DONUT = VERSION_CODES.DONUT * SDK_INT_MULTIPLIER; + + /** + * Android 5.0. + */ + public static final int ECLAIR = VERSION_CODES.ECLAIR * SDK_INT_MULTIPLIER; + + /** + * Android 6.0. + */ + public static final int ECLAIR_0_1 = VERSION_CODES.ECLAIR_0_1 * SDK_INT_MULTIPLIER; + + /** + * Android 7.0. + */ + public static final int ECLAIR_MR1 = VERSION_CODES.ECLAIR_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 8.0. + */ + public static final int FROYO = VERSION_CODES.FROYO * SDK_INT_MULTIPLIER; + + /** + * Android 9.0. + */ + public static final int GINGERBREAD = VERSION_CODES.GINGERBREAD * SDK_INT_MULTIPLIER; + + /** + * Android 10.0. + */ + public static final int GINGERBREAD_MR1 = + VERSION_CODES.GINGERBREAD_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 11.0. + */ + public static final int HONEYCOMB = VERSION_CODES.HONEYCOMB * SDK_INT_MULTIPLIER; + + /** + * Android 12.0. + */ + public static final int HONEYCOMB_MR1 = VERSION_CODES.HONEYCOMB_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 13.0. + */ + public static final int HONEYCOMB_MR2 = VERSION_CODES.HONEYCOMB_MR2 * SDK_INT_MULTIPLIER; + + /** + * Android 14.0. + */ + public static final int ICE_CREAM_SANDWICH = + VERSION_CODES.ICE_CREAM_SANDWICH * SDK_INT_MULTIPLIER; + + /** + * Android 15.0. + */ + public static final int ICE_CREAM_SANDWICH_MR1 = + VERSION_CODES.ICE_CREAM_SANDWICH_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 16.0. + */ + public static final int JELLY_BEAN = VERSION_CODES.JELLY_BEAN * SDK_INT_MULTIPLIER; + + /** + * Android 17.0. + */ + public static final int JELLY_BEAN_MR1 = VERSION_CODES.JELLY_BEAN_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 18.0. + */ + public static final int JELLY_BEAN_MR2 = VERSION_CODES.JELLY_BEAN_MR2 * SDK_INT_MULTIPLIER; + + /** + * Android 19.0. + */ + public static final int KITKAT = VERSION_CODES.KITKAT * SDK_INT_MULTIPLIER; + + /** + * Android 20.0. + */ + public static final int KITKAT_WATCH = VERSION_CODES.KITKAT_WATCH * SDK_INT_MULTIPLIER; + + /** + * Android 21.0. + */ + public static final int LOLLIPOP = VERSION_CODES.LOLLIPOP * SDK_INT_MULTIPLIER; + + /** + * Android 22.0. + */ + public static final int LOLLIPOP_MR1 = VERSION_CODES.LOLLIPOP_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 23.0. + */ + public static final int M = VERSION_CODES.M * SDK_INT_MULTIPLIER; + + /** + * Android 24.0. + */ + public static final int N = VERSION_CODES.N * SDK_INT_MULTIPLIER; + + /** + * Android 25.0. + */ + public static final int N_MR1 = VERSION_CODES.N_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 26.0. + */ + public static final int O = VERSION_CODES.O * SDK_INT_MULTIPLIER; + + /** + * Android 27.0. + */ + public static final int O_MR1 = VERSION_CODES.O_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 28.0. + */ + public static final int P = VERSION_CODES.P * SDK_INT_MULTIPLIER; + + /** + * Android 29.0. + */ + public static final int Q = VERSION_CODES.Q * SDK_INT_MULTIPLIER; + + /** + * Android 30.0. + */ + public static final int R = VERSION_CODES.R * SDK_INT_MULTIPLIER; + + /** + * Android 31.0. + */ + public static final int S = VERSION_CODES.S * SDK_INT_MULTIPLIER; + + /** + * Android 32.0. + */ + public static final int S_V2 = VERSION_CODES.S_V2 * SDK_INT_MULTIPLIER; + + /** + * Android 33.0. + */ + public static final int TIRAMISU = VERSION_CODES.TIRAMISU * SDK_INT_MULTIPLIER; + + /** + * Android 34.0. + */ + public static final int UPSIDE_DOWN_CAKE = + VERSION_CODES.UPSIDE_DOWN_CAKE * SDK_INT_MULTIPLIER; + + /** + * Android 35.0. + */ + public static final int VANILLA_ICE_CREAM = + VERSION_CODES.VANILLA_ICE_CREAM * SDK_INT_MULTIPLIER; } /** -- GitLab From 91a04ab988105dfa19656e7c4efef31c550b13eb Mon Sep 17 00:00:00 2001 From: Graciela Wissen Putri Date: Mon, 14 Oct 2024 10:30:00 +0000 Subject: [PATCH 175/441] Add move to external display Allow app to move to external display with cascading and initial bounds applied. Expose moveToExternalDisplay to launcher. Flag: com.android.window.flags.move_to_external_display_shortcut Test: atest DesktopTasksControllerTest Bug: 372872848 Change-Id: I42a4fe8d1850d6f4cf8ff67b88e6d08806dd5c3f --- .../desktopmode/DesktopTasksController.kt | 34 +++++++++++++------ .../wm/shell/desktopmode/IDesktopMode.aidl | 3 ++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 57a59c946f98..b723337cc894 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -659,6 +659,7 @@ class DesktopTasksController( } val wct = WindowContainerTransaction() + if (!task.isFreeform) addMoveToDesktopChanges(wct, task, displayId) wct.reparent(task.token, displayAreaInfo.token, true /* onTop */) transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) @@ -1245,7 +1246,7 @@ class DesktopTasksController( val bounds = when (newTaskWindowingMode) { WINDOWING_MODE_FREEFORM -> { displayController.getDisplayLayout(callingTask.displayId) - ?.let { getInitialBounds(it, callingTask) } + ?.let { getInitialBounds(it, callingTask, callingTask.displayId) } } WINDOWING_MODE_MULTI_WINDOW -> { Rect() @@ -1311,7 +1312,7 @@ class DesktopTasksController( val displayLayout = displayController.getDisplayLayout(task.displayId) if (displayLayout != null) { val initialBounds = Rect(task.configuration.windowConfiguration.bounds) - cascadeWindow(task, initialBounds, displayLayout) + cascadeWindow(initialBounds, displayLayout, task.displayId) wct.setBounds(task.token, initialBounds) } } @@ -1399,13 +1400,19 @@ class DesktopTasksController( return if (wct.isEmpty) null else wct } + /** + * Apply all changes required when task is first added to desktop. Uses the task's current + * display by default to apply initial bounds and placement relative to the display. + * Use a different [displayId] if the task should be moved to a different display. + */ @VisibleForTesting fun addMoveToDesktopChanges( wct: WindowContainerTransaction, - taskInfo: RunningTaskInfo + taskInfo: RunningTaskInfo, + displayId: Int = taskInfo.displayId, ) { - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return - val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! + val displayLayout = displayController.getDisplayLayout(displayId) ?: return + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { @@ -1414,7 +1421,7 @@ class DesktopTasksController( } else { WINDOWING_MODE_FREEFORM } - val initialBounds = getInitialBounds(displayLayout, taskInfo) + val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId) if (canChangeTaskPosition(taskInfo)) { wct.setBounds(taskInfo.token, initialBounds) @@ -1428,7 +1435,8 @@ class DesktopTasksController( private fun getInitialBounds( displayLayout: DisplayLayout, - taskInfo: RunningTaskInfo + taskInfo: RunningTaskInfo, + displayId: Int, ): Rect { val bounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) { calculateInitialBounds(displayLayout, taskInfo) @@ -1437,7 +1445,7 @@ class DesktopTasksController( } if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) { - cascadeWindow(taskInfo, bounds, displayLayout) + cascadeWindow(bounds, displayLayout, displayId) } return bounds } @@ -1466,11 +1474,11 @@ class DesktopTasksController( } } - private fun cascadeWindow(task: TaskInfo, bounds: Rect, displayLayout: DisplayLayout) { + private fun cascadeWindow(bounds: Rect, displayLayout: DisplayLayout, displayId: Int) { val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) - val activeTasks = taskRepository.getActiveNonMinimizedOrderedTasks(task.displayId) + val activeTasks = taskRepository.getActiveNonMinimizedOrderedTasks(displayId) activeTasks.firstOrNull()?.let { activeTask -> shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let { cascadeWindow(context.resources, stableBounds, @@ -2069,6 +2077,12 @@ class DesktopTasksController( c.removeDesktop(displayId) } } + + override fun moveToExternalDisplay(taskId: Int) { + executeRemoteCallWithTaskPermission(controller, "moveTaskToExternalDisplay") { c -> + c.moveToNextDisplay(taskId) + } + } } private fun logV(msg: String, vararg arguments: Any?) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 86351e364cdd..c27813de5358 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -52,4 +52,7 @@ interface IDesktopMode { /** Remove desktop on the given display */ oneway void removeDesktop(int displayId); + + /** Move a task with given `taskId` to external display */ + void moveToExternalDisplay(int taskId); } \ No newline at end of file -- GitLab From bc7e7477b82c7ad64f3a1f173046a3e468e51d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Kongstad?= Date: Wed, 25 Sep 2024 09:59:05 +0200 Subject: [PATCH 176/441] aapt2: add Baklava Add Baklava to aapt2's list of known development SDK code names. Bug: 354023031 Test: atest aapt2_tests Flag: EXEMPT introducing new version codes can not be flagged Change-Id: Id093107a168bef3e3d7cfcb1aff994ac7729b653 --- tools/aapt2/SdkConstants.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index 83f2eb31aa57..993516d07386 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -28,8 +28,19 @@ using namespace std::literals; namespace aapt { static constexpr ApiVersion sDevelopmentSdkLevel = 10000; + +// clang-format off static constexpr StringPiece sDevelopmentSdkCodeNames[] = { - "Q"sv, "R"sv, "S"sv, "Sv2"sv, "Tiramisu"sv, "UpsideDownCake"sv, "VanillaIceCream"sv}; + "Q"sv, + "R"sv, + "S"sv, + "Sv2"sv, + "Tiramisu"sv, + "UpsideDownCake"sv, + "VanillaIceCream"sv, + "Baklava"sv, +}; +// clang-format on static constexpr auto sPrivacySandboxSuffix = "PrivacySandbox"sv; -- GitLab From 0a291023a2479b6cb226ffd9577b83242b07681c Mon Sep 17 00:00:00 2001 From: Alina Zaidi Date: Wed, 9 Oct 2024 16:43:08 +0000 Subject: [PATCH 177/441] [logging] Move session id tracking to DesktopModeLogger This is so that other classes which use DesktopModeLogger don't need to query sessionId Bug: 371204657 Test: updated unit tests Flag: EXEMPT refactor Change-Id: Ie093f25e1f8b3060a1a76f9a5009d841c10e2cd0 --- .../desktopmode/DesktopModeEventLogger.kt | 133 +++++-- .../DesktopModeLoggerTransitionObserver.kt | 40 +- .../desktopmode/DesktopModeEventLoggerTest.kt | 348 +++++++++++++----- ...DesktopModeLoggerTransitionObserverTest.kt | 156 ++++---- 4 files changed, 427 insertions(+), 250 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 5a277316ffd4..349c8e2b3c22 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -21,17 +21,39 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.util.FrameworkStatsLog import com.android.window.flags.Flags import com.android.wm.shell.EventLogTags -import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import java.security.SecureRandom +import java.util.Random +import java.util.concurrent.atomic.AtomicInteger + /** Event logger for logging desktop mode session events */ class DesktopModeEventLogger { + private val random: Random = SecureRandom() + + /** The session id for the current desktop mode session */ + @VisibleForTesting + val currentSessionId: AtomicInteger = AtomicInteger(NO_SESSION_ID) + + private fun generateSessionId() = 1 + random.nextInt(1 shl 20) + /** - * Logs the enter of desktop mode having session id [sessionId] and the reason [enterReason] for - * entering desktop mode + * Logs enter into desktop mode with [enterReason] */ - fun logSessionEnter(sessionId: Int, enterReason: EnterReason) { + fun logSessionEnter(enterReason: EnterReason) { + val sessionId = generateSessionId() + val previousSessionId = currentSessionId.getAndSet(sessionId) + if (previousSessionId != NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: Existing desktop mode session id: %s found on desktop " + + "mode enter", + previousSessionId + ) + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging session enter, session: %s reason: %s", sessionId, enterReason.name @@ -47,12 +69,20 @@ class DesktopModeEventLogger { } /** - * Logs the exit of desktop mode having session id [sessionId] and the reason [exitReason] for - * exiting desktop mode + * Logs exit from desktop mode session with [exitReason] */ - fun logSessionExit(sessionId: Int, exitReason: ExitReason) { + fun logSessionExit(exitReason: ExitReason) { + val sessionId = currentSessionId.getAndSet(NO_SESSION_ID) + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging exit from desktop mode" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging session exit, session: %s reason: %s", sessionId, exitReason.name @@ -68,12 +98,20 @@ class DesktopModeEventLogger { } /** - * Logs that the task with update [taskUpdate] was added in the desktop mode session having - * session id [sessionId] + * Logs that a task with [taskUpdate] was added in a desktop mode session */ - fun logTaskAdded(sessionId: Int, taskUpdate: TaskUpdate) { + fun logTaskAdded(taskUpdate: TaskUpdate) { + val sessionId = currentSessionId.get() + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging task added" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task added, session: %s taskId: %s", sessionId, taskUpdate.instanceId @@ -85,12 +123,20 @@ class DesktopModeEventLogger { } /** - * Logs that the task with update [taskUpdate] was removed in the desktop mode session having - * session id [sessionId] + * Logs that a task with [taskUpdate] was removed from a desktop mode session */ - fun logTaskRemoved(sessionId: Int, taskUpdate: TaskUpdate) { + fun logTaskRemoved(taskUpdate: TaskUpdate) { + val sessionId = currentSessionId.get() + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging task removed" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task remove, session: %s taskId: %s", sessionId, taskUpdate.instanceId @@ -102,12 +148,20 @@ class DesktopModeEventLogger { } /** - * Logs that the task with update [taskUpdate] had it's info changed in the desktop mode session - * having session id [sessionId] + * Logs that a task with [taskUpdate] had it's info changed in a desktop mode session */ - fun logTaskInfoChanged(sessionId: Int, taskUpdate: TaskUpdate) { + fun logTaskInfoChanged(taskUpdate: TaskUpdate) { + val sessionId = currentSessionId.get() + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging task info changed" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task info changed, session: %s taskId: %s", sessionId, taskUpdate.instanceId @@ -119,14 +173,23 @@ class DesktopModeEventLogger { } /** - * Logs that a task resize event is starting with [taskSizeUpdate] within a - * Desktop mode [sessionId]. + * Logs that a task resize event is starting with [taskSizeUpdate] within a Desktop mode + * session. */ - fun logTaskResizingStarted(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) { + fun logTaskResizingStarted(taskSizeUpdate: TaskSizeUpdate) { if (!Flags.enableResizingMetrics()) return + val sessionId = currentSessionId.get() + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging start of task resizing" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task resize is starting, session: %s taskId: %s", sessionId, taskSizeUpdate.instanceId @@ -138,14 +201,22 @@ class DesktopModeEventLogger { } /** - * Logs that a task resize event is ending with [taskSizeUpdate] within a - * Desktop mode [sessionId]. + * Logs that a task resize event is ending with [taskSizeUpdate] within a Desktop mode session. */ - fun logTaskResizingEnded(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) { + fun logTaskResizingEnded(taskSizeUpdate: TaskSizeUpdate) { if (!Flags.enableResizingMetrics()) return + val sessionId = currentSessionId.get() + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging end of task resizing" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task resize is ending, session: %s taskId: %s", sessionId, taskSizeUpdate.instanceId @@ -233,6 +304,7 @@ class DesktopModeEventLogger { } companion object { + /** * Describes a task position and dimensions. * @@ -450,5 +522,6 @@ class DesktopModeEventLogger { FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE private const val DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED + @VisibleForTesting const val NO_SESSION_ID = 0 } -} +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index b8507e3b2764..d2b483dff2ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -35,8 +35,6 @@ import androidx.core.util.isEmpty import androidx.core.util.isNotEmpty import androidx.core.util.plus import androidx.core.util.putAll -import com.android.internal.logging.InstanceId -import com.android.internal.logging.InstanceIdSequence import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason @@ -48,8 +46,8 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE -import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.TransitionUtil +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions @@ -65,8 +63,6 @@ class DesktopModeLoggerTransitionObserver( private val desktopModeEventLogger: DesktopModeEventLogger ) : Transitions.TransitionObserver { - private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) } - init { if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(this::onInit, this) @@ -87,15 +83,7 @@ class DesktopModeLoggerTransitionObserver( // following enter reason could be Screen On private var wasPreviousTransitionExitByScreenOff: Boolean = false - // The instanceId for the current logging session - private var loggerInstanceId: InstanceId? = null - - private val isSessionActive: Boolean - get() = loggerInstanceId != null - - private fun setSessionInactive() { - loggerInstanceId = null - } + @VisibleForTesting var isSessionActive: Boolean = false fun onInit() { transitions.registerObserver(this) @@ -246,38 +234,32 @@ class DesktopModeLoggerTransitionObserver( ) { // Sessions is finishing, log task updates followed by an exit event identifyAndLogTaskUpdates( - loggerInstanceId!!.id, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks ) desktopModeEventLogger.logSessionExit( - loggerInstanceId!!.id, getExitReason(transitionInfo) ) - - setSessionInactive() + isSessionActive = false } else if ( postTransitionVisibleFreeformTasks.isNotEmpty() && preTransitionVisibleFreeformTasks.isEmpty() && !isSessionActive ) { // Session is starting, log enter event followed by task updates - loggerInstanceId = idSequence.newInstanceId() + isSessionActive = true desktopModeEventLogger.logSessionEnter( - loggerInstanceId!!.id, getEnterReason(transitionInfo) ) identifyAndLogTaskUpdates( - loggerInstanceId!!.id, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks ) } else if (isSessionActive) { // Session is neither starting, nor finishing, log task updates if there are any identifyAndLogTaskUpdates( - loggerInstanceId!!.id, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks ) @@ -290,7 +272,6 @@ class DesktopModeLoggerTransitionObserver( /** Compare the old and new state of taskInfos and identify and log the changes */ private fun identifyAndLogTaskUpdates( - sessionId: Int, preTransitionVisibleFreeformTasks: SparseArray, postTransitionVisibleFreeformTasks: SparseArray ) { @@ -301,7 +282,7 @@ class DesktopModeLoggerTransitionObserver( when { // new tasks added previousTaskInfo == null -> { - desktopModeEventLogger.logTaskAdded(sessionId, currentTaskUpdate) + desktopModeEventLogger.logTaskAdded(currentTaskUpdate) Trace.setCounter( Trace.TRACE_TAG_WINDOW_MANAGER, VISIBLE_TASKS_COUNTER_NAME, @@ -314,14 +295,14 @@ class DesktopModeLoggerTransitionObserver( // TODO(b/347935387): Log changes only once they are stable. buildTaskUpdateForTask(previousTaskInfo, postTransitionVisibleFreeformTasks.size()) != currentTaskUpdate -> - desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate) + desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate) } } // find old tasks that were removed preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) { - desktopModeEventLogger.logTaskRemoved(sessionId, + desktopModeEventLogger.logTaskRemoved( buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size())) Trace.setCounter( Trace.TRACE_TAG_WINDOW_MANAGER, @@ -416,13 +397,6 @@ class DesktopModeLoggerTransitionObserver( visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo) } - @VisibleForTesting fun getLoggerSessionId(): Int? = loggerInstanceId?.id - - @VisibleForTesting - fun setLoggerSessionId(id: Int) { - loggerInstanceId = InstanceId.fakeInstanceId(id) - } - private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo { return this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change") } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index d7a132dfa1be..f2741a622539 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -16,10 +16,12 @@ package com.android.wm.shell.desktopmode -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule +import com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations +import com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker import com.android.dx.mockito.inline.extended.ExtendedMockito.verify +import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.window.flags.Flags @@ -27,15 +29,16 @@ import com.android.wm.shell.EventLogTags import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.NO_SESSION_ID import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger -import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod -import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskSizeUpdate -import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON -import kotlinx.coroutines.runBlocking +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason +import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test import org.mockito.kotlin.eq @@ -50,40 +53,87 @@ class DesktopModeEventLoggerTest : ShellTestCase() { @JvmField @Rule(order = 0) val extendedMockitoRule = ExtendedMockitoRule.Builder(this) - .mockStatic(FrameworkStatsLog::class.java) - .mockStatic(EventLogTags::class.java).build()!! + .mockStatic(FrameworkStatsLog::class.java) + .mockStatic(EventLogTags::class.java).build()!! @JvmField @Rule(order = 1) val setFlagsRule = SetFlagsRule() @Test - fun logSessionEnter_enterReason() = runBlocking { - desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER) + fun logSessionEnter_logsEnterReasonWithNewSessionId() { + desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER) + val sessionId = desktopModeEventLogger.currentSessionId.get() + assertThat(sessionId).isNotEqualTo(NO_SESSION_ID) verify { FrameworkStatsLog.write( eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED), /* event */ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER), /* enter_reason */ - eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__UNKNOWN_ENTER), + eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER), /* exit_reason */ eq(0), /* sessionId */ - eq(SESSION_ID) + eq(sessionId) ) } + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellEnterDesktopMode( - eq(EnterReason.UNKNOWN_ENTER.reason), - eq(SESSION_ID)) + eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason), + eq(sessionId) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logSessionExit_exitReason() = runBlocking { - desktopModeEventLogger.logSessionExit(sessionId = SESSION_ID, ExitReason.UNKNOWN_EXIT) + fun logSessionEnter_ongoingSession_logsEnterReasonWithNewSessionId() { + val previousSessionId = startDesktopModeSession() + + desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER) + + val sessionId = desktopModeEventLogger.currentSessionId.get() + assertThat(sessionId).isNotEqualTo(NO_SESSION_ID) + assertThat(sessionId).isNotEqualTo(previousSessionId) + verify { + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED), + /* event */ + eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER), + /* enter_reason */ + eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER), + /* exit_reason */ + eq(0), + /* sessionId */ + eq(sessionId) + ) + } + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verify { + EventLogTags.writeWmShellEnterDesktopMode( + eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason), + eq(sessionId) + ) + } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logSessionExit_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logSessionExit_logsExitReasonAndClearsSessionId() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT) verify { FrameworkStatsLog.write( @@ -93,24 +143,39 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* enter_reason */ eq(0), /* exit_reason */ - eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__UNKNOWN_EXIT), + eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT), /* sessionId */ - eq(SESSION_ID) + eq(sessionId) ) } + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellExitDesktopMode( - eq(ExitReason.UNKNOWN_EXIT.reason), - eq(SESSION_ID)) + eq(ExitReason.DRAG_TO_EXIT.reason), + eq(sessionId) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + assertThat(desktopModeEventLogger.currentSessionId.get()).isEqualTo(NO_SESSION_ID) } @Test - fun logTaskAdded_taskUpdate() = runBlocking { - desktopModeEventLogger.logTaskAdded(sessionId = SESSION_ID, TASK_UPDATE) + fun logTaskAdded_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logTaskAdded(TASK_UPDATE) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logTaskAdded_logsTaskUpdate() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskAdded(TASK_UPDATE) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED), /* instance_id */ @@ -126,36 +191,52 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } - + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED + ), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), eq(TASK_UPDATE.taskWidth), eq(TASK_UPDATE.taskX), eq(TASK_UPDATE.taskY), - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logTaskRemoved_taskUpdate() = runBlocking { - desktopModeEventLogger.logTaskRemoved(sessionId = SESSION_ID, TASK_UPDATE) + fun logTaskRemoved_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logTaskRemoved(TASK_UPDATE) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logTaskRemoved_taskUpdate() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskRemoved(TASK_UPDATE) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED), /* instance_id */ @@ -171,39 +252,57 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } - + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED + ), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), eq(TASK_UPDATE.taskWidth), eq(TASK_UPDATE.taskX), eq(TASK_UPDATE.taskY), - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logTaskInfoChanged_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logTaskInfoChanged_taskUpdate() = runBlocking { - desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, TASK_UPDATE) + fun logTaskInfoChanged_taskUpdate() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), /* instance_id */ eq(TASK_UPDATE.instanceId), /* uid */ @@ -217,40 +316,51 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } - + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), eq(TASK_UPDATE.taskWidth), eq(TASK_UPDATE.taskX), eq(TASK_UPDATE.taskY), - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logTaskInfoChanged_logsTaskUpdateWithMinimizeReason() = runBlocking { - desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, - createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT)) + fun logTaskInfoChanged_logsTaskUpdateWithMinimizeReason() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskInfoChanged( + createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT) + ) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), /* instance_id */ eq(TASK_UPDATE.instanceId), /* uid */ @@ -264,42 +374,53 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID), + eq(sessionId), /* minimize_reason */ eq(MinimizeReason.TASK_LIMIT.reason), /* unminimize_reason */ eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } - + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), eq(TASK_UPDATE.taskWidth), eq(TASK_UPDATE.taskX), eq(TASK_UPDATE.taskY), - eq(SESSION_ID), + eq(sessionId), eq(MinimizeReason.TASK_LIMIT.reason), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logTaskInfoChanged_logsTaskUpdateWithUnminimizeReason() = runBlocking { - desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, - createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP)) + fun logTaskInfoChanged_logsTaskUpdateWithUnminimizeReason() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskInfoChanged( + createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP) + ) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), /* instance_id */ eq(TASK_UPDATE.instanceId), /* uid */ @@ -313,39 +434,55 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID), + eq(sessionId), /* minimize_reason */ eq(UNSET_MINIMIZE_REASON), /* unminimize_reason */ eq(UnminimizeReason.TASKBAR_TAP.reason), /* visible_task_count */ - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } - + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), eq(TASK_UPDATE.taskWidth), eq(TASK_UPDATE.taskX), eq(TASK_UPDATE.taskY), - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UnminimizeReason.TASKBAR_TAP.reason), - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logTaskResizingStarted_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logTaskResizingStarted(TASK_SIZE_UPDATE) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS) - fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() = runBlocking { - desktopModeEventLogger.logTaskResizingStarted(sessionId = SESSION_ID, createTaskSizeUpdate()) + fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskResizingStarted(TASK_SIZE_UPDATE) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), /* resize_trigger */ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER), /* resizing_stage */ @@ -353,7 +490,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* input_method */ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD), /* desktop_mode_session_id */ - eq(SESSION_ID), + eq(sessionId), /* instance_id */ eq(TASK_SIZE_UPDATE.instanceId), /* uid */ @@ -366,15 +503,27 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(TASK_SIZE_UPDATE.displayArea), ) } + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + } + + @Test + fun logTaskResizingEnded_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logTaskResizingEnded(TASK_SIZE_UPDATE) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS) - fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() = runBlocking { - desktopModeEventLogger.logTaskResizingEnded(sessionId = SESSION_ID, createTaskSizeUpdate()) + fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskResizingEnded(TASK_SIZE_UPDATE) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), /* resize_trigger */ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER), /* resizing_stage */ @@ -382,7 +531,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* input_method */ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD), /* desktop_mode_session_id */ - eq(SESSION_ID), + eq(sessionId), /* instance_id */ eq(TASK_SIZE_UPDATE.instanceId), /* uid */ @@ -395,10 +544,18 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(TASK_SIZE_UPDATE.displayArea), ) } + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + } + + private fun startDesktopModeSession(): Int { + desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER) + clearInvocations(staticMockMarker(FrameworkStatsLog::class.java)) + clearInvocations(staticMockMarker(EventLogTags::class.java)) + return desktopModeEventLogger.currentSessionId.get() } private companion object { - private const val SESSION_ID = 1 + private const val sessionId = 1 private const val TASK_ID = 1 private const val TASK_UID = 1 private const val TASK_X = 0 @@ -423,23 +580,12 @@ class DesktopModeEventLoggerTest : ShellTestCase() { DISPLAY_AREA, ) - private fun createTaskSizeUpdate( - resizeTrigger: ResizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, - inputMethod: InputMethod = InputMethod.UNKNOWN_INPUT_METHOD, - ) = TaskSizeUpdate( - resizeTrigger, - inputMethod, - TASK_ID, - TASK_UID, - TASK_HEIGHT, - TASK_WIDTH, - DISPLAY_AREA, - ) - private fun createTaskUpdate( minimizeReason: MinimizeReason? = null, unminimizeReason: UnminimizeReason? = null, - ) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason, - unminimizeReason, TASK_COUNT) + ) = TaskUpdate( + TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason, + unminimizeReason, TASK_COUNT + ) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index daf7e7d5397b..15d5e6ed558c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -59,8 +59,8 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions -import junit.framework.Assert.assertNotNull -import junit.framework.Assert.assertNull +import kotlin.test.assertFalse +import kotlin.test.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -139,8 +139,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, never()).logSessionEnter(any(), any()) - verify(desktopModeEventLogger, never()).logTaskAdded(any(), any()) + verify(desktopModeEventLogger, never()).logSessionEnter(any()) + verify(desktopModeEventLogger, never()).logTaskAdded(any()) } @Test @@ -225,11 +225,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @Test fun transitToFront_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { // previous exit to overview transition - val previousSessionId = 1 // add a freeform task val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.setLoggerSessionId(previousSessionId) + transitionObserver.isSessionActive = true val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) @@ -238,7 +237,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(previousTransitionInfo) verifyTaskRemovedAndExitLogging( - previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows @@ -256,11 +256,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @Test fun transitChange_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { // previous exit to overview transition - val previousSessionId = 1 // add a freeform task val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.setLoggerSessionId(previousSessionId) + transitionObserver.isSessionActive = true val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) @@ -269,7 +268,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(previousTransitionInfo) verifyTaskRemovedAndExitLogging( - previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows @@ -287,11 +287,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @Test fun transitOpen_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { // previous exit to overview transition - val previousSessionId = 1 // add a freeform task val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.setLoggerSessionId(previousSessionId) + transitionObserver.isSessionActive = true val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) @@ -300,7 +299,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(previousTransitionInfo) verifyTaskRemovedAndExitLogging( - previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows @@ -321,11 +321,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { // Tests for AppFromOverview precedence in compared to cancelled Overview // previous exit to overview transition - val previousSessionId = 1 // add a freeform task val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.setLoggerSessionId(previousSessionId) + transitionObserver.isSessionActive = true val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) @@ -334,7 +333,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(previousTransitionInfo) verifyTaskRemovedAndExitLogging( - previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) @@ -376,11 +376,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { fun transitBack_previousExitReasonScreenOff_logTaskAddedAndEnterReasonScreenOn() { val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM) // Previous Exit reason recorded as Screen Off - val sessionId = 1 transitionObserver.addTaskInfosToCachedMap(freeformTask) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build()) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) // Enter desktop through back transition, this happens when user enters after dismissing // keyguard val change = createChange(TRANSIT_TO_FRONT, freeformTask) @@ -396,11 +395,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { fun transitEndDragToDesktop_previousExitReasonScreenOff_logTaskAddedAndEnterReasonAppDrag() { val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM) // Previous Exit reason recorded as Screen Off - val sessionId = 1 transitionObserver.addTaskInfosToCachedMap(freeformTask) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build()) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) // Enter desktop through app handle drag. This represents cases where instead of moving to // desktop right after turning the screen on, we move to fullscreen then move another task @@ -416,24 +414,22 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { } @Test - fun transitSleep_logTaskRemovedAndExitReasonScreenOff_sessionIdNull() { - val sessionId = 1 + fun transitSleep_logTaskRemovedAndExitReasonScreenOff() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) } @Test - fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit_sessionIdNull() { - val sessionId = 1 + fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // window mode changing from FREEFORM to FULLSCREEN val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) @@ -441,15 +437,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE) } @Test - fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton_sessionIdNull() { - val sessionId = 1 + fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // window mode changing from FREEFORM to FULLSCREEN val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) @@ -459,16 +454,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { .build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging( - sessionId, ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE) } @Test - fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard_sessionIdNull() { - val sessionId = 1 + fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // window mode changing from FREEFORM to FULLSCREEN val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) @@ -476,16 +469,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT).addChange(change).build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging( - sessionId, ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE) } @Test - fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown_sessionIdNull() { - val sessionId = 1 + fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // window mode changing from FREEFORM to FULLSCREEN val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) @@ -493,15 +484,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE) } @Test - fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview_sessionIdNull() { - val sessionId = 1 + fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // recents transition val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM)) @@ -510,31 +500,30 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo) verifyTaskRemovedAndExitLogging( - sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) } @Test - fun transitClose_logTaskRemovedAndExitReasonTaskFinished_sessionIdNull() { - val sessionId = 1 + fun transitClose_logTaskRemovedAndExitReasonTaskFinished() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // task closing val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE) } @Test fun sessionExitByRecents_cancelledAnimation_sessionRestored() { - val sessionId = 1 // add a freeform task to an existing session val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(taskInfo) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // recents transition sent freeform window to back val change = createChange(TRANSIT_TO_BACK, taskInfo) @@ -543,7 +532,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo1) verifyTaskRemovedAndExitLogging( - sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build() callOnTransitionReady(transitionInfo2) @@ -554,10 +544,9 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @Test fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() { - val sessionId = 1 // add an existing freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // new freeform task added val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) @@ -565,18 +554,16 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo) verify(desktopModeEventLogger, times(1)) - .logTaskAdded(eq(sessionId), - eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2))) - verify(desktopModeEventLogger, never()).logSessionEnter(any(), any()) + .logTaskAdded(eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2))) + verify(desktopModeEventLogger, never()).logSessionEnter(any()) } @Test fun sessionAlreadyStarted_taskPositionChanged_logsTaskUpdate() { - val sessionId = 1 // add an existing freeform task val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(taskInfo) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // task position changed val newTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) @@ -588,18 +575,17 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verify(desktopModeEventLogger, times(1)) .logTaskInfoChanged( - eq(sessionId), - eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1))) + eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1)) + ) verifyZeroInteractions(desktopModeEventLogger) } @Test fun sessionAlreadyStarted_taskResized_logsTaskUpdate() { - val sessionId = 1 // add an existing freeform task val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(taskInfo) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // task resized val newTaskInfo = @@ -615,23 +601,22 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verify(desktopModeEventLogger, times(1)) .logTaskInfoChanged( - eq(sessionId), eq( DEFAULT_TASK_UPDATE.copy( taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100, - visibleTaskCount = 1))) + visibleTaskCount = 1)) + ) verifyZeroInteractions(desktopModeEventLogger) } @Test fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() { - val sessionId = 1 // add 2 existing freeform task val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM) val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2) transitionObserver.addTaskInfosToCachedMap(taskInfo1) transitionObserver.addTaskInfosToCachedMap(taskInfo2) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // task 1 position update val newTaskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) @@ -643,8 +628,9 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verify(desktopModeEventLogger, times(1)) .logTaskInfoChanged( - eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy( - taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2))) + eq(DEFAULT_TASK_UPDATE.copy( + taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2)) + ) verifyZeroInteractions(desktopModeEventLogger) // task 2 resize @@ -663,7 +649,6 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verify(desktopModeEventLogger, times(1)) .logTaskInfoChanged( - eq(sessionId), eq( DEFAULT_TASK_UPDATE.copy( instanceId = 2, @@ -676,11 +661,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @Test fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() { - val sessionId = 1 // add two existing freeform tasks transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // new freeform task closed val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) @@ -688,9 +672,11 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo) verify(desktopModeEventLogger, times(1)) - .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy( - instanceId = 2, visibleTaskCount = 1))) - verify(desktopModeEventLogger, never()).logSessionExit(any(), any()) + .logTaskRemoved( + eq(DEFAULT_TASK_UPDATE.copy( + instanceId = 2, visibleTaskCount = 1)) + ) + verify(desktopModeEventLogger, never()).logSessionExit(any()) } /** Simulate calling the onTransitionReady() method */ @@ -703,10 +689,9 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { } private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) { - val sessionId = transitionObserver.getLoggerSessionId() - assertNotNull(sessionId) - verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), eq(enterReason)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), eq(taskUpdate)) + assertTrue(transitionObserver.isSessionActive) + verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(enterReason)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(taskUpdate)) ExtendedMockito.verify { Trace.setCounter( eq(Trace.TRACE_TAG_WINDOW_MANAGER), @@ -722,14 +707,13 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { } private fun verifyTaskRemovedAndExitLogging( - sessionId: Int, exitReason: ExitReason, taskUpdate: TaskUpdate ) { - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), eq(taskUpdate)) - verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), eq(exitReason)) + assertFalse(transitionObserver.isSessionActive) + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(taskUpdate)) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(exitReason)) verifyZeroInteractions(desktopModeEventLogger) - assertNull(transitionObserver.getLoggerSessionId()) } private companion object { -- GitLab From 88a11936baf2e2149ac3c7506f2426022116bc09 Mon Sep 17 00:00:00 2001 From: Santiago Seifert Date: Tue, 15 Oct 2024 14:42:38 +0000 Subject: [PATCH 178/441] Remove dead code related to playback suitability Bug: b/279555229 Test: m - non functional change. Flag: EXEMPT dead code removal Change-Id: Id4c46e85c9ed9a8acb174fc056a5d2ad508bccf5 --- .../java/android/media/IMediaRouterService.aidl | 3 +-- media/java/android/media/MediaRouter2.java | 14 +++----------- .../java/android/media/MediaRouter2Manager.java | 10 +++------- .../server/media/MediaRouter2ServiceImpl.java | 17 +++-------------- .../server/media/MediaRouterService.java | 12 ++---------- 5 files changed, 12 insertions(+), 44 deletions(-) diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index eeb4853afadc..961962f6a010 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -85,8 +85,7 @@ interface IMediaRouterService { void updateScanningState(IMediaRouter2Manager manager, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState); void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, - in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route, - in UserHandle transferInitiatorUserHandle, in String transferInitiatorPackageName); + in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route); void selectRouteWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, in MediaRoute2Info route); void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId, diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index b84990b54bd5..3499c438086d 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -2770,7 +2770,7 @@ public final class MediaRouter2 { || isSystemRouteReselection) { transferToRoute(sessionInfo, route, mClientUser, mClientPackageName); } else { - requestCreateSession(sessionInfo, route, mClientUser, mClientPackageName); + requestCreateSession(sessionInfo, route); } } @@ -2826,10 +2826,7 @@ public final class MediaRouter2 { * @param route The {@link MediaRoute2Info route} to transfer to. */ private void requestCreateSession( - @NonNull RoutingSessionInfo oldSession, - @NonNull MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { + @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) { if (TextUtils.isEmpty(oldSession.getClientPackageName())) { Log.w(TAG, "requestCreateSession: Can't create a session without package name."); this.onTransferFailed(oldSession, route); @@ -2840,12 +2837,7 @@ public final class MediaRouter2 { try { mMediaRouterService.requestCreateSessionWithManager( - mClient, - requestId, - oldSession, - route, - transferInitiatorUserHandle, - transferInitiatorPackageName); + mClient, requestId, oldSession, route); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 8fa0e49e8b96..7e1dccf2d366 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -524,8 +524,7 @@ public final class MediaRouter2Manager { transferToRoute( sessionInfo, route, transferInitiatorUserHandle, transferInitiatorPackageName); } else { - requestCreateSession(sessionInfo, route, transferInitiatorUserHandle, - transferInitiatorPackageName); + requestCreateSession(sessionInfo, route); } } @@ -914,9 +913,7 @@ public final class MediaRouter2Manager { } } - private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiationPackageName) { + private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route) { if (TextUtils.isEmpty(oldSession.getClientPackageName())) { Log.w(TAG, "requestCreateSession: Can't create a session without package name."); notifyTransferFailed(oldSession, route); @@ -927,8 +924,7 @@ public final class MediaRouter2Manager { try { mMediaRouterService.requestCreateSessionWithManager( - mClient, requestId, oldSession, route, transferInitiatorUserHandle, - transferInitiationPackageName); + mClient, requestId, oldSession, route); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 8b06dadbaeeb..c85907cf8018 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -677,24 +677,15 @@ class MediaRouter2ServiceImpl { @NonNull IMediaRouter2Manager manager, int requestId, @NonNull RoutingSessionInfo oldSession, - @NonNull MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { + @NonNull MediaRoute2Info route) { Objects.requireNonNull(manager, "manager must not be null"); Objects.requireNonNull(oldSession, "oldSession must not be null"); Objects.requireNonNull(route, "route must not be null"); - Objects.requireNonNull(transferInitiatorUserHandle); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestCreateSessionWithManagerLocked( - requestId, - manager, - oldSession, - route, - transferInitiatorUserHandle, - transferInitiatorPackageName); + requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route); } } finally { Binder.restoreCallingIdentity(token); @@ -1745,9 +1736,7 @@ class MediaRouter2ServiceImpl { int requestId, @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession, - @NonNull MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { + @NonNull MediaRoute2Info route) { ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder()); if (managerRecord == null) { return; diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 363b8e4228b0..68e195d7f079 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -607,16 +607,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub IMediaRouter2Manager manager, int requestId, RoutingSessionInfo oldSession, - MediaRoute2Info route, - UserHandle transferInitiatorUserHandle, - String transferInitiatorPackageName) { - mService2.requestCreateSessionWithManager( - manager, - requestId, - oldSession, - route, - transferInitiatorUserHandle, - transferInitiatorPackageName); + MediaRoute2Info route) { + mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route); } // Binder call -- GitLab From aa04336b3aa2b2beb4a0f659e3c7a207365b3696 Mon Sep 17 00:00:00 2001 From: Cam Bickel Date: Fri, 4 Oct 2024 23:05:50 +0000 Subject: [PATCH 179/441] audio: Only replace route info when Bluetooth device is bonded This fixes a bug that causes non-Bluetooth devices with an `address` to have their routeId and deviceName replaced with null. The bug is fixed by detecting if the given AudioDeviceInfo is a bonded Bluetooth device before attempting to replace its info. Bug: b/371630439 Test: atest AudioManagerRouteControllerTest Flag: EXEMPT bugfix Change-Id: Ibe2452d064def3e57cfd88c0b6ed354e5dec6fd2 --- .../media/AudioManagerRouteController.java | 2 +- .../media/BluetoothDeviceRoutesManager.java | 5 ++ .../AudioManagerRouteControllerTest.java | 54 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java index c79f41d32b29..25e1c94aa689 100644 --- a/services/core/java/com/android/server/media/AudioManagerRouteController.java +++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java @@ -420,7 +420,7 @@ import java.util.Objects; // to derive a name ourselves from the type instead. String deviceName = audioDeviceInfo.getPort().name(); - if (!TextUtils.isEmpty(address)) { + if (mBluetoothRouteController.containsBondedDeviceWithAddress(address)) { routeId = mBluetoothRouteController.getRouteIdForBluetoothAddress(address); deviceName = mBluetoothRouteController.getNameForBluetoothAddress(address); } diff --git a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java index 8b65ea305ad8..c79d6e5400cd 100644 --- a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java +++ b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java @@ -141,6 +141,11 @@ import java.util.stream.Collectors; mContext.unregisterReceiver(mDeviceStateChangedReceiver); } + /** Returns true if the given address corresponds to a currently-bonded Bluetooth device. */ + public synchronized boolean containsBondedDeviceWithAddress(@Nullable String address) { + return mAddressToBondedDevice.containsKey(address); + } + @Nullable public synchronized String getRouteIdForBluetoothAddress(@Nullable String address) { BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address); diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java index 121145672d68..439243e85e75 100644 --- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java +++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java @@ -82,6 +82,11 @@ public class AudioManagerRouteControllerTest { private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET = createAudioDeviceInfo( AudioSystem.DEVICE_OUT_WIRED_HEADSET, "name_wired_hs", /* address= */ null); + private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS = + createAudioDeviceInfo( + AudioSystem.DEVICE_OUT_WIRED_HEADSET, + "name_wired_hs_with_address", + /* address= */ "card=1;device=0"); private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP = createAudioDeviceInfo( AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "name_a2dp", /* address= */ "12:34:45"); @@ -304,6 +309,55 @@ public class AudioManagerRouteControllerTest { assertThat(selectedRoute.getName().toString()).isEqualTo(FAKE_ROUTE_NAME); } + @Test + public void getAvailableRoutes_whenAddressIsPopulatedForNonBluetoothDevice_usesCorrectName() { + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS, + FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP); + + List availableRoutes = mControllerUnderTest.getAvailableRoutes(); + assertThat(availableRoutes.size()).isEqualTo(3); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET) + .getName() + .toString()) + .isEqualTo( + FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS + .getProductName() + .toString()); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP) + .getName() + .toString()) + .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP.getProductName().toString()); + } + + @Test + public void + getAvailableRoutes_whenAddressIsNotPopulatedForNonBluetoothDevice_usesCorrectName() { + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); + + List availableRoutes = mControllerUnderTest.getAvailableRoutes(); + assertThat(availableRoutes.size()).isEqualTo(2); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) + .getName() + .toString()) + .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER.getProductName().toString()); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET) + .getName() + .toString()) + .isEqualTo(FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET.getProductName().toString()); + } + // Internal methods. @NonNull -- GitLab From 064443125effd29c071bf2b0c69391e1870433ea Mon Sep 17 00:00:00 2001 From: helencheuk Date: Tue, 15 Oct 2024 14:37:48 +0100 Subject: [PATCH 180/441] [Contextual Edu] Accessibility - Not change focus when ContextualEduDialog is shown This toast-like dialog should be unobtrusive to user interaction: 1) should allow interaction with background elements 2) should not change the focus as user might be already focusing on another item Bug: 372895069 Test: ContextualEduDialogTest Flag: com.android.systemui.keyboard_touchpad_contextual_education Change-Id: I2f0c341748cb651ab750fa738071c892d76b8e9f --- .../domain/ui/view/ContextualEduDialogTest.kt | 72 +++++++++++++++++++ .../education/ui/view/ContextualEduDialog.kt | 30 +++++++- .../ui/view/ContextualEduUiCoordinator.kt | 4 +- 3 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt new file mode 100644 index 000000000000..90727b240caf --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 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.education.domain.ui.view + +import android.testing.TestableLooper +import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityManager +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.EmptyTestActivity +import com.android.systemui.education.ui.view.ContextualEduDialog +import com.android.systemui.education.ui.viewmodel.ContextualEduToastViewModel +import kotlin.test.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.firstValue +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +class ContextualEduDialogTest : SysuiTestCase() { + @Rule + @JvmField + val activityRule: ActivityScenarioRule = + ActivityScenarioRule(EmptyTestActivity::class.java) + @get:Rule val mockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var accessibilityManager: AccessibilityManager + private lateinit var underTest: ContextualEduDialog + + @Before + fun setUp() { + whenever(accessibilityManager.isEnabled).thenReturn(true) + } + + @Test + fun sendAccessibilityInfo() { + val message = "Testing message" + val viewModel = ContextualEduToastViewModel(message, icon = 0, userId = 0) + activityRule.scenario.onActivity { + underTest = ContextualEduDialog(context, viewModel, accessibilityManager) + underTest.show() + } + + val eventCaptor = ArgumentCaptor.forClass(AccessibilityEvent::class.java) + verify(accessibilityManager).sendAccessibilityEvent(eventCaptor.capture()) + assertEquals(message, eventCaptor.firstValue.text[0]) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt index ca92953dca4a..1439ecde3bfd 100644 --- a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt @@ -22,13 +22,18 @@ import android.os.Bundle import android.view.Gravity import android.view.Window import android.view.WindowManager +import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityManager import android.widget.ImageView import android.widget.TextView import com.android.systemui.education.ui.viewmodel.ContextualEduToastViewModel import com.android.systemui.res.R -class ContextualEduDialog(context: Context, private val model: ContextualEduToastViewModel) : - Dialog(context) { +class ContextualEduDialog( + context: Context, + private val model: ContextualEduToastViewModel, + private val accessibilityManager: AccessibilityManager, +) : Dialog(context) { override fun onCreate(savedInstanceState: Bundle?) { setUpWindowProperties() setWindowPosition() @@ -36,6 +41,7 @@ class ContextualEduDialog(context: Context, private val model: ContextualEduToas window?.setTitle(context.getString(R.string.contextual_education_dialog_title)) setContentView(R.layout.contextual_edu_dialog) setContent() + sendAccessibilityEvent() super.onCreate(savedInstanceState) } @@ -44,10 +50,30 @@ class ContextualEduDialog(context: Context, private val model: ContextualEduToas findViewById(R.id.edu_icon)?.let { it.setImageResource(model.icon) } } + private fun sendAccessibilityEvent() { + if (!accessibilityManager.isEnabled) { + return + } + + // It is a toast-like dialog which is unobtrusive and not focusable. So it needs to call + // accessibilityManager.sendAccessibilityEvent explicitly to announce the message. + accessibilityManager.sendAccessibilityEvent( + AccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT).apply { + text.add(model.message) + } + ) + } + private fun setUpWindowProperties() { window?.apply { requestFeature(Window.FEATURE_NO_TITLE) setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG) + // NOT_TOUCH_MODAL allows users to interact with background elements and NOT_FOCUSABLE + // avoids changing the existing focus when dialog is shown. + addFlags( + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + ) clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) setBackgroundDrawableResource(android.R.color.transparent) } diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt index 913ecdd4aadc..1996efa14d7c 100644 --- a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt @@ -25,6 +25,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.os.UserHandle +import android.view.accessibility.AccessibilityManager import androidx.core.app.NotificationCompat import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton @@ -64,12 +65,13 @@ constructor( context: Context, viewModel: ContextualEduViewModel, notificationManager: NotificationManager, + accessibilityManager: AccessibilityManager, ) : this( applicationScope, viewModel, context, notificationManager, - createDialog = { model -> ContextualEduDialog(context, model) }, + createDialog = { model -> ContextualEduDialog(context, model, accessibilityManager) }, ) var dialog: Dialog? = null -- GitLab From 5de14166adb4e6dfc5a3039a7ae098ea946c07d1 Mon Sep 17 00:00:00 2001 From: Xin Guan Date: Sun, 13 Oct 2024 02:48:31 +0000 Subject: [PATCH 181/441] JobScheduler: Remove quota bump Clean it up as it was never used. Bug: 373069645 Test: atest FrameworksMockingServicesTests:com.android.server.job.controllers.QuotaControllerTest Flag: EXEMPT code cleanup Change-Id: I8d08f1942cd158aa7456803197b2c5c5a7d916e7 --- .../job/controllers/QuotaController.java | 257 +---------- .../job/controllers/QuotaControllerTest.java | 426 +----------------- 2 files changed, 7 insertions(+), 676 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 03a3a0d51891..46cc3f01d261 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -498,14 +498,6 @@ public final class QuotaController extends StateController { private long mEJGracePeriodTopAppMs = QcConstants.DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; - private long mQuotaBumpAdditionalDurationMs = - QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS; - private int mQuotaBumpAdditionalJobCount = QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT; - private int mQuotaBumpAdditionalSessionCount = - QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT; - private long mQuotaBumpWindowSizeMs = QcConstants.DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS; - private int mQuotaBumpLimit = QcConstants.DEFAULT_QUOTA_BUMP_LIMIT; - /** * List of system apps with the {@link android.Manifest.permission#INSTALL_PACKAGES} permission * granted for each user. @@ -1095,7 +1087,7 @@ public final class QuotaController extends StateController { // essentially run until they reach the maximum limit. if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { return calculateTimeUntilQuotaConsumedLocked( - events, startMaxElapsed, maxExecutionTimeRemainingMs, false); + events, startMaxElapsed, maxExecutionTimeRemainingMs); } // Need to check both max time and period time in case one is less than the other. @@ -1104,9 +1096,9 @@ public final class QuotaController extends StateController { // bucket value. return Math.min( calculateTimeUntilQuotaConsumedLocked( - events, startMaxElapsed, maxExecutionTimeRemainingMs, false), + events, startMaxElapsed, maxExecutionTimeRemainingMs), calculateTimeUntilQuotaConsumedLocked( - events, startWindowElapsed, allowedTimeRemainingMs, true)); + events, startWindowElapsed, allowedTimeRemainingMs)); } /** @@ -1116,36 +1108,12 @@ public final class QuotaController extends StateController { * @param deadSpaceMs How much time can be allowed to count towards the quota */ private long calculateTimeUntilQuotaConsumedLocked(@NonNull List sessions, - final long windowStartElapsed, long deadSpaceMs, boolean allowQuotaBumps) { + final long windowStartElapsed, long deadSpaceMs) { long timeUntilQuotaConsumedMs = 0; long start = windowStartElapsed; - int numQuotaBumps = 0; - final long quotaBumpWindowStartElapsed = - sElapsedRealtimeClock.millis() - mQuotaBumpWindowSizeMs; final int numSessions = sessions.size(); - if (allowQuotaBumps) { - for (int i = numSessions - 1; i >= 0; --i) { - TimedEvent event = sessions.get(i); - - if (event instanceof QuotaBump) { - if (event.getEndTimeElapsed() >= quotaBumpWindowStartElapsed - && numQuotaBumps++ < mQuotaBumpLimit) { - deadSpaceMs += mQuotaBumpAdditionalDurationMs; - } else { - break; - } - } - } - } for (int i = 0; i < numSessions; ++i) { - TimedEvent event = sessions.get(i); - - if (event instanceof QuotaBump) { - continue; - } - - TimingSession session = (TimingSession) event; - + TimingSession session = (TimingSession) sessions.get(i); if (session.endTimeElapsed < windowStartElapsed) { // Outside of window. Ignore. continue; @@ -1330,41 +1298,15 @@ public final class QuotaController extends StateController { final long startWindowElapsed = nowElapsed - stats.windowSizeMs; final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; int sessionCountInWindow = 0; - int numQuotaBumps = 0; - final long quotaBumpWindowStartElapsed = nowElapsed - mQuotaBumpWindowSizeMs; // The minimum time between the start time and the beginning of the events that were // looked at --> how much time the stats will be valid for. long emptyTimeMs = Long.MAX_VALUE; // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get // the most recent ones. final int loopStart = events.size() - 1; - // Process QuotaBumps first to ensure the limits are properly adjusted. - for (int i = loopStart; i >= 0; --i) { - TimedEvent event = events.get(i); - - if (event.getEndTimeElapsed() < quotaBumpWindowStartElapsed - || numQuotaBumps >= mQuotaBumpLimit) { - break; - } - - if (event instanceof QuotaBump) { - stats.allowedTimePerPeriodMs += mQuotaBumpAdditionalDurationMs; - stats.jobCountLimit += mQuotaBumpAdditionalJobCount; - stats.sessionCountLimit += mQuotaBumpAdditionalSessionCount; - emptyTimeMs = Math.min(emptyTimeMs, - event.getEndTimeElapsed() - quotaBumpWindowStartElapsed); - numQuotaBumps++; - } - } TimingSession lastSeenTimingSession = null; for (int i = loopStart; i >= 0; --i) { - TimedEvent event = events.get(i); - - if (event instanceof QuotaBump) { - continue; - } - - TimingSession session = (TimingSession) event; + TimingSession session = (TimingSession) events.get(i); // Window management. if (startWindowElapsed < session.endTimeElapsed) { @@ -2057,28 +1999,6 @@ public final class QuotaController extends StateController { } } - @VisibleForTesting - static final class QuotaBump implements TimedEvent { - // Event timestamp in elapsed realtime timebase. - public final long eventTimeElapsed; - - QuotaBump(long eventElapsed) { - this.eventTimeElapsed = eventElapsed; - } - - @Override - public long getEndTimeElapsed() { - return eventTimeElapsed; - } - - @Override - public void dump(IndentingPrintWriter pw) { - pw.print("Quota bump @ "); - pw.print(eventTimeElapsed); - pw.println(); - } - } - @VisibleForTesting static final class ShrinkableDebits { /** The amount of quota remaining. Can be negative if limit changes. */ @@ -2528,21 +2448,6 @@ public final class QuotaController extends StateController { updateStandbyBucket(userId, packageName, bucketIndex); }); } - - @Override - public void triggerTemporaryQuotaBump(String packageName, @UserIdInt int userId) { - synchronized (mLock) { - List events = mTimingEvents.get(userId, packageName); - if (events == null || events.size() == 0) { - // If the app hasn't run any jobs, there's no point giving it a quota bump. - return; - } - events.add(new QuotaBump(sElapsedRealtimeClock.millis())); - invalidateAllExecutionStatsLocked(userId, packageName); - } - // Update jobs out of band. - mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); - } } @VisibleForTesting @@ -3019,7 +2924,6 @@ public final class QuotaController extends StateController { mQcConstants.mRateLimitingConstantsUpdated = false; mQcConstants.mExecutionPeriodConstantsUpdated = false; mQcConstants.mEJLimitConstantsUpdated = false; - mQcConstants.mQuotaBumpConstantsUpdated = false; } @Override @@ -3046,7 +2950,6 @@ public final class QuotaController extends StateController { private boolean mRateLimitingConstantsUpdated = false; private boolean mExecutionPeriodConstantsUpdated = false; private boolean mEJLimitConstantsUpdated = false; - private boolean mQuotaBumpConstantsUpdated = false; /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ private static final String QC_CONSTANT_PREFIX = "qc_"; @@ -3194,21 +3097,6 @@ public final class QuotaController extends StateController { @VisibleForTesting static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS = QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms"; - @VisibleForTesting - static final String KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS = - QC_CONSTANT_PREFIX + "quota_bump_additional_duration_ms"; - @VisibleForTesting - static final String KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT = - QC_CONSTANT_PREFIX + "quota_bump_additional_job_count"; - @VisibleForTesting - static final String KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = - QC_CONSTANT_PREFIX + "quota_bump_additional_session_count"; - @VisibleForTesting - static final String KEY_QUOTA_BUMP_WINDOW_SIZE_MS = - QC_CONSTANT_PREFIX + "quota_bump_window_size_ms"; - @VisibleForTesting - static final String KEY_QUOTA_BUMP_LIMIT = - QC_CONSTANT_PREFIX + "quota_bump_limit"; private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 10 * 60 * 1000L; // 10 minutes @@ -3281,11 +3169,6 @@ public final class QuotaController extends StateController { private static final long DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS = 0; private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * MINUTE_IN_MILLIS; - private static final long DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS = 1 * MINUTE_IN_MILLIS; - private static final int DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT = 2; - private static final int DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = 1; - private static final long DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS = 8 * HOUR_IN_MILLIS; - private static final int DEFAULT_QUOTA_BUMP_LIMIT = 8; /** * How much time each app in the exempted bucket will have to run jobs within their standby @@ -3587,33 +3470,6 @@ public final class QuotaController extends StateController { */ public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; - /** - * How much additional session duration to give an app for each accepted quota bump. - */ - public long QUOTA_BUMP_ADDITIONAL_DURATION_MS = DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS; - - /** - * How many additional regular jobs to give an app for each accepted quota bump. - */ - public int QUOTA_BUMP_ADDITIONAL_JOB_COUNT = DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT; - - /** - * How many additional sessions to give an app for each accepted quota bump. - */ - public int QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = - DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT; - - /** - * The rolling window size within which to accept and apply quota bump events. - */ - public long QUOTA_BUMP_WINDOW_SIZE_MS = DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS; - - /** - * The maximum number of quota bumps to accept and apply within the - * {@link #QUOTA_BUMP_WINDOW_SIZE_MS window}. - */ - public int QUOTA_BUMP_LIMIT = DEFAULT_QUOTA_BUMP_LIMIT; - public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @NonNull String key) { switch (key) { @@ -3650,14 +3506,6 @@ public final class QuotaController extends StateController { updateEJLimitConstantsLocked(); break; - case KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS: - case KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT: - case KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT: - case KEY_QUOTA_BUMP_WINDOW_SIZE_MS: - case KEY_QUOTA_BUMP_LIMIT: - updateQuotaBumpConstantsLocked(); - break; - case KEY_MAX_JOB_COUNT_EXEMPTED: MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED); int newExemptedMaxJobCount = @@ -4156,65 +4004,6 @@ public final class QuotaController extends StateController { } } - private void updateQuotaBumpConstantsLocked() { - if (mQuotaBumpConstantsUpdated) { - return; - } - mQuotaBumpConstantsUpdated = true; - - // Query the values as an atomic set. - final DeviceConfig.Properties properties = DeviceConfig.getProperties( - DeviceConfig.NAMESPACE_JOB_SCHEDULER, - KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, - KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, - KEY_QUOTA_BUMP_WINDOW_SIZE_MS, KEY_QUOTA_BUMP_LIMIT); - QUOTA_BUMP_ADDITIONAL_DURATION_MS = properties.getLong( - KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, - DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS); - QUOTA_BUMP_ADDITIONAL_JOB_COUNT = properties.getInt( - KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT); - QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = properties.getInt( - KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, - DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT); - QUOTA_BUMP_WINDOW_SIZE_MS = properties.getLong( - KEY_QUOTA_BUMP_WINDOW_SIZE_MS, DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS); - QUOTA_BUMP_LIMIT = properties.getInt( - KEY_QUOTA_BUMP_LIMIT, DEFAULT_QUOTA_BUMP_LIMIT); - - // The window must be in the range [1 hour, 24 hours]. - long newWindowSizeMs = Math.max(HOUR_IN_MILLIS, - Math.min(MAX_PERIOD_MS, QUOTA_BUMP_WINDOW_SIZE_MS)); - if (mQuotaBumpWindowSizeMs != newWindowSizeMs) { - mQuotaBumpWindowSizeMs = newWindowSizeMs; - mShouldReevaluateConstraints = true; - } - // The limit must be nonnegative. - int newLimit = Math.max(0, QUOTA_BUMP_LIMIT); - if (mQuotaBumpLimit != newLimit) { - mQuotaBumpLimit = newLimit; - mShouldReevaluateConstraints = true; - } - // The job count must be nonnegative. - int newJobAddition = Math.max(0, QUOTA_BUMP_ADDITIONAL_JOB_COUNT); - if (mQuotaBumpAdditionalJobCount != newJobAddition) { - mQuotaBumpAdditionalJobCount = newJobAddition; - mShouldReevaluateConstraints = true; - } - // The session count must be nonnegative. - int newSessionAddition = Math.max(0, QUOTA_BUMP_ADDITIONAL_SESSION_COUNT); - if (mQuotaBumpAdditionalSessionCount != newSessionAddition) { - mQuotaBumpAdditionalSessionCount = newSessionAddition; - mShouldReevaluateConstraints = true; - } - // The additional duration must be in the range [0, 10 minutes]. - long newAdditionalDuration = Math.max(0, - Math.min(10 * MINUTE_IN_MILLIS, QUOTA_BUMP_ADDITIONAL_DURATION_MS)); - if (mQuotaBumpAdditionalDurationMs != newAdditionalDuration) { - mQuotaBumpAdditionalDurationMs = newAdditionalDuration; - mShouldReevaluateConstraints = true; - } - } - private void dump(IndentingPrintWriter pw) { pw.println(); pw.println("QuotaController:"); @@ -4277,15 +4066,6 @@ public final class QuotaController extends StateController { EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS).println(); pw.print(KEY_EJ_GRACE_PERIOD_TOP_APP_MS, EJ_GRACE_PERIOD_TOP_APP_MS).println(); - pw.print(KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, - QUOTA_BUMP_ADDITIONAL_DURATION_MS).println(); - pw.print(KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, - QUOTA_BUMP_ADDITIONAL_JOB_COUNT).println(); - pw.print(KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, - QUOTA_BUMP_ADDITIONAL_SESSION_COUNT).println(); - pw.print(KEY_QUOTA_BUMP_WINDOW_SIZE_MS, QUOTA_BUMP_WINDOW_SIZE_MS).println(); - pw.print(KEY_QUOTA_BUMP_LIMIT, QUOTA_BUMP_LIMIT).println(); - pw.decreaseIndent(); } @@ -4503,31 +4283,6 @@ public final class QuotaController extends StateController { return mQcConstants; } - @VisibleForTesting - long getQuotaBumpAdditionDurationMs() { - return mQuotaBumpAdditionalDurationMs; - } - - @VisibleForTesting - int getQuotaBumpAdditionJobCount() { - return mQuotaBumpAdditionalJobCount; - } - - @VisibleForTesting - int getQuotaBumpAdditionSessionCount() { - return mQuotaBumpAdditionalSessionCount; - } - - @VisibleForTesting - int getQuotaBumpLimit() { - return mQuotaBumpLimit; - } - - @VisibleForTesting - long getQuotaBumpWindowSizeMs() { - return mQuotaBumpWindowSizeMs; - } - //////////////////////////// DATA DUMP ////////////////////////////// @NeverCompile // Avoid size overhead of debugging code. diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 31157f9e27dc..9781851da7e6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -95,7 +95,6 @@ import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; import com.android.server.job.controllers.QuotaController.ExecutionStats; import com.android.server.job.controllers.QuotaController.QcConstants; -import com.android.server.job.controllers.QuotaController.QuotaBump; import com.android.server.job.controllers.QuotaController.ShrinkableDebits; import com.android.server.job.controllers.QuotaController.TimedEvent; import com.android.server.job.controllers.QuotaController.TimingSession; @@ -136,7 +135,6 @@ public class QuotaControllerTest { private QuotaController.QcConstants mQcConstants; private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); private int mSourceUid; - private AppStandbyInternal.AppIdleStateChangeListener mAppIdleStateChangeListener; private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener; private IUidObserver mUidObserver; private UsageStatsManagerInternal.UsageEventListener mUsageEventListener; @@ -191,8 +189,7 @@ public class QuotaControllerTest { when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager); doReturn(mActivityMangerInternal) .when(() -> LocalServices.getService(ActivityManagerInternal.class)); - final AppStandbyInternal appStandbyInternal = mock(AppStandbyInternal.class); - doReturn(appStandbyInternal) + doReturn(mock(AppStandbyInternal.class)) .when(() -> LocalServices.getService(AppStandbyInternal.class)); doReturn(mock(BatteryManagerInternal.class)) .when(() -> LocalServices.getService(BatteryManagerInternal.class)); @@ -239,8 +236,6 @@ public class QuotaControllerTest { // Initialize real objects. // Capture the listeners. - ArgumentCaptor aiscListenerCaptor = - ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class); ArgumentCaptor uidObserverCaptor = ArgumentCaptor.forClass(IUidObserver.class); ArgumentCaptor taChangeCaptor = @@ -250,8 +245,6 @@ public class QuotaControllerTest { mQuotaController = new QuotaController(mJobSchedulerService, mock(BackgroundJobsController.class), mock(ConnectivityController.class)); - verify(appStandbyInternal).addListener(aiscListenerCaptor.capture()); - mAppIdleStateChangeListener = aiscListenerCaptor.getValue(); verify(mPowerAllowlistInternal) .registerTempAllowlistChangeListener(taChangeCaptor.capture()); mTempAllowlistListener = taChangeCaptor.getValue(); @@ -488,14 +481,12 @@ public class QuotaControllerTest { now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); TimingSession two = createTimingSession( now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); - QuotaBump bump1 = new QuotaBump(now - 2 * HOUR_IN_MILLIS); TimingSession thr = createTimingSession( now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); // Overlaps 24 hour boundary. TimingSession fou = createTimingSession( now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1); // Way past the 24 hour boundary. - QuotaBump bump2 = new QuotaBump(now - 24 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); TimingSession fiv = createTimingSession( now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4); List expectedRegular = new ArrayList<>(); @@ -503,16 +494,13 @@ public class QuotaControllerTest { // Added in correct (chronological) order. expectedRegular.add(fou); expectedRegular.add(thr); - expectedRegular.add(bump1); expectedRegular.add(two); expectedRegular.add(one); expectedEJ.add(fou); expectedEJ.add(one); mQuotaController.saveTimingSession(0, "com.android.test", fiv, false); - mQuotaController.getTimingSessions(0, "com.android.test").add(bump2); mQuotaController.saveTimingSession(0, "com.android.test", fou, false); mQuotaController.saveTimingSession(0, "com.android.test", thr, false); - mQuotaController.getTimingSessions(0, "com.android.test").add(bump1); mQuotaController.saveTimingSession(0, "com.android.test", two, false); mQuotaController.saveTimingSession(0, "com.android.test", one, false); mQuotaController.saveTimingSession(0, "com.android.test", fiv, true); @@ -1847,129 +1835,6 @@ public class QuotaControllerTest { } } - /** - * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket - * window and there are valid QuotaBumps in the history. - */ - @Test - public void testGetTimeUntilQuotaConsumedLocked_QuotaBump() { - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); - - final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - // Close to RARE boundary. - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS), - 30 * SECOND_IN_MILLIS, 5), false); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 16 * HOUR_IN_MILLIS)); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 12 * HOUR_IN_MILLIS)); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 8 * HOUR_IN_MILLIS)); - // Far away from FREQUENT boundary. - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); - // Overlap WORKING_SET boundary. - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 2 * HOUR_IN_MILLIS)); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), - 3 * MINUTE_IN_MILLIS, 5), false); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS)); - // Close to ACTIVE boundary. - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); - - setStandbyBucket(RARE_INDEX); - synchronized (mQuotaController.mLock) { - assertEquals(3 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, - mQuotaController.getRemainingExecutionTimeLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(4 * MINUTE_IN_MILLIS, - mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - } - - setStandbyBucket(FREQUENT_INDEX); - synchronized (mQuotaController.mLock) { - assertEquals(4 * MINUTE_IN_MILLIS, - mQuotaController.getRemainingExecutionTimeLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(4 * MINUTE_IN_MILLIS, - mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - } - - setStandbyBucket(WORKING_INDEX); - synchronized (mQuotaController.mLock) { - assertEquals(8 * MINUTE_IN_MILLIS, - mQuotaController.getRemainingExecutionTimeLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(10 * MINUTE_IN_MILLIS, - mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - } - - // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the - // max execution time. - setStandbyBucket(ACTIVE_INDEX); - synchronized (mQuotaController.mLock) { - assertEquals(10 * MINUTE_IN_MILLIS, - mQuotaController.getRemainingExecutionTimeLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS, - mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - } - } - - /** - * Test getTimeUntilQuotaConsumedLocked when there are valid QuotaBumps in recent history that - * provide enough additional quota to bridge gaps between sessions. - */ - @Test - public void testGetTimeUntilQuotaConsumedLocked_QuotaBump_CrucialBumps() { - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); - - final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (25 * HOUR_IN_MILLIS), - 30 * MINUTE_IN_MILLIS, 25), false); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 16 * HOUR_IN_MILLIS)); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 12 * HOUR_IN_MILLIS)); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 8 * HOUR_IN_MILLIS)); - // Without the valid quota bumps, the app would only 3 minutes until the quota was consumed. - // The quota bumps provide enough quota to bridge the gap between the two earliest sessions. - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (8 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (8 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS), - 2 * MINUTE_IN_MILLIS, 5), false); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), - 3 * MINUTE_IN_MILLIS, 1), false); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS)); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (9 * MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 1), false); - - setStandbyBucket(FREQUENT_INDEX); - synchronized (mQuotaController.mLock) { - assertEquals(2 * MINUTE_IN_MILLIS, - mQuotaController.getRemainingExecutionTimeLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(7 * MINUTE_IN_MILLIS, - mQuotaController.getTimeUntilQuotaConsumedLocked( - SOURCE_USER_ID, SOURCE_PACKAGE)); - } - } - @Test public void testIsWithinQuotaLocked_NeverApp() { synchronized (mQuotaController.mLock) { @@ -2316,272 +2181,6 @@ public class QuotaControllerTest { } } - @Test - public void testIsWithinQuotaLocked_WithQuotaBump_Duration() { - setDischarging(); - int standbyBucket = WORKING_INDEX; - setStandbyBucket(standbyBucket); - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, - 5 * MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5); - - long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession( - now - (HOUR_IN_MILLIS - 2 * MINUTE_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 1), - false); - final ExecutionStats stats; - synchronized (mQuotaController.mLock) { - stats = mQuotaController.getExecutionStatsLocked( - SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); - mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(5 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - - advanceElapsedClock(HOUR_IN_MILLIS); - - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - - // Emulate a quota bump while some jobs are executing - JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 1); - JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 2); - - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(job1, null); - mQuotaController.prepareForExecutionLocked(job1); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - mQuotaController.maybeStartTrackingJobLocked(job2, null); - mQuotaController.prepareForExecutionLocked(job2); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, null); - mQuotaController.maybeStopTrackingJobLocked(job2, null); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - - // Phase out the first session - advanceElapsedClock(5 * MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - - // Phase out the first quota bump - advanceElapsedClock(7 * HOUR_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - } - - @Test - public void testIsWithinQuotaLocked_WithQuotaBump_JobCount() { - setDischarging(); - int standbyBucket = WORKING_INDEX; - setStandbyBucket(standbyBucket); - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, - 20 * MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 1); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5); - - long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (mQcConstants.WINDOW_SIZE_WORKING_MS - HOUR_IN_MILLIS), - 5 * MINUTE_IN_MILLIS, 10), false); - final ExecutionStats stats; - synchronized (mQuotaController.mLock) { - stats = mQuotaController.getExecutionStatsLocked( - SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); - mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 10); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(10, stats.jobCountLimit); - } - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(11, stats.jobCountLimit); - } - - advanceElapsedClock(HOUR_IN_MILLIS); - - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(11, stats.jobCountLimit); - } - - // Emulate a quota bump while some jobs are executing - JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1); - JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2); - - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(job1, null); - mQuotaController.prepareForExecutionLocked(job1); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(12, stats.jobCountLimit); - mQuotaController.maybeStartTrackingJobLocked(job2, null); - mQuotaController.prepareForExecutionLocked(job2); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, null); - mQuotaController.maybeStopTrackingJobLocked(job2, null); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(12, stats.jobCountLimit); - } - - // Phase out the first session - advanceElapsedClock(3 * MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(12, stats.jobCountLimit); - } - - // Phase out the first quota bump - advanceElapsedClock(7 * HOUR_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(11, stats.jobCountLimit); - } - } - - @Test - public void testIsWithinQuotaLocked_WithQuotaBump_SessionCount() { - setDischarging(); - int standbyBucket = WORKING_INDEX; - setStandbyBucket(standbyBucket); - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, - 20 * MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 2); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 1); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5); - - long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (mQcConstants.WINDOW_SIZE_WORKING_MS - HOUR_IN_MILLIS), - 5 * MINUTE_IN_MILLIS, 1), false); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (30 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), false); - final ExecutionStats stats; - synchronized (mQuotaController.mLock) { - stats = mQuotaController.getExecutionStatsLocked( - SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(2, stats.sessionCountLimit); - } - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(3, stats.sessionCountLimit); - } - - advanceElapsedClock(HOUR_IN_MILLIS); - - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(3, stats.sessionCountLimit); - } - - // Emulate a quota bump while some jobs are executing - JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1); - JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2); - - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(job1, null); - mQuotaController.prepareForExecutionLocked(job1); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, null); - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(4, stats.sessionCountLimit); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(job2, null); - mQuotaController.prepareForExecutionLocked(job2); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job2, null); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(4, stats.sessionCountLimit); - } - - // Phase out the first session - advanceElapsedClock(2 * MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(4, stats.sessionCountLimit); - } - - // Phase out the first quota bump - advanceElapsedClock(7 * HOUR_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(3, stats.sessionCountLimit); - } - } @Test public void testIsWithinEJQuotaLocked_NeverApp() { @@ -3590,12 +3189,6 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 84 * SECOND_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, - 93 * SECOND_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 92); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 91); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 90 * MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 89); assertEquals(8 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); @@ -3653,11 +3246,6 @@ public class QuotaControllerTest { assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs()); assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs()); assertEquals(83 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs()); - assertEquals(93 * SECOND_IN_MILLIS, mQuotaController.getQuotaBumpAdditionDurationMs()); - assertEquals(92, mQuotaController.getQuotaBumpAdditionJobCount()); - assertEquals(91, mQuotaController.getQuotaBumpAdditionSessionCount()); - assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs()); - assertEquals(89, mQuotaController.getQuotaBumpLimit()); } @Test @@ -3710,11 +3298,6 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, -1); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, -1); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, -1); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 59 * MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, -1); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); @@ -3765,11 +3348,6 @@ public class QuotaControllerTest { assertEquals(0, mQuotaController.getEJRewardNotificationSeenMs()); assertEquals(0, mQuotaController.getEJGracePeriodTempAllowlistMs()); assertEquals(0, mQuotaController.getEJGracePeriodTopAppMs()); - assertEquals(0, mQuotaController.getQuotaBumpAdditionDurationMs()); - assertEquals(0, mQuotaController.getQuotaBumpAdditionJobCount()); - assertEquals(0, mQuotaController.getQuotaBumpAdditionSessionCount()); - assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs()); - assertEquals(0, mQuotaController.getQuotaBumpLimit()); // Invalid configurations. // In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD @@ -3827,7 +3405,6 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); @@ -3868,7 +3445,6 @@ public class QuotaControllerTest { assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs()); assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs()); assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs()); - assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs()); } /** Tests that TimingSessions aren't saved when the device is charging. */ -- GitLab From 02f9df30ab63b8ff83f1ad36947e6e50c1dbd8a2 Mon Sep 17 00:00:00 2001 From: Olivier St-Onge Date: Wed, 9 Oct 2024 11:30:40 -0400 Subject: [PATCH 182/441] Use Bounceable on BC25 tiles This slightly refactors the resizing code to allow edit tiles to be bounceable Flag: com.android.systemui.qs_ui_refactor_compose_fragment Bug: 331598956 Test: manually Change-Id: Iab1de097a370477a7f233549446ed726ca22d9cb --- .../qs/panels/ui/compose/BounceableInfo.kt | 60 +++ .../qs/panels/ui/compose/EditTileListState.kt | 4 +- .../panels/ui/compose/QuickQuickSettings.kt | 10 + .../ui/compose/infinitegrid/CommonTile.kt | 19 +- .../ui/compose/infinitegrid/EditTile.kt | 352 ++++++------------ .../infinitegrid/InfiniteGridLayout.kt | 11 + .../qs/panels/ui/compose/infinitegrid/Tile.kt | 184 +++++---- .../selection/MutableSelectionState.kt | 8 +- .../panels/ui/compose/selection/Selection.kt | 147 ++++++-- .../qs/panels/ui/model/TileGridCell.kt | 16 +- .../ui/viewmodel/BounceableTileViewModel.kt | 38 ++ .../panels/ui/viewmodel/EditTileViewModel.kt | 14 +- 12 files changed, 513 insertions(+), 350 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt create mode 100644 packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt new file mode 100644 index 000000000000..b9994d7bb821 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 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.panels.ui.compose + +import com.android.compose.animation.Bounceable +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.ui.model.GridCell +import com.android.systemui.qs.panels.ui.model.TileGridCell +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel + +data class BounceableInfo( + val bounceable: BounceableTileViewModel, + val previousTile: Bounceable?, + val nextTile: Bounceable?, + val bounceEnd: Boolean, +) + +fun List>.bounceableInfo( + index: Int, + columns: Int, +): BounceableInfo { + val cell = this[index].first as TileGridCell + // Only look for neighbor bounceables if they are on the same row + val onLastColumn = cell.onLastColumn(cell.column, columns) + val previousTile = getOrNull(index - 1)?.takeIf { cell.column != 0 } + val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn } + return BounceableInfo(this[index].second, previousTile?.second, nextTile?.second, !onLastColumn) +} + +fun List.bounceableInfo( + sizedTile: SizedTile, + index: Int, + column: Int, + columns: Int, +): BounceableInfo { + // Only look for neighbor bounceables if they are on the same row + val onLastColumn = sizedTile.onLastColumn(column, columns) + val previousTile = getOrNull(index - 1)?.takeIf { column != 0 } + val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn } + return BounceableInfo(this[index], previousTile, nextTile, !onLastColumn) +} + +private fun SizedTile.onLastColumn(column: Int, columns: Int): Boolean { + return column == columns - width +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt index 770fd785723a..74fa0fef21d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt @@ -116,9 +116,9 @@ class EditTileListState(tiles: List>, private val c regenerateGrid() _tiles.add(insertionIndex.coerceIn(0, _tiles.size), cell) } else { - // Add the tile with a temporary row which will get reassigned when + // Add the tile with a temporary row/col which will get reassigned when // regenerating spacers - _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0)) + _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0, 0)) } regenerateGrid() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index a645b51404e7..f36f45c7942d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -20,6 +20,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap @@ -29,6 +31,7 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.panels.ui.compose.infinitegrid.Tile +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey import com.android.systemui.res.R @@ -41,7 +44,9 @@ fun SceneScope.QuickQuickSettings( val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList()) val tiles = sizedTiles.fastMap { it.tile } + val bounceables = remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle() + val scope = rememberCoroutineScope() DisposableEffect(tiles) { val token = Any() @@ -49,6 +54,7 @@ fun SceneScope.QuickQuickSettings( onDispose { tiles.forEach { it.stopListening(token) } } } val columns by viewModel.columns.collectAsStateWithLifecycle() + var cellIndex = 0 Box(modifier = modifier) { GridAnchor() VerticalSpannedGrid( @@ -59,11 +65,15 @@ fun SceneScope.QuickQuickSettings( modifier = Modifier.sysuiResTag("qqs_tile_layout"), ) { spanIndex -> val it = sizedTiles[spanIndex] + val column = cellIndex % columns + cellIndex += it.width Tile( tile = it.tile, iconOnly = it.isIcon, modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), squishiness = { squishiness }, + coroutineScope = scope, + bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt index 9ec5a82a18d7..71fa0ac30fb7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -54,6 +55,7 @@ import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.semantics.toggleableState +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.android.compose.modifiers.background import com.android.compose.modifiers.thenIf @@ -64,7 +66,6 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState import com.android.systemui.res.R -import kotlinx.coroutines.delay private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target" @@ -138,13 +139,20 @@ fun LargeTileLabels( accessibilityUiState: AccessibilityUiState? = null, ) { Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) { - Text(label, color = colors.label, modifier = Modifier.tileMarquee()) + Text( + label, + style = MaterialTheme.typography.labelLarge, + color = colors.label, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) if (!TextUtils.isEmpty(secondaryLabel)) { Text( secondaryLabel ?: "", color = colors.secondaryLabel, + style = MaterialTheme.typography.bodyMedium, modifier = - Modifier.tileMarquee().thenIf( + Modifier.thenIf( accessibilityUiState?.stateDescription?.contains(secondaryLabel ?: "") == true ) { @@ -182,10 +190,7 @@ fun SmallTileContent( rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true) } else { var atEnd by remember(icon.res) { mutableStateOf(false) } - LaunchedEffect(key1 = icon.res) { - delay(350) - atEnd = true - } + LaunchedEffect(key1 = icon.res) { atEnd = true } rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt index 45c1e48840ee..5c2a2bd2b78c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -22,13 +22,13 @@ import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.animateIntAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -46,7 +46,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyGridItemScope import androidx.compose.foundation.lazy.grid.LazyGridScope import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.rememberScrollState @@ -66,19 +65,17 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot @@ -98,23 +95,26 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastMap -import androidx.compose.ui.zIndex +import com.android.compose.animation.bounceable import com.android.compose.modifiers.background import com.android.compose.modifiers.height import com.android.systemui.common.ui.compose.load import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.SizedTileImpl +import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.DragAndDropState import com.android.systemui.qs.panels.ui.compose.EditTileListState +import com.android.systemui.qs.panels.ui.compose.bounceableInfo import com.android.systemui.qs.panels.ui.compose.dragAndDropRemoveZone import com.android.systemui.qs.panels.ui.compose.dragAndDropTileList import com.android.systemui.qs.panels.ui.compose.dragAndDropTileSource import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileArrangementPadding +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.ToggleTargetSize import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState -import com.android.systemui.qs.panels.ui.compose.selection.ResizingHandle +import com.android.systemui.qs.panels.ui.compose.selection.ResizableTileContainer import com.android.systemui.qs.panels.ui.compose.selection.TileWidths import com.android.systemui.qs.panels.ui.compose.selection.clearSelectionTile import com.android.systemui.qs.panels.ui.compose.selection.rememberSelectionState @@ -122,11 +122,14 @@ import com.android.systemui.qs.panels.ui.compose.selection.selectableTile import com.android.systemui.qs.panels.ui.model.GridCell import com.android.systemui.qs.panels.ui.model.SpacerGridCell import com.android.systemui.qs.panels.ui.model.TileGridCell +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.shared.model.groupAndSort import com.android.systemui.res.R +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay +import kotlinx.coroutines.launch object TileType @@ -241,15 +244,20 @@ private fun CurrentTilesGrid( onSetTiles: (List) -> Unit, ) { val currentListState by rememberUpdatedState(listState) - val tileHeight = CommonTileDefaults.TileHeight val totalRows = listState.tiles.lastOrNull()?.row ?: 0 val totalHeight by animateDpAsState( - gridHeight(totalRows + 1, tileHeight, TileArrangementPadding, CurrentTilesGridPadding), + gridHeight(totalRows + 1, TileHeight, TileArrangementPadding, CurrentTilesGridPadding), label = "QSEditCurrentTilesGridHeight", ) val gridState = rememberLazyGridState() var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) } + val coroutineScope = rememberCoroutineScope() + + val cells = + remember(listState.tiles) { + listState.tiles.fastMap { Pair(it, BounceableTileViewModel()) } + } TileLazyGrid( state = gridState, @@ -272,7 +280,7 @@ private fun CurrentTilesGrid( } .testTag(CURRENT_TILES_GRID_TEST_TAG), ) { - EditTiles(listState.tiles, listState, selectionState) { spec -> + EditTiles(cells, columns, listState, selectionState, coroutineScope) { spec -> // Toggle the current size of the tile currentListState.isIcon(spec)?.let { onResize(spec, !it) } } @@ -286,10 +294,10 @@ private fun AvailableTileGrid( columns: Int, dragAndDropState: DragAndDropState, ) { - // Available tiles aren't visible during drag and drop, so the row isn't needed + // Available tiles aren't visible during drag and drop, so the row/col isn't needed val groupedTiles = remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) { - groupAndSort(tiles.fastMap { TileGridCell(it, 0) }) + groupAndSort(tiles.fastMap { TileGridCell(it, 0, 0) }) } val labelColors = EditModeTileDefaults.editTileColors() @@ -349,24 +357,26 @@ private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { /** * Adds a list of [GridCell] to the lazy grid * - * @param cells the list of [GridCell] + * @param cells the pairs of [GridCell] to [BounceableTileViewModel] * @param dragAndDropState the [DragAndDropState] for this grid * @param selectionState the [MutableSelectionState] for this grid * @param onToggleSize the callback when a tile's size is toggled */ fun LazyGridScope.EditTiles( - cells: List, + cells: List>, + columns: Int, dragAndDropState: DragAndDropState, selectionState: MutableSelectionState, + coroutineScope: CoroutineScope, onToggleSize: (spec: TileSpec) -> Unit, ) { items( count = cells.size, - key = { cells[it].key(it, dragAndDropState) }, - span = { cells[it].span }, + key = { cells[it].first.key(it, dragAndDropState) }, + span = { cells[it].first.span }, contentType = { TileType }, ) { index -> - when (val cell = cells[index]) { + when (val cell = cells[index].first) { is TileGridCell -> if (dragAndDropState.isMoving(cell.tile.tileSpec)) { // If the tile is being moved, replace it with a visible spacer @@ -385,6 +395,9 @@ fun LazyGridScope.EditTiles( dragAndDropState = dragAndDropState, selectionState = selectionState, onToggleSize = onToggleSize, + coroutineScope = coroutineScope, + bounceableInfo = cells.bounceableInfo(index, columns), + modifier = Modifier.animateItem(), ) } is SpacerGridCell -> SpacerGridCell() @@ -393,12 +406,15 @@ fun LazyGridScope.EditTiles( } @Composable -private fun LazyGridItemScope.TileGridCell( +private fun TileGridCell( cell: TileGridCell, index: Int, dragAndDropState: DragAndDropState, selectionState: MutableSelectionState, onToggleSize: (spec: TileSpec) -> Unit, + coroutineScope: CoroutineScope, + bounceableInfo: BounceableInfo, + modifier: Modifier = Modifier, ) { val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1) var selected by remember { mutableStateOf(false) } @@ -407,6 +423,9 @@ private fun LazyGridItemScope.TileGridCell( targetValue = if (selected) 1f else 0f, label = "QSEditTileSelectionAlpha", ) + val selectionColor = MaterialTheme.colorScheme.primary + val colors = EditModeTileDefaults.editTileColors() + val currentBounceableInfo by rememberUpdatedState(bounceableInfo) LaunchedEffect(selectionState.selection?.tileSpec) { selectionState.selection?.let { @@ -420,152 +439,61 @@ private fun LazyGridItemScope.TileGridCell( selected = selectionState.selection?.tileSpec == cell.tile.tileSpec } - val modifier = - Modifier.animateItem() - .semantics(mergeDescendants = true) { - this.stateDescription = stateDescription - contentDescription = cell.tile.label.text - customActions = - listOf( - // TODO(b/367748260): Add final accessibility actions - CustomAccessibilityAction("Toggle size") { - onToggleSize(cell.tile.tileSpec) - true - } - ) - } - .height(CommonTileDefaults.TileHeight) - .fillMaxWidth() - - val content = - @Composable { - EditTile( - tileViewModel = cell.tile, - iconOnly = cell.isIcon, - selectionAlpha = { selectionAlpha }, - modifier = - Modifier.fillMaxSize() - .selectableTile(cell.tile.tileSpec, selectionState) - .dragAndDropTileSource( - SizedTileImpl(cell.tile, cell.width), - dragAndDropState, - selectionState::unSelect, - ), - ) - } - - if (selected) { - SelectedTile( - isIcon = cell.isIcon, - selectionAlpha = { selectionAlpha }, - selectionState = selectionState, - modifier = modifier.zIndex(2f), // 2f to display this tile over neighbors when dragged - content = content, - ) - } else { - UnselectedTile( - selectionAlpha = { selectionAlpha }, - selectionState = selectionState, - modifier = modifier, - content = content, - ) - } -} - -@Composable -private fun SelectedTile( - isIcon: Boolean, - selectionAlpha: () -> Float, - selectionState: MutableSelectionState, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { // Current base, min and max width of this tile var tileWidths: TileWidths? by remember { mutableStateOf(null) } - - // Animated diff between the current width and the resized width of the tile. We can't use - // animateContentSize here as the tile is sometimes unbounded. - val remainingOffset by - animateIntAsState( - selectionState.resizingState?.let { tileWidths?.base?.minus(it.width) ?: 0 } ?: 0, - label = "QSEditTileWidthOffset", - ) - val padding = with(LocalDensity.current) { TileArrangementPadding.roundToPx() } - Box( - modifier.onSizeChanged { - val min = if (isIcon) it.width else (it.width - padding) / 2 - val max = if (isIcon) (it.width * 2) + padding else it.width - tileWidths = TileWidths(it.width, min, max) - } + + ResizableTileContainer( + selected = selected, + selectionState = selectionState, + selectionAlpha = { selectionAlpha }, + selectionColor = selectionColor, + tileWidths = { tileWidths }, + modifier = + modifier + .height(TileHeight) + .fillMaxWidth() + .onSizeChanged { + // Grab the size before the bounceable to get the idle width + val min = if (cell.isIcon) it.width else (it.width - padding) / 2 + val max = if (cell.isIcon) (it.width * 2) + padding else it.width + tileWidths = TileWidths(it.width, min, max) + } + .bounceable( + bounceable = currentBounceableInfo.bounceable, + previousBounceable = currentBounceableInfo.previousTile, + nextBounceable = currentBounceableInfo.nextTile, + orientation = Orientation.Horizontal, + bounceEnd = currentBounceableInfo.bounceEnd, + ), ) { - val handle = - @Composable { - ResizingHandle( - enabled = true, - selectionState = selectionState, - transition = selectionAlpha, - tileWidths = { tileWidths }, + Box( + modifier + .fillMaxSize() + .semantics(mergeDescendants = true) { + this.stateDescription = stateDescription + contentDescription = cell.tile.label.text + customActions = + listOf( + // TODO(b/367748260): Add final accessibility actions + CustomAccessibilityAction("Toggle size") { + onToggleSize(cell.tile.tileSpec) + true + } + ) + } + .selectableTile(cell.tile.tileSpec, selectionState) { + coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() } + } + .dragAndDropTileSource( + SizedTileImpl(cell.tile, cell.width), + dragAndDropState, + selectionState::unSelect, ) - } - - Layout(contents = listOf(content, handle)) { - (contentMeasurables, handleMeasurables), - constraints -> - // Grab the width from the resizing state if a resize is in progress, otherwise fill the - // max width - val width = - selectionState.resizingState?.width ?: (constraints.maxWidth - remainingOffset) - val contentPlaceable = - contentMeasurables.first().measure(constraints.copy(maxWidth = width)) - val handlePlaceable = handleMeasurables.first().measure(constraints) - - // Place the dot vertically centered on the right edge - val handleX = contentPlaceable.width - (handlePlaceable.width / 2) - val handleY = (contentPlaceable.height / 2) - (handlePlaceable.height / 2) - - layout(constraints.maxWidth, constraints.maxHeight) { - contentPlaceable.place(0, 0) - handlePlaceable.place(handleX, handleY) - } - } - } -} - -@Composable -private fun UnselectedTile( - selectionAlpha: () -> Float, - selectionState: MutableSelectionState, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { - val handle = - @Composable { - ResizingHandle( - enabled = false, - selectionState = selectionState, - transition = selectionAlpha, - ) - } - - Box(modifier) { - Layout(contents = listOf(content, handle)) { - (contentMeasurables, handleMeasurables), - constraints -> - val contentPlaceable = - contentMeasurables - .first() - .measure(constraints.copy(maxWidth = constraints.maxWidth)) - val handlePlaceable = handleMeasurables.first().measure(constraints) - - // Place the dot vertically centered on the right edge - val handleX = contentPlaceable.width - (handlePlaceable.width / 2) - val handleY = (contentPlaceable.height / 2) - (handlePlaceable.height / 2) - - layout(constraints.maxWidth, constraints.maxHeight) { - contentPlaceable.place(0, 0) - handlePlaceable.place(handleX, handleY) - } + .tileBackground(colors.background) + .tilePadding() + ) { + EditTile(tile = cell.tile, iconOnly = cell.isIcon) } } } @@ -588,19 +516,19 @@ private fun AvailableTileGridCell( verticalArrangement = spacedBy(CommonTileDefaults.TilePadding, Alignment.Top), modifier = modifier, ) { - EditTileContainer( - colors = colors, - modifier = - Modifier.fillMaxWidth() - .height(CommonTileDefaults.TileHeight) - .clearSelectionTile(selectionState) - .semantics(mergeDescendants = true) { - onClick(onClickActionName) { false } - this.stateDescription = stateDescription - } - .dragAndDropTileSource(SizedTileImpl(cell.tile, cell.width), dragAndDropState) { - selectionState.unSelect() - }, + Box( + Modifier.fillMaxWidth() + .height(TileHeight) + .clearSelectionTile(selectionState) + .semantics(mergeDescendants = true) { + onClick(onClickActionName) { false } + this.stateDescription = stateDescription + } + .dragAndDropTileSource(SizedTileImpl(cell.tile, cell.width), dragAndDropState) { + selectionState.unSelect() + } + .tileBackground(colors.background) + .tilePadding() ) { // Icon SmallTileContent( @@ -626,16 +554,14 @@ private fun AvailableTileGridCell( @Composable private fun SpacerGridCell(modifier: Modifier = Modifier) { // By default, spacers are invisible and exist purely to catch drag movements - Box(modifier.height(CommonTileDefaults.TileHeight).fillMaxWidth()) + Box(modifier.height(TileHeight).fillMaxWidth()) } @Composable -fun EditTile( - tileViewModel: EditTileViewModel, +fun BoxScope.EditTile( + tile: EditTileViewModel, iconOnly: Boolean, - modifier: Modifier = Modifier, colors: TileColors = EditModeTileDefaults.editTileColors(), - selectionAlpha: () -> Float = { 1f }, ) { // Animated horizontal alignment from center (0f) to start (-1f) val alignmentValue by @@ -646,68 +572,36 @@ fun EditTile( val alignment by remember { derivedStateOf { BiasAlignment(horizontalBias = alignmentValue, verticalBias = 0f) } } + // Icon + Box(Modifier.size(ToggleTargetSize).align(alignment)) { + SmallTileContent( + icon = tile.icon, + color = colors.icon, + animateToEnd = true, + modifier = Modifier.align(Alignment.Center), + ) + } - EditTileContainer(colors = colors, selectionAlpha = selectionAlpha, modifier = modifier) { - // Icon - Box(Modifier.size(ToggleTargetSize).align(alignment)) { - SmallTileContent( - icon = tileViewModel.icon, - color = colors.icon, - animateToEnd = true, - modifier = Modifier.align(Alignment.Center), - ) - } - - // Labels, positioned after the icon - AnimatedVisibility(visible = !iconOnly, enter = fadeIn(), exit = fadeOut()) { - LargeTileLabels( - label = tileViewModel.label.text, - secondaryLabel = tileViewModel.appName?.text, - colors = colors, - modifier = Modifier.padding(start = ToggleTargetSize + TileArrangementPadding), - ) - } + // Labels, positioned after the icon + AnimatedVisibility(visible = !iconOnly, enter = fadeIn(), exit = fadeOut()) { + LargeTileLabels( + label = tile.label.text, + secondaryLabel = tile.appName?.text, + colors = colors, + modifier = Modifier.padding(start = ToggleTargetSize + TileArrangementPadding), + ) } } -@Composable -private fun EditTileContainer( - colors: TileColors, - modifier: Modifier = Modifier, - selectionAlpha: () -> Float = { 0f }, - selectionColor: Color = MaterialTheme.colorScheme.primary, - content: @Composable BoxScope.() -> Unit = {}, -) { - Box( - Modifier.wrapContentSize().drawWithContent { - drawContent() - drawRoundRect( - SolidColor(selectionColor), - cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), - style = Stroke(EditModeTileDefaults.SelectedBorderWidth.toPx()), - alpha = selectionAlpha(), - ) - } - ) { - Box( - modifier = - modifier - .drawBehind { - drawRoundRect( - SolidColor(colors.background), - cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), - ) - } - .tilePadding(), - content = content, - ) +private fun Modifier.tileBackground(color: Color): Modifier { + return drawBehind { + drawRoundRect(SolidColor(color), cornerRadius = CornerRadius(InactiveCornerRadius.toPx())) } } private object EditModeTileDefaults { const val PLACEHOLDER_ALPHA = .3f val EditGridHeaderHeight = 60.dp - val SelectedBorderWidth = 2.dp val CurrentTilesGridPadding = 8.dp @Composable diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 6920e498bdde..e5c213519415 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap @@ -29,7 +30,9 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout +import com.android.systemui.qs.panels.ui.compose.bounceableInfo import com.android.systemui.qs.panels.ui.compose.rememberEditListState +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsViewModel @@ -62,7 +65,11 @@ constructor( } val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) } + val bounceables = + remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } val squishiness by squishinessViewModel.squishiness.collectAsStateWithLifecycle() + val scope = rememberCoroutineScope() + var cellIndex = 0 VerticalSpannedGrid( columns = columns, @@ -71,11 +78,15 @@ constructor( spans = sizedTiles.fastMap { it.width }, ) { spanIndex -> val it = sizedTiles[spanIndex] + val column = cellIndex % columns + cellIndex += it.width Tile( tile = it.tile, iconOnly = iconTilesViewModel.isIconTile(it.tile.spec), modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), squishiness = { squishiness }, + coroutineScope = scope, + bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index 4bd5b2d68c4c..52d526123430 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -23,8 +23,8 @@ import android.service.quicksettings.Tile.STATE_ACTIVE import android.service.quicksettings.Tile.STATE_INACTIVE import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box @@ -44,6 +44,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -61,11 +62,13 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable +import com.android.compose.animation.bounceable import com.android.compose.modifiers.thenIf import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.viewmodel.TileUiState @@ -74,6 +77,8 @@ import com.android.systemui.qs.panels.ui.viewmodel.toUiState import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import java.util.function.Supplier +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch private const val TEST_TAG_SMALL = "qs_tile_small" private const val TEST_TAG_LARGE = "qs_tile_large" @@ -102,9 +107,12 @@ fun Tile( tile: TileViewModel, iconOnly: Boolean, squishiness: () -> Float, + coroutineScope: CoroutineScope, + bounceableInfo: BounceableInfo, modifier: Modifier = Modifier, ) { val state by tile.state.collectAsStateWithLifecycle(tile.currentState) + val currentBounceableInfo by rememberUpdatedState(bounceableInfo) val resources = resources() val uiState = remember(state, resources) { state.toUiState(resources) } val colors = TileDefaults.getColorForState(uiState) @@ -112,7 +120,7 @@ fun Tile( // TODO(b/361789146): Draw the shapes instead of clipping val tileShape = TileDefaults.animateTileShape(uiState.state) - TileContainer( + TileExpandable( color = if (iconOnly || !uiState.handlesSecondaryClick) { colors.iconBackground @@ -120,92 +128,100 @@ fun Tile( colors.background }, shape = tileShape, - iconOnly = iconOnly, - onClick = tile::onClick, - onLongClick = tile::onLongClick, - uiState = uiState, squishiness = squishiness, - modifier = modifier, + modifier = + modifier + .fillMaxWidth() + .bounceable( + bounceable = currentBounceableInfo.bounceable, + previousBounceable = currentBounceableInfo.previousTile, + nextBounceable = currentBounceableInfo.nextTile, + orientation = Orientation.Horizontal, + bounceEnd = currentBounceableInfo.bounceEnd, + ), ) { expandable -> - val icon = getTileIcon(icon = uiState.icon) - if (iconOnly) { - SmallTileContent( - icon = icon, - color = colors.icon, - modifier = Modifier.align(Alignment.Center), - ) - } else { - val iconShape = TileDefaults.animateIconShape(uiState.state) - LargeTileContent( - label = uiState.label, - secondaryLabel = uiState.secondaryLabel, - icon = icon, - colors = colors, - iconShape = iconShape, - toggleClickSupported = state.handlesSecondaryClick, - onClick = { - if (state.handlesSecondaryClick) { - tile.onSecondaryClick() - } - }, - onLongClick = { tile.onLongClick(expandable) }, - accessibilityUiState = uiState.accessibilityUiState, - squishiness = squishiness, - ) + TileContainer( + onClick = { + tile.onClick(expandable) + if (uiState.accessibilityUiState.toggleableState != null) { + coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() } + } + }, + onLongClick = { tile.onLongClick(expandable) }, + uiState = uiState, + iconOnly = iconOnly, + ) { + val icon = getTileIcon(icon = uiState.icon) + if (iconOnly) { + SmallTileContent( + icon = icon, + color = colors.icon, + modifier = Modifier.align(Alignment.Center), + ) + } else { + val iconShape = TileDefaults.animateIconShape(uiState.state) + LargeTileContent( + label = uiState.label, + secondaryLabel = uiState.secondaryLabel, + icon = icon, + colors = colors, + iconShape = iconShape, + toggleClickSupported = state.handlesSecondaryClick, + onClick = { + if (state.handlesSecondaryClick) { + tile.onSecondaryClick() + } + }, + onLongClick = { tile.onLongClick(expandable) }, + accessibilityUiState = uiState.accessibilityUiState, + squishiness = squishiness, + ) + } } } } @Composable -private fun TileContainer( +private fun TileExpandable( color: Color, shape: Shape, - iconOnly: Boolean, - uiState: TileUiState, squishiness: () -> Float, modifier: Modifier = Modifier, - onClick: (Expandable) -> Unit = {}, - onLongClick: (Expandable) -> Unit = {}, - content: @Composable BoxScope.(Expandable) -> Unit, + content: @Composable (Expandable) -> Unit, ) { Expandable( color = color, shape = shape, modifier = modifier.clip(shape).verticalSquish(squishiness), ) { - val longPressLabel = longPressLabel() - Box( - modifier = - Modifier.height(CommonTileDefaults.TileHeight) - .fillMaxWidth() - .combinedClickable( - onClick = { onClick(it) }, - onLongClick = { onLongClick(it) }, - onClickLabel = uiState.accessibilityUiState.clickLabel, - onLongClickLabel = longPressLabel, - ) - .semantics { - role = uiState.accessibilityUiState.accessibilityRole - if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) { - uiState.accessibilityUiState.toggleableState?.let { - toggleableState = it - } - } - stateDescription = uiState.accessibilityUiState.stateDescription - } - .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE) - .thenIf(iconOnly) { - Modifier.semantics { - contentDescription = uiState.accessibilityUiState.contentDescription - } - } - .tilePadding() - ) { - content(it) - } + content(it) } } +@Composable +fun TileContainer( + onClick: () -> Unit, + onLongClick: () -> Unit, + uiState: TileUiState, + iconOnly: Boolean, + content: @Composable BoxScope.() -> Unit, +) { + Box( + modifier = + Modifier.height(CommonTileDefaults.TileHeight) + .fillMaxWidth() + .tileCombinedClickable( + onClick = onClick, + onLongClick = onLongClick, + uiState = uiState, + iconOnly = iconOnly, + ) + .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE) + .tilePadding(), + content = content, + ) +} + @Composable private fun getTileIcon(icon: Supplier): Icon { val context = LocalContext.current @@ -222,14 +238,38 @@ fun tileHorizontalArrangement(): Arrangement.Horizontal { return spacedBy(space = CommonTileDefaults.TileArrangementPadding, alignment = Alignment.Start) } -fun Modifier.tileMarquee(): Modifier { - return basicMarquee(iterations = 1, initialDelayMillis = 200) -} - fun Modifier.tilePadding(): Modifier { return padding(CommonTileDefaults.TilePadding) } +@Composable +fun Modifier.tileCombinedClickable( + onClick: () -> Unit, + onLongClick: () -> Unit, + uiState: TileUiState, + iconOnly: Boolean, +): Modifier { + val longPressLabel = longPressLabel() + return combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + onClickLabel = uiState.accessibilityUiState.clickLabel, + onLongClickLabel = longPressLabel, + ) + .semantics { + role = uiState.accessibilityUiState.accessibilityRole + if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) { + uiState.accessibilityUiState.toggleableState?.let { toggleableState = it } + } + stateDescription = uiState.accessibilityUiState.stateDescription + } + .thenIf(iconOnly) { + Modifier.semantics { + contentDescription = uiState.accessibilityUiState.contentDescription + } + } +} + data class TileColors( val background: Color, val iconBackground: Color, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt index 441d96289d86..1d36aee4eb85 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt @@ -89,8 +89,11 @@ class MutableSelectionState( * Listens for click events to select/unselect the given [TileSpec]. Use this on current tiles as * they can be selected. */ -@Composable -fun Modifier.selectableTile(tileSpec: TileSpec, selectionState: MutableSelectionState): Modifier { +fun Modifier.selectableTile( + tileSpec: TileSpec, + selectionState: MutableSelectionState, + onClick: () -> Unit = {}, +): Modifier { return pointerInput(Unit) { detectTapGestures( onTap = { @@ -99,6 +102,7 @@ fun Modifier.selectableTile(tileSpec: TileSpec, selectionState: MutableSelection } else { selectionState.select(tileSpec, manual = true) } + onClick() } ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt index e0f0b6aa8919..9f13a3788f53 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt @@ -16,63 +16,117 @@ package com.android.systemui.qs.panels.ui.compose.selection +import androidx.compose.animation.core.animateIntAsState import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.size import androidx.compose.foundation.systemGestureExclusion import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.toSize +import androidx.compose.ui.zIndex +import com.android.compose.modifiers.thenIf +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.ResizingDotSize +import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.SelectedBorderWidth /** - * Dot handling resizing drag events. Use this on the selected tile to resize it + * Places a dot to handle resizing drag events. Use this on tiles to resize. * - * @param enabled whether resizing drag events should be handled + * The dot is placed vertically centered on the right border. The [content] will have a border when + * selected. + * + * @param selected whether resizing drag events should be handled * @param selectionState the [MutableSelectionState] on the grid - * @param transition the animated value for the dot, used for its alpha and scale + * @param selectionAlpha the animated value for the dot and border alpha + * @param selectionColor the [Color] of the dot and border * @param tileWidths the [TileWidths] of the selected tile - * @param onResize the callback when the drag passes the resizing threshold */ @Composable -fun ResizingHandle( +fun ResizableTileContainer( + selected: Boolean, + selectionState: MutableSelectionState, + selectionAlpha: () -> Float, + selectionColor: Color, + tileWidths: () -> TileWidths?, + modifier: Modifier = Modifier, + content: @Composable BoxScope.() -> Unit = {}, +) { + Box( + modifier + .resizable(selected, selectionState, tileWidths) + .selectionBorder(selectionColor, selectionAlpha) + ) { + content() + ResizingHandle( + enabled = selected, + selectionState = selectionState, + transition = selectionAlpha, + tileWidths = tileWidths, + modifier = + // Higher zIndex to make sure the handle is drawn above the content + Modifier.zIndex(2f), + ) + } +} + +@Composable +private fun ResizingHandle( enabled: Boolean, selectionState: MutableSelectionState, transition: () -> Float, - tileWidths: () -> TileWidths? = { null }, + tileWidths: () -> TileWidths?, + modifier: Modifier = Modifier, ) { - if (enabled) { - // Manually creating the touch target around the resizing dot to ensure that the next tile - // does - // not receive the touch input accidentally. - val minTouchTargetSize = LocalMinimumInteractiveComponentSize.current - Box( - Modifier.size(minTouchTargetSize) - .systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) } - .pointerInput(Unit) { - detectHorizontalDragGestures( - onHorizontalDrag = { _, offset -> selectionState.onResizingDrag(offset) }, - onDragStart = { - tileWidths()?.let { selectionState.onResizingDragStart(it) } - }, - onDragEnd = selectionState::onResizingDragEnd, - onDragCancel = selectionState::onResizingDragEnd, + // Manually creating the touch target around the resizing dot to ensure that the next tile + // does not receive the touch input accidentally. + val minTouchTargetSize = LocalMinimumInteractiveComponentSize.current + Box( + modifier + .layout { measurable, constraints -> + val size = minTouchTargetSize.roundToPx() + val placeable = measurable.measure(Constraints(size, size, size, size)) + layout(placeable.width, placeable.height) { + placeable.place( + x = constraints.maxWidth - placeable.width / 2, + y = constraints.maxHeight / 2 - placeable.height / 2, ) } - ) { - ResizingDot(transition = transition, modifier = Modifier.align(Alignment.Center)) - } - } else { - ResizingDot(transition = transition) + } + .thenIf(enabled) { + Modifier.systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) } + .pointerInput(Unit) { + detectHorizontalDragGestures( + onHorizontalDrag = { _, offset -> + selectionState.onResizingDrag(offset) + }, + onDragStart = { + tileWidths()?.let { selectionState.onResizingDragStart(it) } + }, + onDragEnd = selectionState::onResizingDragEnd, + onDragCancel = selectionState::onResizingDragEnd, + ) + } + } + ) { + ResizingDot(transition = transition, modifier = Modifier.align(Alignment.Center)) } } @@ -88,6 +142,45 @@ private fun ResizingDot( } } +private fun Modifier.selectionBorder( + selectionColor: Color, + selectionAlpha: () -> Float = { 0f }, +): Modifier { + return drawWithContent { + drawContent() + drawRoundRect( + SolidColor(selectionColor), + cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), + style = Stroke(SelectedBorderWidth.toPx()), + alpha = selectionAlpha(), + ) + } +} + +@Composable +private fun Modifier.resizable( + selected: Boolean, + selectionState: MutableSelectionState, + tileWidths: () -> TileWidths?, +): Modifier { + if (!selected) return zIndex(1f) + + // Animated diff between the current width and the resized width of the tile. We can't use + // animateContentSize here as the tile is sometimes unbounded. + val remainingOffset by + animateIntAsState( + selectionState.resizingState?.let { tileWidths()?.base?.minus(it.width) ?: 0 } ?: 0, + label = "QSEditTileWidthOffset", + ) + return zIndex(2f).layout { measurable, constraints -> + // Grab the width from the resizing state if a resize is in progress + val width = selectionState.resizingState?.width ?: (constraints.maxWidth - remainingOffset) + val placeable = measurable.measure(constraints.copy(minWidth = width, maxWidth = width)) + layout(constraints.maxWidth, placeable.height) { placeable.place(0, 0) } + } +} + private object SelectionDefaults { val ResizingDotSize = 16.dp + val SelectedBorderWidth = 2.dp } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt index b1841c4c5ffa..c0441f8a38a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt @@ -31,8 +31,8 @@ sealed interface GridCell { } /** - * Represents a [EditTileViewModel] from a grid associated with a tile format and the row it's - * positioned at + * Represents a [EditTileViewModel] from a grid associated with a tile format and the row and column + * it's positioned at */ @Immutable data class TileGridCell( @@ -41,13 +41,15 @@ data class TileGridCell( override val width: Int, override val span: GridItemSpan = GridItemSpan(width), override val s: String = "${tile.tileSpec.spec}-$row-$width", + val column: Int, ) : GridCell, SizedTile, CategoryAndName by tile { val key: String = "${tile.tileSpec.spec}-$row" constructor( sizedTile: SizedTile, row: Int, - ) : this(tile = sizedTile.tile, row = row, width = sizedTile.width) + column: Int, + ) : this(tile = sizedTile.tile, row = row, column = column, width = sizedTile.width) } /** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */ @@ -73,7 +75,13 @@ fun List>.toGridCells( return splitInRowsSequence(this, columns) .flatMapIndexed { rowIndex, sizedTiles -> val correctedRowIndex = rowIndex + startingRow - val row: List = sizedTiles.map { TileGridCell(it, correctedRowIndex) } + var column = 0 + val row: List = + sizedTiles.map { + TileGridCell(it, correctedRowIndex, column).also { cell -> + column += cell.width + } + } // Fill the incomplete rows with spacers val numSpacers = columns - sizedTiles.sumOf { it.width } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt new file mode 100644 index 000000000000..506b05256880 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 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.panels.ui.viewmodel + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.VectorConverter +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.android.compose.animation.Bounceable + +class BounceableTileViewModel : Bounceable { + private val animatableBounce = Animatable(0.dp, Dp.VectorConverter) + override val bounce: Dp + get() = animatableBounce.value + + suspend fun animateBounce() { + animatableBounce.animateTo(BounceSize) + animatableBounce.animateTo(0.dp) + } + + private companion object { + val BounceSize = 8.dp + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt index ee12736f6db4..be6ce5c5b4f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt @@ -43,13 +43,13 @@ data class UnloadedEditTileViewModel( ) { fun load(context: Context): EditTileViewModel { return EditTileViewModel( - tileSpec, - icon, - label.toAnnotatedString(context) ?: AnnotatedString(tileSpec.spec), - appName?.toAnnotatedString(context), - isCurrent, - availableEditActions, - category, + tileSpec = tileSpec, + icon = icon, + label = label.toAnnotatedString(context) ?: AnnotatedString(tileSpec.spec), + appName = appName?.toAnnotatedString(context), + isCurrent = isCurrent, + availableEditActions = availableEditActions, + category = category, ) } } -- GitLab From 21b5c2f81085826dba071f98bbe0068cd9839289 Mon Sep 17 00:00:00 2001 From: Marvin Ramin Date: Mon, 14 Oct 2024 14:48:52 +0200 Subject: [PATCH 183/441] Don't stop MediaProjection sessions without display When device lock would otherwise stop MediaProjection. Bug: 370108833 Test: atest MediaProjectionManagerServiceTest Flag: android.companion.virtualdevice.flags.media_projection_keyguard_restrictions Change-Id: Ic5179113180d3bb71d813aeed02ae2a302190965 --- .../MediaProjectionManagerService.java | 3 ++- .../MediaProjectionManagerServiceTest.java | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) 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 7511799afeef..e0913ccbc7f7 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -237,7 +237,8 @@ public final class MediaProjectionManagerService extends SystemService void onKeyguardLockedStateChanged(boolean isKeyguardLocked) { if (!isKeyguardLocked) return; synchronized (mLock) { - if (mProjectionGrant != null && !canCaptureKeyguard()) { + if (mProjectionGrant != null && !canCaptureKeyguard() + && mProjectionGrant.mVirtualDisplayId != INVALID_DISPLAY) { Slog.d(TAG, "Content Recording: Stopped MediaProjection" + " due to keyguard lock"); mProjectionGrant.stop(); diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index 59af5c0764d9..b1d658cb1e86 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -401,6 +401,23 @@ public class MediaProjectionManagerServiceTest { } } + @EnableFlags(android.companion.virtualdevice.flags + .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) + @Test + public void testCreateProjection_keyguardLocked_noDisplayCreated() + throws NameNotFoundException { + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + doReturn(true).when(mKeyguardManager).isKeyguardLocked(); + + doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission( + RECORD_SENSITIVE_CONTENT, projection.packageName); + + projection.start(mIMediaProjectionCallback); + + // The projection was started because it was allowed to capture the keyguard. + assertThat(mService.getActiveProjectionInfo()).isNotNull(); + } + @Test public void testCreateProjection_attemptReuse_noPriorProjectionGrant() throws NameNotFoundException { @@ -514,6 +531,7 @@ public class MediaProjectionManagerServiceTest { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(service); projection.start(mIMediaProjectionCallback); + projection.notifyVirtualDisplayCreated(10); assertThat(service.getActiveProjectionInfo()).isNotNull(); @@ -536,6 +554,7 @@ public class MediaProjectionManagerServiceTest { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(service); projection.start(mIMediaProjectionCallback); + projection.notifyVirtualDisplayCreated(10); assertThat(service.getActiveProjectionInfo()).isNotNull(); -- GitLab From 84ec6502fa84e4a0e0a56a4e4c2eb021ded97703 Mon Sep 17 00:00:00 2001 From: Alexander Roederer Date: Mon, 14 Oct 2024 23:44:31 +0000 Subject: [PATCH 184/441] Add music (piano) icon for zen mode picker Bug: 372366178 Test: manual Flag: EXEMPT Unflaggable, adding resources Change-Id: Id11cf4fec832bd12108297abe4fb8df138fa9004 --- .../res/drawable/ic_zen_mode_icon_piano.xml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 core/res/res/drawable/ic_zen_mode_icon_piano.xml diff --git a/core/res/res/drawable/ic_zen_mode_icon_piano.xml b/core/res/res/drawable/ic_zen_mode_icon_piano.xml new file mode 100644 index 000000000000..012b9398d687 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_piano.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file -- GitLab From c4bd9923ccd4e88cbf599542d1ebb543fd6e5afe Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Tue, 15 Oct 2024 13:59:37 +0000 Subject: [PATCH 185/441] Add ownership of scrim/lockscreen files to keyguard Bug: NONE documentation only Flag: NONE documentation only Test: NONE (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:9bb9c633c06f3ea693aa2394ecf89ffe53e1039d) Merged-In: I371272e98058e570a578eafc4b0fefd10457366b Change-Id: I371272e98058e570a578eafc4b0fefd10457366b NOTE FOR REVIEWERS - errors occurred while applying the patch. PLEASE REVIEW CAREFULLY. Errors: Error applying patch in packages/SystemUI/src/com/android/systemui/statusbar/OWNERS, hunk HunkHeader[14,6->14,8]: Hunk cannot be applied Original patch: From 9bb9c633c06f3ea693aa2394ecf89ffe53e1039d Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Tue, 15 Oct 2024 13:51:21 +0000 Subject: [PATCH] Add ownership of scrim/lockscreen files to keyguard Bug: NONE documentation only Flag: NONE documentation only Test: NONE Change-Id: I371272e98058e570a578eafc4b0fefd10457366b --- --- packages/SystemUI/src/com/android/systemui/statusbar/OWNERS | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS index c4f539a4acdf..9de229e2ddb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS @@ -13,4 +13,8 @@ per-file *Doze* = file:../keyguard/OWNERS per-file *Keyboard* = set noparent per-file *Keyboard* = file:../keyguard/OWNERS per-file *Keyguard* = set noparent -per-file *Keyguard* = file:../keyguard/OWNERS \ No newline at end of file +per-file *Keyguard* = file:../keyguard/OWNERS +per-file *Lockscreen* = set noparent +per-file *Lockscreen* = file:../keyguard/OWNERS +per-file *Scrim* = set noparent +per-file *Scrim* = file:../keyguard/OWNERS \ No newline at end of file -- GitLab From 5eb1f7827cf03e4f8b399854719af6fa52efd5ee Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Tue, 15 Oct 2024 13:20:43 +0000 Subject: [PATCH 186/441] Remove concept of backdrop This was used by the legacy ambient wallpaper system and is not in use. Test: atest ScrimControllerTest Bug: 373600993 Flag: EXEMPT code cleanup Change-Id: I51e6e428b804a3f2d8b9657c49fb0a991a11835a --- .../statusbar/phone/ScrimController.java | 20 --------- .../systemui/statusbar/phone/ScrimState.java | 7 +-- .../statusbar/phone/ScrimControllerTest.java | 44 ------------------- 3 files changed, 1 insertion(+), 70 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 069c6241491c..40e52935daf1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -1715,26 +1715,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump updateScrims(); } - public void setHasBackdrop(boolean hasBackdrop) { - for (ScrimState state : ScrimState.values()) { - state.setHasBackdrop(hasBackdrop); - } - - // Backdrop event may arrive after state was already applied, - // in this case, back-scrim needs to be re-evaluated - if (mState == ScrimState.AOD || mState == ScrimState.PULSING) { - float newBehindAlpha = mState.getBehindAlpha(); - if (isNaN(newBehindAlpha)) { - throw new IllegalStateException("Scrim opacity is NaN for state: " + mState - + ", back: " + mBehindAlpha); - } - if (mBehindAlpha != newBehindAlpha) { - mBehindAlpha = newBehindAlpha; - updateScrims(); - } - } - } - private void setKeyguardFadingAway(boolean fadingAway, long duration) { for (ScrimState state : ScrimState.values()) { state.setKeyguardFadingAway(fadingAway, duration); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index fbba3dc107f1..a0ab6120f372 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -210,7 +210,7 @@ public enum ScrimState { @Override public float getMaxLightRevealScrimAlpha() { - return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f; + return mWallpaperSupportsAmbientMode ? 0f : 1f; } @Override @@ -369,7 +369,6 @@ public enum ScrimState { DockManager mDockManager; boolean mDisplayRequiresBlanking; boolean mWallpaperSupportsAmbientMode; - boolean mHasBackdrop; boolean mLaunchingAffordanceWithPreview; boolean mOccludeAnimationPlaying; boolean mWakeLockScreenSensorActive; @@ -489,10 +488,6 @@ public enum ScrimState { return false; } - public void setHasBackdrop(boolean hasBackdrop) { - mHasBackdrop = hasBackdrop; - } - public void setWakeLockScreenSensorActive(boolean active) { mWakeLockScreenSensorActive = active; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index e804b33db1f7..eecf36e9343b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -309,8 +309,6 @@ public class ScrimControllerTest extends SysuiTestCase { // Attach behind scrim so flows that are collecting on it start running. ViewUtils.attachView(mScrimBehind); - mScrimController.setHasBackdrop(false); - mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false); mTestScope.getTestScheduler().runCurrent(); @@ -482,47 +480,6 @@ public class ScrimControllerTest extends SysuiTestCase { assertEquals(0f, mScrimController.getState().getMaxLightRevealScrimAlpha(), 0f); } - @Test - public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() { - mScrimController.setHasBackdrop(true); - mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true); - mTestScope.getTestScheduler().runCurrent(); - - mScrimController.legacyTransitionTo(ScrimState.AOD); - finishAnimationsImmediately(); - - assertScrimAlpha(Map.of( - mScrimInFront, TRANSPARENT, - mScrimBehind, TRANSPARENT)); - assertEquals(1f, mScrimController.getState().getMaxLightRevealScrimAlpha(), 0f); - - assertScrimTinted(Map.of( - mScrimInFront, true, - mScrimBehind, true - )); - } - - @Test - public void setHasBackdrop_withAodWallpaperAndAlbumArt() { - mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true); - mTestScope.getTestScheduler().runCurrent(); - - mScrimController.legacyTransitionTo(ScrimState.AOD); - finishAnimationsImmediately(); - mScrimController.setHasBackdrop(true); - finishAnimationsImmediately(); - - assertScrimAlpha(Map.of( - mScrimInFront, TRANSPARENT, - mScrimBehind, TRANSPARENT)); - assertEquals(1f, mScrimController.getState().getMaxLightRevealScrimAlpha(), 0f); - - assertScrimTinted(Map.of( - mScrimInFront, true, - mScrimBehind, true - )); - } - @Test public void transitionToAod_withFrontAlphaUpdates() { // Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state. @@ -1356,7 +1313,6 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); - mScrimController.setHasBackdrop(false); mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false); mTestScope.getTestScheduler().runCurrent(); mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); -- GitLab From 04efdc82adfb6cc564149334e80006dab2d5337a Mon Sep 17 00:00:00 2001 From: Matt Casey Date: Tue, 15 Oct 2024 16:21:16 +0000 Subject: [PATCH 187/441] Manually apply "hide capture more" change to main https://android-review.git.corp.google.com/c/platform/frameworks/base/+/3250819 Code moved in main, applying it manually. Bug: 356648830 Bug: 373587617 Test: NONE merging simple change to main Flag: EXEMPT minor bugfix Change-Id: I5da5f61ce8e9252410054cbfa8de1ff013ebb5ec --- .../systemui/screenshot/LegacyScreenshotController.java | 4 +++- .../com/android/systemui/screenshot/ScreenshotController.kt | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java index acfcd13738f0..2259b55dc268 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java @@ -313,7 +313,9 @@ public class LegacyScreenshotController implements InteractiveScreenshotHandler setWindowFocusable(true); mViewProxy.requestFocus(); - enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle()); + if (screenshot.getType() != WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { + enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle()); + } attachWindow(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt index f5c605211520..08214c456897 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt @@ -35,6 +35,7 @@ import android.util.Log import android.view.Display import android.view.ScrollCaptureResponse import android.view.ViewRootImpl.ActivityConfigCallback +import android.view.WindowManager import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE import android.widget.Toast import android.window.WindowContext @@ -217,7 +218,9 @@ internal constructor( window.setFocusable(true) viewProxy.requestFocus() - enqueueScrollCaptureRequest(requestId, screenshot.userHandle) + if (screenshot.type != WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { + enqueueScrollCaptureRequest(requestId, screenshot.userHandle) + } window.attachWindow() -- GitLab From 85692fd6a7a8bdac5edb4a46ba1fcb433b027060 Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Fri, 4 Oct 2024 17:27:25 -0700 Subject: [PATCH 188/441] Document handling unsupported EAP-SIM/AKA Bug: 369220073 Test: atest android.carrierapi.cts.CarrierApiTest Flag: DOCS_ONLY Change-Id: Id00f3f26881a7c7201f26915cd1f583a170a2e80 --- .../java/android/telephony/TelephonyManager.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 818f3413671d..a7fe0cb0940c 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -8778,13 +8778,14 @@ public class TelephonyManager { * Authentication error, no memory space available in EFMUK * * @throws UnsupportedOperationException If the device does not have - * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}. + * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} or doesn't support given + * authType. */ // TODO(b/73660190): This should probably require MODIFY_PHONE_STATE, not // READ_PRIVILEGED_PHONE_STATE. It certainly shouldn't reference the permission in Javadoc since // it's not public API. @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) - public String getIccAuthentication(int appType,@AuthType int authType, String data) { + public String getIccAuthentication(int appType, @AuthType int authType, String data) { return getIccAuthentication(getSubId(), appType, authType, data); } @@ -8809,10 +8810,14 @@ public class TelephonyManager { * Key freshness failure * Authentication error, no memory space available * Authentication error, no memory space available in EFMUK + * @throws UnsupportedOperationException If the device does not have + * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} or doesn't support given + * authType. * @hide */ @UnsupportedAppUsage - public String getIccAuthentication(int subId, int appType,@AuthType int authType, String data) { + public String getIccAuthentication(int subId, int appType, @AuthType int authType, + String data) { try { IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) -- GitLab From 57a34e17e1aedbd5b045d3925cef421f3ced53af Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Tue, 15 Oct 2024 10:04:12 -0700 Subject: [PATCH 189/441] Use screen timeout settings of current user. This changelist updates fetching screen timeout settings by asking for the current user's setting. Fixes: 369489115 Test: atest CommunalSceneStartableTest Flag: EXEMPT bugfix Change-Id: I901386dc01d2c84a61d748929ae1370a69dc2727 --- .../systemui/communal/CommunalSceneStartableTest.kt | 7 ++++++- .../android/systemui/communal/CommunalSceneStartable.kt | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index ee65fbd810ae..1e8651683ec1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal +import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.provider.Settings @@ -77,7 +78,11 @@ class CommunalSceneStartableTest : SysuiTestCase() { @Before fun setUp() { with(kosmos) { - fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT) + fakeSettings.putIntForUser( + Settings.System.SCREEN_OFF_TIMEOUT, + SCREEN_TIMEOUT, + UserHandle.USER_CURRENT, + ) kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) underTest = diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 08a7c395e57f..8510915d55af 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal +import android.os.UserHandle import android.provider.Settings import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey @@ -147,9 +148,10 @@ constructor( .emitOnStart() .onEach { screenTimeout = - systemSettings.getInt( + systemSettings.getIntForUser( Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_TIMEOUT, + UserHandle.USER_CURRENT, ) } .launchIn(bgScope) -- GitLab From b0bd2dba7dc9dfa86b94569d095ff6ebca8bc067 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 15 Oct 2024 17:14:54 +0000 Subject: [PATCH 190/441] Mark UinputRecordingIntegrationTests.testEvemuRecording as flaky Re-enable the test and mark it as flaky to get it to run in postsubmit to get more information on the test failures. Bug: 370828465 Change-Id: Iab153c72d84df8438c9a6ea87ca23ea5b9bdc083 Flag: TEST_ONLY Test: Presbumit --- .../com/android/test/input/UinputRecordingIntegrationTests.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt index 9f4df90422eb..1a29c0f24edf 100644 --- a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt +++ b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt @@ -21,6 +21,7 @@ import android.cts.input.EventVerifier import android.graphics.PointF import android.hardware.input.InputManager import android.os.ParcelFileDescriptor +import android.platform.test.annotations.FlakyTest import android.util.Log import android.util.Size import android.view.InputEvent @@ -39,7 +40,6 @@ import com.android.cts.input.inputeventmatchers.withSource import junit.framework.Assert.fail import org.hamcrest.Matchers.allOf import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TestName @@ -108,7 +108,7 @@ class UinputRecordingIntegrationTests { parser = InputJsonParser(instrumentation.context) } - @Ignore("b/366602644") + @FlakyTest(bugId = 366602644) @Test fun testEvemuRecording() { VirtualDisplayActivityScenario.AutoClose( -- GitLab From 436a2714e5f846770d91af6aaa9e37fa217dfed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Kozynski?= Date: Fri, 11 Oct 2024 14:58:51 -0400 Subject: [PATCH 191/441] Add scrolling to QS This will allow QS to scroll when it's taller than its container. A few notes: * The ShadeHeader scrolls with the same Y, that's following current behavior. * Fixes an empty list on composition of QQS. This prevents a frame where the height is just the padding and therefore QuickSettingsControllerImpl has the wrong height for fling. * Forces height of the footer, so it won't be squished when it almost fits. Instead, the container will become scrollable. Test: manual, using larger display size Bug: 353254131 Flag: com.android.systemui.qs_ui_refactor_compose_fragment Change-Id: I9a13aefebd2fd6be93e1d5eafbf44f8a1c6487b4 --- .../composable/QuickSettingsShadeOverlay.kt | 9 ++- .../qs/composefragment/QSFragmentCompose.kt | 78 ++++++++++++++----- .../panels/ui/compose/PaginatedGridLayout.kt | 6 +- .../panels/ui/compose/QuickQuickSettings.kt | 3 +- 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index 54497f621c73..2a91bd8b1d73 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -133,7 +133,14 @@ fun SceneScope.QuickSettingsLayout( Column( verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding), horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding), + modifier = + modifier + .fillMaxWidth() + .padding( + start = QuickSettingsShade.Dimensions.Padding, + end = QuickSettingsShade.Dimensions.Padding, + top = QuickSettingsShade.Dimensions.Padding, + ), ) { BrightnessSliderContainer( viewModel = viewModel.brightnessSliderViewModel, diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index 9c5231d716da..49b44cb95e46 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -23,7 +23,9 @@ import android.graphics.Rect import android.os.Bundle import android.util.IndentingPrintWriter import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View +import android.view.ViewConfiguration import android.view.ViewGroup import android.widget.FrameLayout import androidx.activity.OnBackPressedDispatcher @@ -35,6 +37,7 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -43,6 +46,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -83,6 +87,7 @@ import com.android.systemui.Dumpable import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dump.DumpManager import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.lifecycle.setSnapshotBinding import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.dagger.MediaModule.QS_PANEL @@ -145,6 +150,7 @@ constructor( private val qqsVisible = MutableStateFlow(false) private val qqsPositionOnRoot = Rect() private val composeViewPositionOnScreen = Rect() + private val scrollState = ScrollState(0) // Inside object for namespacing private val notificationScrimClippingParams = @@ -210,6 +216,9 @@ constructor( context, { notificationScrimClippingParams.isEnabled }, { notificationScrimClippingParams.params.top }, + // Only allow scrolling when we are fully expanded. That way, we don't intercept + // swipes in lockscreen (when somehow QS is receiving touches). + { scrollState.canScrollForward && viewModel.expansionState.value.progress >= 1f }, ) frame.addView( composeView, @@ -488,12 +497,8 @@ constructor( private fun setListenerCollections() { lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - // TODO - // setListenerJob( - // scrollListener, - // - // ) + this@QSFragmentCompose.view?.setSnapshotBinding { + scrollListener.value?.onQsPanelScrollChanged(scrollState.value) } launch { setListenerJob( @@ -528,6 +533,7 @@ constructor( viewModel.containerViewModel.quickQuickSettingsViewModel.squishinessViewModel .squishiness .collectAsStateWithLifecycle() + Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) { Box( modifier = @@ -591,7 +597,12 @@ constructor( modifier = Modifier.element(ElementKeys.QuickSettingsContent).fillMaxSize().weight(1f) ) { - Column { + DisposableEffect(Unit) { + lifecycleScope.launch { scrollState.scrollTo(0) } + onDispose { lifecycleScope.launch { scrollState.scrollTo(0) } } + } + + Column(modifier = Modifier.verticalScroll(scrollState)) { Spacer( modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() } ) @@ -601,15 +612,14 @@ constructor( ) } } - QuickSettingsTheme { - FooterActions( - viewModel = viewModel.footerActionsViewModel, - qsVisibilityLifecycleOwner = this@QSFragmentCompose, - modifier = - Modifier.sysuiResTag("qs_footer_actions") - .element(ElementKeys.FooterActions), - ) - } + } + QuickSettingsTheme { + FooterActions( + viewModel = viewModel.footerActionsViewModel, + qsVisibilityLifecycleOwner = this@QSFragmentCompose, + modifier = + Modifier.sysuiResTag("qs_footer_actions").element(ElementKeys.FooterActions), + ) } } } @@ -791,13 +801,17 @@ private class ExpansionTransition(currentProgress: Float) : private const val EDIT_MODE_TIME_MILLIS = 500 /** - * Ignore touches below the value returned by [clippingTopProvider], when clipping is enabled, as - * per [clippingEnabledProvider]. + * Performs different touch handling based on the state of the ComposeView: + * * Ignore touches below the value returned by [clippingTopProvider], when clipping is enabled, as + * per [clippingEnabledProvider]. + * * Intercept touches that would overscroll QS forward and instead allow them to be used to close + * the shade. */ private class FrameLayoutTouchPassthrough( context: Context, private val clippingEnabledProvider: () -> Boolean, private val clippingTopProvider: () -> Int, + private val canScrollForwardQs: () -> Boolean, ) : FrameLayout(context) { override fun isTransformedTouchPointInView( x: Float, @@ -811,4 +825,32 @@ private class FrameLayoutTouchPassthrough( super.isTransformedTouchPointInView(x, y, child, outLocalPoint) } } + + val touchSlop = ViewConfiguration.get(context).scaledTouchSlop + var downY = 0f + + override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { + // If there's a touch on this view and we can scroll down, we don't want to be intercepted + val action = ev.actionMasked + + when (action) { + MotionEvent.ACTION_DOWN -> { + // If we can scroll down, make sure none of our parents intercepts us. + if (canScrollForwardQs()) { + parent?.requestDisallowInterceptTouchEvent(true) + } + downY = ev.y + } + + MotionEvent.ACTION_MOVE -> { + val y = ev.y.toInt() + val yDiff: Float = y - downY + if (yDiff < -touchSlop && !canScrollForwardQs()) { + // Intercept touches that are overscrolling. + return true + } + } + } + return super.onInterceptTouchEvent(ev) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index e749475479d8..d55763aaeddb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -19,7 +19,7 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons @@ -97,7 +97,9 @@ constructor( TileGrid(tiles = page, modifier = Modifier, editModeStart = {}) } } - Box(modifier = Modifier.height(FooterHeight).fillMaxWidth()) { + // Use requiredHeight so it won't be squished if the view doesn't quite fit. As this is + // expected to be inside a scrollable container, this should not be an issue. + Box(modifier = Modifier.requiredHeight(FooterHeight).fillMaxWidth()) { PagerDots( pagerState = pagerState, activeColor = MaterialTheme.colorScheme.primary, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index a645b51404e7..cfd667bc4986 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -38,8 +38,7 @@ fun SceneScope.QuickQuickSettings( viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier, ) { - val sizedTiles by - viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList()) + val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle() val tiles = sizedTiles.fastMap { it.tile } val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle() -- GitLab From 0b558a935f18be75b71f06b81d8a807c3fca4c3d Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Mon, 14 Oct 2024 18:31:46 +0000 Subject: [PATCH 192/441] Do not handle touches next to shelf NSSL would return true for all touches below the last notification, and to the right of the shelf. This is essentially empty space. The reason for this is the SwipeHelper is always returning true, which would set horizontalSwipeWantsIt == true even though that is clearly not true on a tap. This causes an issue with tapping the unlocked icon when the last notification is directly above it. The shelf was taking the touch directly over lock icon, preventing device unlock. Instead, use the swipe information from the intercept event which seems to be more accurate, and carry this state to the onTouch event. Fixes: 370270324 Fixes: 358424256 Test: manual - horizontal swipe notifications Test: manual - vertically swipe notifications Test: manual - all touch interactions with notifs Test: manual - tap on shelf to expand shade Flag: EXEMPT bugfix Change-Id: I906da1cf5e5106ada355232941399efe7f1d3b14 --- .../res/layout/super_notification_shade.xml | 16 ++++++++-------- ...NotificationStackScrollLayoutController.java | 17 +++++++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 22d34eb7b115..fbb07bed4b50 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -58,22 +58,22 @@ android:layout_height="match_parent" android:visibility="invisible" /> - - + - - + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 00c5c40fc8ac..f3437b5732ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -2098,8 +2098,13 @@ public class NotificationStackScrollLayoutController implements Dumpable { } class TouchHandler implements Gefingerpoken { + private boolean mSwipeWantsIt = false; + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { + // Reset on each call to intercept, and share swipe state with onTouchEvent() + // below when this method returns true. + mSwipeWantsIt = false; mView.initDownStates(ev); mView.handleEmptySpaceClick(ev); @@ -2126,17 +2131,16 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.startDraggingOnHun(); } } - boolean swipeWantsIt = false; if (mLongPressedView == null && !mView.isBeingDragged() && !mView.isExpandingNotification() && !mView.getExpandedInThisMotion() && !mView.getOnlyScrollingInThisMotion() && !mView.getDisallowDismissInThisMotion()) { - swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); + mSwipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); } // Check if we need to clear any snooze leavebehinds boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; - if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt && + if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !mSwipeWantsIt && !expandWantsIt && !scrollWantsIt) { mView.setCheckForLeaveBehind(false); mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, @@ -2155,7 +2159,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { && ev.getActionMasked() != MotionEvent.ACTION_DOWN) { mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING); } - return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt || hunWantsIt; + return mSwipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt || + hunWantsIt; } @Override @@ -2192,7 +2197,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } } } - boolean horizontalSwipeWantsIt = false; + boolean horizontalSwipeWantsIt = mSwipeWantsIt; boolean scrollerWantsIt = false; // NOTE: the order of these is important. If reversed, onScrollTouch will reset on an // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes. @@ -2201,7 +2206,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { && !mView.getExpandedInThisMotion() && !onlyScrollingInThisMotion && !mView.getDisallowDismissInThisMotion()) { - horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); + mSwipeHelper.onTouchEvent(ev); } if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping() && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) { -- GitLab From 35137debc4bf0116093d6df0925a7a6dd88f714c Mon Sep 17 00:00:00 2001 From: Ats Jenk Date: Fri, 11 Oct 2024 17:28:09 -0700 Subject: [PATCH 193/441] Pass bubble logger to BubbleBarExpandedView Enables logging bubble metrics from expanded view. Bug: 349845968 Test: atest BubblesTest Test: atest WMShellUnitTests Test: atest BubbleStackViewTest Test: atest BubbleBarExpandedViewTest Test: atest BubbleOverflowTest Test: atest BubbleViewInfoTaskTest Flag: EXEMPT refactor Change-Id: I51bba20fbb11ce4debd85de52cc1627874c72353 --- .../wm/shell/bubbles/BubbleStackViewTest.kt | 5 ++++- .../wm/shell/bubbles/BubbleViewInfoTaskTest.kt | 8 ++++++-- .../bubbles/bar/BubbleBarExpandedViewTest.kt | 5 ++++- .../src/com/android/wm/shell/bubbles/Bubble.java | 4 ++++ .../wm/shell/bubbles/BubbleController.java | 7 ++++++- .../com/android/wm/shell/bubbles/BubbleData.java | 2 +- .../android/wm/shell/bubbles/BubbleOverflow.kt | 6 ++++-- .../wm/shell/bubbles/BubbleViewInfoTask.java | 8 ++++++-- .../shell/bubbles/BubbleViewInfoTaskLegacy.java | 15 ++++++++++----- .../shell/bubbles/bar/BubbleBarExpandedView.java | 4 ++++ .../wm/shell/bubbles/BubbleOverflowTest.java | 5 ++++- 11 files changed, 53 insertions(+), 16 deletions(-) diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 96ffa03a1f65..52ce8cb5c0dc 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -67,6 +67,7 @@ class BubbleStackViewTest { private val context = ApplicationProvider.getApplicationContext() private lateinit var positioner: BubblePositioner + private lateinit var bubbleLogger: BubbleLogger private lateinit var iconFactory: BubbleIconFactory private lateinit var expandedViewManager: FakeBubbleExpandedViewManager private lateinit var bubbleStackView: BubbleStackView @@ -96,10 +97,11 @@ class BubbleStackViewTest { ) ) positioner = BubblePositioner(context, windowManager) + bubbleLogger = BubbleLogger(UiEventLoggerFake()) bubbleData = BubbleData( context, - BubbleLogger(UiEventLoggerFake()), + bubbleLogger, positioner, BubbleEducationController(context), shellExecutor, @@ -394,6 +396,7 @@ class BubbleStackViewTest { expandedViewManager, bubbleTaskViewFactory, positioner, + bubbleLogger, bubbleStackView, null, iconFactory, diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt index 9fdde128ce41..712cc7c475bc 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt @@ -29,6 +29,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R +import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.launcher3.icons.BubbleIconFactory @@ -70,6 +71,7 @@ class BubbleViewInfoTaskTest { private lateinit var bgExecutor: TestExecutor private lateinit var bubbleStackView: BubbleStackView private lateinit var bubblePositioner: BubblePositioner + private lateinit var bubbleLogger: BubbleLogger private lateinit var expandedViewManager: BubbleExpandedViewManager private val bubbleTaskViewFactory = BubbleTaskViewFactory { @@ -103,10 +105,11 @@ class BubbleViewInfoTaskTest { mainExecutor ) bubblePositioner = BubblePositioner(context, windowManager) + bubbleLogger = BubbleLogger(UiEventLoggerFake()) val bubbleData = BubbleData( context, - mock(), + bubbleLogger, bubblePositioner, BubbleEducationController(context), mainExecutor, @@ -138,7 +141,7 @@ class BubbleViewInfoTaskTest { WindowManagerShellWrapper(mainExecutor), mock(), mock(), - mock(), + bubbleLogger, mock(), mock(), bubblePositioner, @@ -314,6 +317,7 @@ class BubbleViewInfoTaskTest { expandedViewManager, bubbleTaskViewFactory, bubblePositioner, + bubbleLogger, bubbleStackView, null /* layerView */, iconFactory, diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt index 35d459f27534..f181ce004478 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt @@ -27,11 +27,13 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleData import com.android.wm.shell.bubbles.BubbleExpandedViewManager +import com.android.wm.shell.bubbles.BubbleLogger import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.bubbles.BubbleTaskView import com.android.wm.shell.bubbles.BubbleTaskViewFactory @@ -106,11 +108,12 @@ class BubbleBarExpandedViewTest { bubbleExpandedView.initialize( expandedViewManager, positioner, + BubbleLogger(UiEventLoggerFake()), false /* isOverflow */, bubbleTaskView, mainExecutor, bgExecutor, - regionSamplingProvider + regionSamplingProvider, ) getInstrumentation().runOnMainSync(Runnable { 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 e3fc5c2273e2..e8e25e20d8d8 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 @@ -572,6 +572,7 @@ public class Bubble implements BubbleViewProvider { * @param expandedViewManager the bubble expanded view manager. * @param taskViewFactory the task view factory used to create the task view for the bubble. * @param positioner the bubble positioner. + * @param bubbleLogger log bubble metrics. * @param stackView the view the bubble is added to, iff showing as floating. * @param layerView the layer the bubble is added to, iff showing in the bubble bar. * @param iconFactory the icon factory used to create images for the bubble. @@ -581,6 +582,7 @@ public class Bubble implements BubbleViewProvider { BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory taskViewFactory, BubblePositioner positioner, + BubbleLogger bubbleLogger, @Nullable BubbleStackView stackView, @Nullable BubbleBarLayerView layerView, BubbleIconFactory iconFactory, @@ -595,6 +597,7 @@ public class Bubble implements BubbleViewProvider { expandedViewManager, taskViewFactory, positioner, + bubbleLogger, stackView, layerView, iconFactory, @@ -616,6 +619,7 @@ public class Bubble implements BubbleViewProvider { expandedViewManager, taskViewFactory, positioner, + bubbleLogger, stackView, layerView, iconFactory, 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 eb9cdab6e856..37e8ead4fc78 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 @@ -892,7 +892,7 @@ public class BubbleController implements ConfigurationChangeListener, registerBroadcastReceiver(); if (isShowingAsBubbleBar()) { mBubbleData.getOverflow().initializeForBubbleBar( - mExpandedViewManager, mBubblePositioner); + mExpandedViewManager, mBubblePositioner, mLogger); } else { mBubbleData.getOverflow().initialize( mExpandedViewManager, mStackView, mBubblePositioner); @@ -1100,6 +1100,7 @@ public class BubbleController implements ConfigurationChangeListener, mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, + mLogger, mStackView, mLayerView, mBubbleIconFactory, @@ -1111,6 +1112,7 @@ public class BubbleController implements ConfigurationChangeListener, mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, + mLogger, mStackView, mLayerView, mBubbleIconFactory, @@ -1585,6 +1587,7 @@ public class BubbleController implements ConfigurationChangeListener, mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, + mLogger, mStackView, mLayerView, mBubbleIconFactory, @@ -1647,6 +1650,7 @@ public class BubbleController implements ConfigurationChangeListener, mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, + mLogger, mStackView, mLayerView, mBubbleIconFactory, @@ -1727,6 +1731,7 @@ public class BubbleController implements ConfigurationChangeListener, mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, + mLogger, mStackView, mLayerView, mBubbleIconFactory, 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 709a7bdc61f2..4de9dfa54c5d 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 @@ -495,7 +495,7 @@ public class BubbleData { /** * When this method is called it is expected that all info in the bubble has completed loading. * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager, - * BubbleTaskViewFactory, BubblePositioner, BubbleStackView, + * BubbleTaskViewFactory, BubblePositioner, BubbleLogger, BubbleStackView, * com.android.wm.shell.bubbles.bar.BubbleBarLayerView, * com.android.launcher3.icons.BubbleIconFactory, boolean) */ 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 68c4657f2b68..c74412b825d9 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 @@ -73,17 +73,19 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl fun initializeForBubbleBar( expandedViewManager: BubbleExpandedViewManager, - positioner: BubblePositioner + positioner: BubblePositioner, + bubbleLogger: BubbleLogger, ) { createBubbleBarExpandedView() .initialize( expandedViewManager, positioner, + bubbleLogger, /* isOverflow= */ true, /* bubbleTaskView= */ null, /* mainExecutor= */ null, /* backgroundExecutor= */ null, - /* regionSamplingProvider= */ null + /* regionSamplingProvider= */ null, ) } 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 39fb2f49c1ee..96b6043059d2 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 @@ -73,6 +73,7 @@ public class BubbleViewInfoTask { private final WeakReference mExpandedViewManager; private final WeakReference mTaskViewFactory; private final WeakReference mPositioner; + private final WeakReference mBubbleLogger; private final WeakReference mStackView; private final WeakReference mLayerView; private final BubbleIconFactory mIconFactory; @@ -94,6 +95,7 @@ public class BubbleViewInfoTask { BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory taskViewFactory, BubblePositioner positioner, + BubbleLogger bubbleLogger, @Nullable BubbleStackView stackView, @Nullable BubbleBarLayerView layerView, BubbleIconFactory factory, @@ -106,6 +108,7 @@ public class BubbleViewInfoTask { mExpandedViewManager = new WeakReference<>(expandedViewManager); mTaskViewFactory = new WeakReference<>(taskViewFactory); mPositioner = new WeakReference<>(positioner); + mBubbleLogger = new WeakReference<>(bubbleLogger); mStackView = new WeakReference<>(stackView); mLayerView = new WeakReference<>(layerView); mIconFactory = factory; @@ -221,8 +224,9 @@ public class BubbleViewInfoTask { ProtoLog.v(WM_SHELL_BUBBLES, "Task initializing bubble bar expanded view key=%s", mBubble.getKey()); viewInfo.bubbleBarExpandedView.initialize(mExpandedViewManager.get(), - mPositioner.get(), false /* isOverflow */, viewInfo.taskView, - mMainExecutor, mBgExecutor, new RegionSamplingProvider() { + mPositioner.get(), mBubbleLogger.get(), false /* isOverflow */, + viewInfo.taskView, mMainExecutor, mBgExecutor, + new RegionSamplingProvider() { @Override public RegionSamplingHelper createHelper(View sampledView, RegionSamplingHelper.SamplingCallback callback, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java index e9a593392dc2..c1da94cc470f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java @@ -78,6 +78,7 @@ public class BubbleViewInfoTaskLegacy extends private WeakReference mExpandedViewManager; private WeakReference mTaskViewFactory; private WeakReference mPositioner; + private WeakReference mBubbleLogger; private WeakReference mStackView; private WeakReference mLayerView; private BubbleIconFactory mIconFactory; @@ -95,6 +96,7 @@ public class BubbleViewInfoTaskLegacy extends BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory taskViewFactory, BubblePositioner positioner, + BubbleLogger bubbleLogger, @Nullable BubbleStackView stackView, @Nullable BubbleBarLayerView layerView, BubbleIconFactory factory, @@ -107,6 +109,7 @@ public class BubbleViewInfoTaskLegacy extends mExpandedViewManager = new WeakReference<>(expandedViewManager); mTaskViewFactory = new WeakReference<>(taskViewFactory); mPositioner = new WeakReference<>(positioner); + mBubbleLogger = new WeakReference<>(bubbleLogger); mStackView = new WeakReference<>(stackView); mLayerView = new WeakReference<>(layerView); mIconFactory = factory; @@ -124,8 +127,9 @@ public class BubbleViewInfoTaskLegacy extends } if (mLayerView.get() != null) { return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(), - mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory, - mBubble, mSkipInflation, mMainExecutor, mBackgroundExecutor); + mTaskViewFactory.get(), mPositioner.get(), mBubbleLogger.get(), + mLayerView.get(), mIconFactory, mBubble, mSkipInflation, mMainExecutor, + mBackgroundExecutor); } else { return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(), mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory, @@ -187,6 +191,7 @@ public class BubbleViewInfoTaskLegacy extends BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory taskViewFactory, BubblePositioner positioner, + BubbleLogger bubbleLogger, BubbleBarLayerView layerView, BubbleIconFactory iconFactory, Bubble b, @@ -200,9 +205,9 @@ public class BubbleViewInfoTaskLegacy extends LayoutInflater inflater = LayoutInflater.from(c); info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate( R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */); - info.bubbleBarExpandedView.initialize( - expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView, - mainExecutor, backgroundExecutor, new RegionSamplingProvider() { + info.bubbleBarExpandedView.initialize(expandedViewManager, positioner, bubbleLogger, + false /* isOverflow */, bubbleTaskView, mainExecutor, backgroundExecutor, + new RegionSamplingProvider() { @Override public RegionSamplingHelper createHelper(View sampledView, RegionSamplingHelper.SamplingCallback callback, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 2a9001728cc2..84405bbe5823 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -39,6 +39,7 @@ import androidx.annotation.VisibleForTesting; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleExpandedViewManager; +import com.android.wm.shell.bubbles.BubbleLogger; import com.android.wm.shell.bubbles.BubbleOverflowContainerView; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleTaskView; @@ -90,6 +91,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private Bubble mBubble; private BubbleExpandedViewManager mManager; private BubblePositioner mPositioner; + private BubbleLogger mBubbleLogger; private boolean mIsOverflow; private BubbleTaskViewHelper mBubbleTaskViewHelper; private BubbleBarMenuViewController mMenuViewController; @@ -178,6 +180,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView /** Initializes the view, must be called before doing anything else. */ public void initialize(BubbleExpandedViewManager expandedViewManager, BubblePositioner positioner, + BubbleLogger bubbleLogger, boolean isOverflow, @Nullable BubbleTaskView bubbleTaskView, @Nullable Executor mainExecutor, @@ -185,6 +188,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Nullable RegionSamplingProvider regionSamplingProvider) { mManager = expandedViewManager; mPositioner = positioner; + mBubbleLogger = bubbleLogger; mIsOverflow = isOverflow; mMainExecutor = mainExecutor; mBackgroundExecutor = backgroundExecutor; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java index 094af9652ea3..a1d4a1a301bd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java @@ -26,6 +26,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; +import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.wm.shell.ShellTestCase; import org.junit.Before; @@ -45,6 +46,7 @@ public class BubbleOverflowTest extends ShellTestCase { private TestableBubblePositioner mPositioner; private BubbleOverflow mOverflow; private BubbleExpandedViewManager mExpandedViewManager; + private BubbleLogger mBubbleLogger; @Mock private BubbleController mBubbleController; @@ -58,6 +60,7 @@ public class BubbleOverflowTest extends ShellTestCase { mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(mBubbleController); mPositioner = new TestableBubblePositioner(mContext, mContext.getSystemService(WindowManager.class)); + mBubbleLogger = new BubbleLogger(new UiEventLoggerFake()); when(mBubbleController.getPositioner()).thenReturn(mPositioner); when(mBubbleController.getStackView()).thenReturn(mBubbleStackView); @@ -77,7 +80,7 @@ public class BubbleOverflowTest extends ShellTestCase { @Test public void test_initialize_forBubbleBar() { - mOverflow.initializeForBubbleBar(mExpandedViewManager, mPositioner); + mOverflow.initializeForBubbleBar(mExpandedViewManager, mPositioner, mBubbleLogger); assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull(); assertThat(mOverflow.getExpandedView()).isNull(); -- GitLab From 1ea2692b95085f136ee407a7d281c0ada2549c40 Mon Sep 17 00:00:00 2001 From: Shane Date: Tue, 15 Oct 2024 16:57:11 +0000 Subject: [PATCH 194/441] [VRR] Not suppress touch boost in ViewRootImpl Avoiding a specific window type from receiving touch boost signals in ViewRootImpl is not an ideal design. Instead, the suppression should be handled at the app level (e.g., by Gboard). Test: atest ViewFrameRateTest / atest ViewRootImplTest Flag: android.view.flags.toolkit_frame_rate_touch_boost_25q1 Bug: 373641534 Change-Id: I9e2454a24962a76d3c3222fb3e446de35d13597d --- core/java/android/view/ViewRootImpl.java | 6 ++ .../view/flags/refresh_rate_flags.aconfig | 8 +++ .../src/android/view/ViewFrameRateTest.java | 62 +++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0ca442d66e6f..733ecadf4951 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -118,6 +118,7 @@ import static android.view.flags.Flags.disableDrawWakeLock; import static android.view.flags.Flags.sensitiveContentAppProtection; import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix; import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly; +import static android.view.flags.Flags.toolkitFrameRateTouchBoost25q1; import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly; import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly; import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly; @@ -13045,6 +13046,11 @@ public final class ViewRootImpl implements ViewParent, boolean desiredAction = motionEventAction != MotionEvent.ACTION_OUTSIDE; boolean undesiredType = windowType == TYPE_INPUT_METHOD && sToolkitFrameRateTypingReadOnlyFlagValue; + + // don't suppress touch boost for TYPE_INPUT_METHOD in ViewRootImpl + if (toolkitFrameRateTouchBoost25q1()) { + return desiredAction && shouldEnableDvrr() && getFrameRateBoostOnTouchEnabled(); + } // use toolkitSetFrameRate flag to gate the change return desiredAction && !undesiredType && shouldEnableDvrr() && getFrameRateBoostOnTouchEnabled(); diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig index 7b049278731d..c31df73fbeae 100644 --- a/core/java/android/view/flags/refresh_rate_flags.aconfig +++ b/core/java/android/view/flags/refresh_rate_flags.aconfig @@ -119,4 +119,12 @@ flag { description: "Feature flag to enable the fix for applyLegacyAnimation for VRR V QPR2" bug: "335874198" is_fixed_read_only: true +} + +flag { + name: "toolkit_frame_rate_touch_boost_25q1" + namespace: "toolkit" + description: "Feature flag to not suppress touch boost for specific windowTypes in VRR V QPR2" + bug: "335874198" + is_exported: true } \ No newline at end of file diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index c98142357d69..483ebc2c8649 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -24,6 +24,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_ANIMATION_BUGFIX_25Q1; import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY; +import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_TOUCH_BOOST_25Q1; import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY; import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; @@ -117,6 +118,67 @@ public class ViewFrameRateTest { waitForAfterDraw(); } + @Test + @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_TOUCH_BOOST_25Q1) + public void shouldNotSuppressTouchBoost() throws Throwable { + if (!ViewProperties.vrr_enabled().orElse(true)) { + return; + } + + mActivityRule.runOnUiThread(() -> { + ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams(); + layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + mMovingView.setLayoutParams(layoutParams); + mMovingView.setOnClickListener((v) -> {}); + }); + waitForFrameRateCategoryToSettle(); + + int[] position = new int[2]; + mActivityRule.runOnUiThread(() -> { + mMovingView.getLocationOnScreen(position); + position[0] += mMovingView.getWidth() / 2; + position[1] += mMovingView.getHeight() / 2; + }); + final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + + // update the window type to TYPE_INPUT_METHOD + int windowType = mViewRoot.mWindowAttributes.type; + final WindowManager.LayoutParams attrs = mViewRoot.mWindowAttributes; + attrs.type = TYPE_INPUT_METHOD; + instrumentation.runOnMainSync(() -> { + mViewRoot.setLayoutParams(attrs, false); + }); + instrumentation.waitForIdleSync(); + + final WindowManager.LayoutParams newAttrs = mViewRoot.mWindowAttributes; + assertTrue(newAttrs.type == TYPE_INPUT_METHOD); + + long now = SystemClock.uptimeMillis(); + MotionEvent down = MotionEvent.obtain( + now, // downTime + now, // eventTime + MotionEvent.ACTION_DOWN, // action + position[0], // x + position[1], // y + 0 // metaState + ); + down.setSource(InputDevice.SOURCE_TOUCHSCREEN); + instrumentation.sendPointerSync(down); + down.recycle(); + + // should have touch boost + assertTrue(mViewRoot.getIsTouchBoosting()); + + // Reset the window type back to the original one. + newAttrs.type = windowType; + instrumentation.runOnMainSync(() -> { + mViewRoot.setLayoutParams(newAttrs, false); + }); + instrumentation.waitForIdleSync(); + assertTrue(mViewRoot.mWindowAttributes.type == windowType); + } + @Test @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY) public void inputMethodWithContentMoves() throws Throwable { -- GitLab From ade1961f0aec952a58231ae0d2aaa67c22c10a7d Mon Sep 17 00:00:00 2001 From: Vlad Zavidovych Date: Tue, 15 Oct 2024 17:32:55 +0000 Subject: [PATCH 195/441] Fix typo in app usage docs Flag: DOCS_ONLY Change-Id: I108a4cbb4698053142225d71bf220073b6a146e9 --- core/java/android/app/usage/UsageEventsQuery.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java index c0f13ca557e2..ecf4cd115fef 100644 --- a/core/java/android/app/usage/UsageEventsQuery.java +++ b/core/java/android/app/usage/UsageEventsQuery.java @@ -75,7 +75,7 @@ public final class UsageEventsQuery implements Parcelable { } /** - * Returns the exclusive timpstamp to indicate the end of the range of events. + * Returns the exclusive timestamp to indicate the end of the range of events. * Defined in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}. */ public @CurrentTimeMillisLong long getEndTimeMillis() { -- GitLab From 44ac5d2438ee55e5df4b33af6079709b5e1190d3 Mon Sep 17 00:00:00 2001 From: Weilin Xu Date: Wed, 9 Oct 2024 10:58:35 -0700 Subject: [PATCH 196/441] Define emergency alert parcelable classes Bug: 361348719 Flag: android.hardware.radio.hd_radio_emergency_alert_system Test: atest BroadcastRadioTests Change-Id: I91a4bd6170d9ecbfc383f8a117f4ba9af1256886 --- .../android/hardware/radio/RadioAlert.aidl | 20 + .../android/hardware/radio/RadioAlert.java | 505 ++++++++++++++++++ 2 files changed, 525 insertions(+) create mode 100644 core/java/android/hardware/radio/RadioAlert.aidl create mode 100644 core/java/android/hardware/radio/RadioAlert.java diff --git a/core/java/android/hardware/radio/RadioAlert.aidl b/core/java/android/hardware/radio/RadioAlert.aidl new file mode 100644 index 000000000000..17f4fc7e9a13 --- /dev/null +++ b/core/java/android/hardware/radio/RadioAlert.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.radio; + +/** @hide */ +parcelable RadioAlert; \ No newline at end of file diff --git a/core/java/android/hardware/radio/RadioAlert.java b/core/java/android/hardware/radio/RadioAlert.java new file mode 100644 index 000000000000..b55dcd82ef7b --- /dev/null +++ b/core/java/android/hardware/radio/RadioAlert.java @@ -0,0 +1,505 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.radio; + +import android.annotation.FlaggedApi; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Emergency Alert Message + * + *

Alert message can be sent from a radio station of technologies such as HD radio to + * the radio users for some emergency events (see ITU-T X.1303 bis for more info). + * @hide + */ +@FlaggedApi(Flags.FLAG_HD_RADIO_EMERGENCY_ALERT_SYSTEM) +public final class RadioAlert implements Parcelable { + + public static final class Geocode implements Parcelable { + + private final String mValueName; + private final String mValue; + + /** + * Constructor of geocode. + * + * @param valueName Name of geocode value + * @param value Value of geocode + * @hide + */ + public Geocode(@NonNull String valueName, @NonNull String value) { + mValueName = Objects.requireNonNull(valueName, "Geocode value name can not be null"); + mValue = Objects.requireNonNull(value, "Geocode value can not be null"); + } + + private Geocode(Parcel in) { + mValueName = in.readString8(); + mValue = in.readString8(); + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public Geocode createFromParcel(Parcel in) { + return new Geocode(in); + } + + @Override + public Geocode[] newArray(int size) { + return new Geocode[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mValueName); + dest.writeString8(mValue); + } + + @NonNull + @Override + public String toString() { + return "Gecode [valueName=" + mValueName + ", value=" + mValue + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mValueName, mValue); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Geocode other)) { + return false; + } + + return Objects.equals(mValueName, other.mValueName) + && Objects.equals(mValue, other.mValue); + } + } + + public static final class Coordinate implements Parcelable { + private final double mLatitude; + private final double mLongitude; + + /** + * Constructor of coordinate. + * + * @param latitude Latitude of the coordinate + * @param longitude Longitude of the coordinate + * @hide + */ + public Coordinate(double latitude, double longitude) { + if (latitude < -90.0 || latitude > 90.0) { + throw new IllegalArgumentException("Latitude value should be between -90 and 90"); + } + if (longitude < -180.0 || longitude > 180.0) { + throw new IllegalArgumentException( + "Longitude value should be between -180 and 180"); + } + mLatitude = latitude; + mLongitude = longitude; + } + + private Coordinate(Parcel in) { + mLatitude = in.readDouble(); + mLongitude = in.readDouble(); + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public Coordinate createFromParcel(Parcel in) { + return new Coordinate(in); + } + + @Override + public Coordinate[] newArray(int size) { + return new Coordinate[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeDouble(mLatitude); + dest.writeDouble(mLongitude); + } + + @NonNull + @Override + public String toString() { + return "Coordinate [latitude=" + mLatitude + ", longitude=" + mLongitude + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mLatitude, mLongitude); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Coordinate other)) { + return false; + } + return mLatitude == other.mLatitude && mLongitude == other.mLongitude; + } + } + + public static final class Polygon implements Parcelable { + + private final List mCoordinates; + + /** + * Constructor of polygon. + * + * @param coordinates Coordinates the polygon is composed of + * @hide + */ + public Polygon(@NonNull List coordinates) { + Objects.requireNonNull(coordinates, "Coordinates can not be null"); + if (coordinates.size() < 4) { + throw new IllegalArgumentException("Number of coordinates must be at least 4"); + } + if (!coordinates.get(0).equals(coordinates.get(coordinates.size() - 1))) { + throw new IllegalArgumentException( + "The last and first coordinates must be the same"); + } + mCoordinates = coordinates; + } + + private Polygon(Parcel in) { + mCoordinates = new ArrayList<>(); + in.readTypedList(mCoordinates, Coordinate.CREATOR); + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public Polygon createFromParcel(Parcel in) { + return new Polygon(in); + } + + @Override + public Polygon[] newArray(int size) { + return new Polygon[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedList(mCoordinates); + } + + @NonNull + @Override + public String toString() { + return "Polygon [coordinates=" + mCoordinates + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mCoordinates); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Polygon other)) { + return false; + } + return mCoordinates.equals(other.mCoordinates); + } + } + + public static final class AlertArea implements Parcelable { + + private final List mPolygons; + private final List mGeocodes; + + /** + * Constructor of alert area. + * + * @param polygons Polygons used in alert area + * @param geocodes Geocodes used in alert area + * @hide + */ + public AlertArea(@NonNull List polygons, @NonNull List geocodes) { + mPolygons = Objects.requireNonNull(polygons, "Polygons can not be null"); + mGeocodes = Objects.requireNonNull(geocodes, "Geocodes can not be null"); + } + + private AlertArea(Parcel in) { + mPolygons = new ArrayList<>(); + mGeocodes = new ArrayList<>(); + in.readTypedList(mPolygons, Polygon.CREATOR); + in.readTypedList(mGeocodes, Geocode.CREATOR); + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public AlertArea createFromParcel(Parcel in) { + return new AlertArea(in); + } + + @Override + public AlertArea[] newArray(int size) { + return new AlertArea[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedList(mPolygons); + dest.writeTypedList(mGeocodes); + } + + @NonNull + @Override + public String toString() { + return "AlertArea [polygons=" + mPolygons + ", geocodes=" + mGeocodes + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mPolygons, mGeocodes); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AlertArea other)) { + return false; + } + + return mPolygons.equals(other.mPolygons) && mGeocodes.equals(other.mGeocodes); + } + } + + public static final class AlertInfo implements Parcelable { + + private final List mCategoryList; + private final int mUrgency; + private final int mSeverity; + private final int mCertainty; + private final String mTextualMessage; + private final List mAreaList; + + /** + * Constructor for alert info. + * + * @param categoryList Array of categories of the subject event of the alert message + * @param urgency The urgency of the subject event of the alert message + * @param severity The severity of the subject event of the alert message + * @param certainty The certainty of the subject event of the alert message + * @param textualMessage Textual descriptions of the subject event + * @param areaList The array of geographic areas to which the alert info segment in which + * it appears applies + * @hide + */ + public AlertInfo(@NonNull List categoryList, int urgency, + int severity, int certainty, + String textualMessage, @NonNull List areaList) { + mCategoryList = Objects.requireNonNull(categoryList, "Category list can not be null"); + mUrgency = urgency; + mSeverity = severity; + mCertainty = certainty; + mTextualMessage = textualMessage; + mAreaList = Objects.requireNonNull(areaList, "Area list can not be null"); + } + + private AlertInfo(Parcel in) { + mCategoryList = in.readArrayList(Integer.class.getClassLoader(), Integer.class); + mUrgency = in.readInt(); + mSeverity = in.readInt(); + mCertainty = in.readInt(); + mTextualMessage = in.readString8(); + mAreaList = new ArrayList<>(); + in.readTypedList(mAreaList, AlertArea.CREATOR); + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public AlertInfo createFromParcel(Parcel in) { + return new AlertInfo(in); + } + + @Override + public AlertInfo[] newArray(int size) { + return new AlertInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeList(mCategoryList); + dest.writeInt(mUrgency); + dest.writeInt(mSeverity); + dest.writeInt(mCertainty); + dest.writeString8(mTextualMessage); + dest.writeTypedList(mAreaList); + } + + @NonNull + @Override + public String toString() { + return "AlertInfo [categoryList=" + mCategoryList + ", urgency=" + mUrgency + + ", severity=" + mSeverity + ", certainty=" + mCertainty + + ", textualMessage=" + mTextualMessage + ", areaList=" + mAreaList + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mCategoryList, mUrgency, mSeverity, mCertainty, mTextualMessage, + mAreaList); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AlertInfo other)) { + return false; + } + + return mCategoryList.equals(other.mCategoryList) && mUrgency == other.mUrgency + && mSeverity == other.mSeverity && mCertainty == other.mCertainty + && mTextualMessage.equals(other.mTextualMessage) + && mAreaList.equals(other.mAreaList); + } + } + + private final int mStatus; + private final int mMessageType; + private final List mInfoList; + private final int mScope; + + /** + * Constructor of radio alert message. + * + * @param status Status of alert message + * @param messageType Message type of alert message + * @param infoList List of alert info + * @param scope Scope of alert message + * @hide + */ + public RadioAlert(int status, int messageType, + @NonNull List infoList, int scope) { + mStatus = status; + mMessageType = messageType; + mInfoList = Objects.requireNonNull(infoList, "Alert info list can not be null"); + mScope = scope; + } + + private RadioAlert(Parcel in) { + mStatus = in.readInt(); + mMessageType = in.readInt(); + mInfoList = in.readParcelableList(new ArrayList<>(), AlertInfo.class.getClassLoader(), + AlertInfo.class); + mScope = in.readInt(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mStatus); + dest.writeInt(mMessageType); + dest.writeParcelableList(mInfoList, /* flags= */ 0); + dest.writeInt(mScope); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + @Override + public String toString() { + return "RadioAlert [status=" + mStatus + ", messageType=" + mMessageType + + ", infoList= " + mInfoList + ", scope=" + mScope + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mStatus, mMessageType, mInfoList, mScope); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof RadioAlert other)) { + return false; + } + + return mStatus == other.mStatus && mMessageType == other.mMessageType + && mInfoList.equals(other.mInfoList) && mScope == other.mScope; + } + + public static final @NonNull Creator CREATOR = new Creator() { + @Override + public RadioAlert createFromParcel(Parcel in) { + return new RadioAlert(in); + } + + @Override + public RadioAlert[] newArray(int size) { + return new RadioAlert[size]; + } + }; +} -- GitLab From c36a6daa8a350480997855b671025f9050c20d30 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Tue, 15 Oct 2024 14:10:27 +0200 Subject: [PATCH 197/441] Notif redesign: Show small icon for headless system apps The implementation to determine if an app iss a headless system app is the same as the one currently in Notification.java (that's currently used by a prototype and will be removed). Bug: 371174789 Test: tested manually for now, unit tests to be added when caching is implemented Flag: android.app.notifications_redesign_app_icons Change-Id: Id93b040f05b6ecea5a41f96a3a66179ba7f58513 --- .../row/NotificationRowModule.java | 3 +- .../row/icon/NotificationIconStyleProvider.kt | 90 +++++++++++++++++++ .../NotificationRowIconViewInflaterFactory.kt | 8 +- .../row/ExpandableNotificationRowBuilder.kt | 6 +- .../NotificationIconStyleProviderKosmos.kt | 21 +++++ 5 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java index 43ade5cc5a7f..4feb78a46d60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.row.icon.AppIconProviderModule; +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProviderModule; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; import dagger.Binds; @@ -29,7 +30,7 @@ import javax.inject.Provider; /** * Dagger Module containing notification row and view inflation implementations. */ -@Module(includes = {AppIconProviderModule.class}) +@Module(includes = {AppIconProviderModule.class, NotificationIconStyleProviderModule.class}) public abstract class NotificationRowModule { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt new file mode 100644 index 000000000000..165c1a7803a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 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.row.icon + +import android.annotation.WorkerThread +import android.app.Flags +import android.content.Context +import android.content.pm.ApplicationInfo +import android.service.notification.StatusBarNotification +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import dagger.Module +import dagger.Provides +import javax.inject.Inject +import javax.inject.Provider + +/** + * A provider used to cache and fetch information about which icon should be displayed by + * notifications. + */ +interface NotificationIconStyleProvider { + @WorkerThread + fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean +} + +@SysUISingleton +class NotificationIconStyleProviderImpl @Inject constructor() : NotificationIconStyleProvider { + override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean { + val packageContext = notification.getPackageContext(context) + return !belongsToHeadlessSystemApp(packageContext) + } + + @WorkerThread + private fun belongsToHeadlessSystemApp(context: Context): Boolean { + val info = context.applicationInfo + if (info != null) { + if ((info.flags and ApplicationInfo.FLAG_SYSTEM) == 0) { + // It's not a system app at all. + return false + } else { + // If there's no launch intent, it's probably a headless app. + val pm = context.packageManager + return (pm.getLaunchIntentForPackage(info.packageName) == null) + } + } else { + // If for some reason we don't have the app info, we don't know; best assume it's + // not a system app. + return false + } + } +} + +class NoOpIconStyleProvider : NotificationIconStyleProvider { + companion object { + const val TAG = "NoOpIconStyleProvider" + } + + override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean { + Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.") + return true + } +} + +@Module +class NotificationIconStyleProviderModule { + @Provides + @SysUISingleton + fun provideImpl( + realImpl: Provider + ): NotificationIconStyleProvider = + if (Flags.notificationsRedesignAppIcons()) { + realImpl.get() + } else { + NoOpIconStyleProvider() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt index 2522e58a08a5..79defd255de0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt @@ -32,7 +32,10 @@ import javax.inject.Inject */ class NotificationRowIconViewInflaterFactory @Inject -constructor(private val appIconProvider: AppIconProvider) : NotifRemoteViewsFactory { +constructor( + private val appIconProvider: AppIconProvider, + private val iconStyleProvider: NotificationIconStyleProvider, +) : NotifRemoteViewsFactory { override fun instantiate( row: ExpandableNotificationRow, @NotificationRowContentBinder.InflationFlag layoutType: Int, @@ -48,8 +51,7 @@ constructor(private val appIconProvider: AppIconProvider) : NotifRemoteViewsFact view.setIconProvider( object : NotificationRowIconView.NotificationIconProvider { override fun shouldShowAppIcon(): Boolean { - // TODO(b/371174789): implement me - return true + return iconStyleProvider.shouldShowAppIcon(row.entry.sbn, context) } override fun getAppIcon(): Drawable { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt index a7a61957d104..7f4c670b05aa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt @@ -70,6 +70,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag import com.android.systemui.statusbar.notification.row.icon.AppIconProviderImpl +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProviderImpl import com.android.systemui.statusbar.notification.row.icon.NotificationRowIconViewInflaterFactory import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger @@ -287,7 +288,10 @@ class ExpandableNotificationRowBuilder( BigPictureLayoutInflaterFactory(), NotificationOptimizedLinearLayoutFactory(), { Mockito.mock(NotificationViewFlipperFactory::class.java) }, - NotificationRowIconViewInflaterFactory(AppIconProviderImpl(context)), + NotificationRowIconViewInflaterFactory( + AppIconProviderImpl(context), + NotificationIconStyleProviderImpl(), + ), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt new file mode 100644 index 000000000000..611c90a6f4e8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 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.row.icon + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.notificationIconStyleProvider by Kosmos.Fixture { NotificationIconStyleProviderImpl() } -- GitLab From abb0ac8dc18c6e3c27cebec7f23ec3004c4a7325 Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Tue, 15 Oct 2024 18:13:50 +0000 Subject: [PATCH 198/441] Add flag for considering flags in PackageCacher Flag: android.content.pm.include_feature_flags_in_package_cacher Bug: 364771256 Test: presubmit Change-Id: If4465d3ac81307ef528e4515f2b1b0406fee0566 --- core/java/android/content/pm/flags.aconfig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index c7d7dc1eb0de..52d733314eb6 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -326,3 +326,11 @@ flag { bug: "360129103" is_fixed_read_only: true } + +flag { + name: "include_feature_flags_in_package_cacher" + namespace: "package_manager_service" + description: "Include feature flag status when determining hits or misses in PackageCacher." + bug: "364771256" + is_fixed_read_only: true +} -- GitLab From 33342e211a9bff08a0e784dd34d9f80c1360aa3f Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 15 Oct 2024 09:59:57 -0400 Subject: [PATCH 199/441] Make bundle channel even more system reserved - Apps cannot post to these channels (no bypassing the notif permission, no diluting the content) - Don't show the notification runtime permission prompt if the only channels that exist are bundle channels (not a strong enough signal that the app intends to send notifications) Test: NotificationManagerServiceTest Test: PreferencesHelperTest Test: NotificationPermissionTest Test: NotificationManagerTest Fixes: 373476732 Flag: android.service.notification.notification_classification Change-Id: I4bdf475e83dfd174482f9ed239276a4e0cdee5fd --- .../NotificationManagerInternal.java | 2 +- .../NotificationManagerService.java | 40 ++++++++++++------- .../notification/PreferencesHelper.java | 6 ++- .../server/notification/RankingConfig.java | 2 +- .../NotificationManagerServiceTest.java | 26 +++++++++--- .../notification/PreferencesHelperTest.java | 30 +++++++++----- 6 files changed, 74 insertions(+), 32 deletions(-) diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index f3d6a2dd0a75..d5d4070ee4c3 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -44,7 +44,7 @@ public interface NotificationManagerInternal { void onConversationRemoved(String pkg, int uid, Set shortcuts); - /** Get the number of notification channels for a given package */ + /** Get the number of app created notification channels for a given package */ int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); /** Does the specified package/uid have permission to post notifications? */ diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 828e02ce3edc..62d762244617 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4392,8 +4392,9 @@ public class NotificationManagerService extends SystemService { List channels = channelsList.getList(); final int channelsSize = channels.size(); ParceledListSlice oldChannels = - mPreferencesHelper.getNotificationChannels(pkg, uid, true); - final boolean hadChannel = oldChannels != null && !oldChannels.getList().isEmpty(); + mPreferencesHelper.getNotificationChannels(pkg, uid, true, false); + final boolean hadNonBundleChannel = + oldChannels != null && !oldChannels.getList().isEmpty(); boolean needsPolicyFileChange = false; boolean hasRequestedNotificationPermission = false; for (int i = 0; i < channelsSize; i++) { @@ -4410,13 +4411,18 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false), NOTIFICATION_CHANNEL_OR_GROUP_ADDED); - boolean hasChannel = hadChannel || hasRequestedNotificationPermission; - if (!hasChannel) { + boolean hasNonBundleChannel = + hadNonBundleChannel || hasRequestedNotificationPermission; + if (!hasNonBundleChannel) { ParceledListSlice currChannels = - mPreferencesHelper.getNotificationChannels(pkg, uid, true); - hasChannel = currChannels != null && !currChannels.getList().isEmpty(); - } - if (!hadChannel && hasChannel && !hasRequestedNotificationPermission + mPreferencesHelper.getNotificationChannels(pkg, uid, true, false); + hasNonBundleChannel = + currChannels != null && !currChannels.getList().isEmpty(); + } + // show perm prompt if new non-bundle channel added and the user has not + // seen the prompt + if (!hadNonBundleChannel && hasNonBundleChannel + && !hasRequestedNotificationPermission && startingTaskId != ActivityTaskManager.INVALID_TASK_ID) { hasRequestedNotificationPermission = true; if (mPermissionPolicyInternal == null) { @@ -4651,7 +4657,7 @@ public class NotificationManagerService extends SystemService { public ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) { enforceSystemOrSystemUI("getNotificationChannelsForPackage"); - return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted); + return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted, true); } @Override @@ -4783,7 +4789,7 @@ public class NotificationManagerService extends SystemService { /* ignore */ } return mPreferencesHelper.getNotificationChannels( - targetPkg, targetUid, false /* includeDeleted */); + targetPkg, targetUid, false /* includeDeleted */, true); } throw new SecurityException("Pkg " + callingPkg + " cannot read channels for " + targetPkg + " in " + userId); @@ -6652,7 +6658,7 @@ public class NotificationManagerService extends SystemService { verifyPrivilegedListener(token, user, true); return mPreferencesHelper.getNotificationChannels(pkg, - getUidForPackageAndUser(pkg, user), false /* includeDeleted */); + getUidForPackageAndUser(pkg, user), false /* includeDeleted */, true); } @Override @@ -7693,8 +7699,9 @@ public class NotificationManagerService extends SystemService { } int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) { - return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted).getList() - .size(); + // don't show perm prompt if the only channels are bundle channels + return mPreferencesHelper.getNotificationChannels( + pkg, uid, includeDeleted, false).getList().size(); } void cancelNotificationInternal(String pkg, String opPkg, int callingUid, int callingPid, @@ -8005,11 +8012,16 @@ public class NotificationManagerService extends SystemService { } /** - * Returns a channel, if exists, and restores deleted conversation channels. + * Returns a channel, if exists and is not a bundle channel, and restores deleted + * conversation channels. */ @Nullable private NotificationChannel getNotificationChannelRestoreDeleted(String pkg, int callingUid, int notificationUid, String channelId, String conversationId) { + if (SYSTEM_RESERVED_IDS.contains(channelId)) { + // apps cannot post to these channels directly, in case they post incorrect content + return null; + } // Restore a deleted conversation channel, if exists. Otherwise use the parent channel. NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel( pkg, notificationUid, channelId, conversationId, diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 3349b1308c3f..c9edc4106943 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -1870,7 +1870,7 @@ public class PreferencesHelper implements RankingConfig { @Override public ParceledListSlice getNotificationChannels(String pkg, int uid, - boolean includeDeleted) { + boolean includeDeleted, boolean includeBundles) { Objects.requireNonNull(pkg); List channels = new ArrayList<>(); synchronized (mLock) { @@ -1882,7 +1882,9 @@ public class PreferencesHelper implements RankingConfig { for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); if (includeDeleted || !nc.isDeleted()) { - channels.add(nc); + if (includeBundles || !SYSTEM_RESERVED_IDS.contains(nc.getId())) { + channels.add(nc); + } } } return new ParceledListSlice<>(channels); diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 8df24c9911a6..001e91cbea12 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -55,5 +55,5 @@ public interface RankingConfig { void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannels(String pkg, int uid); ParceledListSlice getNotificationChannels(String pkg, int uid, - boolean includeDeleted); + boolean includeDeleted, boolean includeBundles); } 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 becb9fdabfcf..e845d80b412a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4838,7 +4838,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { null, mPkg, Process.myUserHandle()); verify(mPreferencesHelper, times(1)).getNotificationChannels( - anyString(), anyInt(), anyBoolean()); + anyString(), anyInt(), anyBoolean(), anyBoolean()); } @Test @@ -4856,7 +4856,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } verify(mPreferencesHelper, never()).getNotificationChannels( - anyString(), anyInt(), anyBoolean()); + anyString(), anyInt(), anyBoolean(), anyBoolean()); } @Test @@ -4871,7 +4871,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { null, mPkg, Process.myUserHandle()); verify(mPreferencesHelper, times(1)).getNotificationChannels( - anyString(), anyInt(), anyBoolean()); + anyString(), anyInt(), anyBoolean(), anyBoolean()); } @Test @@ -4891,7 +4891,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } verify(mPreferencesHelper, never()).getNotificationChannels( - anyString(), anyInt(), anyBoolean()); + anyString(), anyInt(), anyBoolean(), anyBoolean()); } @Test @@ -4913,7 +4913,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } verify(mPreferencesHelper, never()).getNotificationChannels( - anyString(), anyInt(), anyBoolean()); + anyString(), anyInt(), anyBoolean(), anyBoolean()); } @Test @@ -17062,4 +17062,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.hasFlag(captor.getValue().getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse(); } + + @Test + @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) + public void testAppCannotUseReservedBundleChannels() throws Exception { + mBinderService.getBubblePreferenceForPackage(mPkg, mUid); + NotificationChannel news = mBinderService.getNotificationChannel( + mPkg, mContext.getUserId(), mPkg, NEWS_ID); + assertThat(news).isNotNull(); + + NotificationRecord nr = generateNotificationRecord(news); + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + assertThat(mService.mNotificationList).isEmpty(); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 404ede676f02..b92bdb5f3e6e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -2547,7 +2547,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // Returns only non-deleted channels List channels = - mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList(); + mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true).getList(); // Default channel + non-deleted channel + system defaults assertEquals(notificationClassification() ? 6 : 2, channels.size()); for (NotificationChannel nc : channels) { @@ -2557,7 +2557,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { } // Returns deleted channels too - channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList(); + channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true, true).getList(); // Includes system channel(s) assertEquals(notificationClassification() ? 7 : 3, channels.size()); for (NotificationChannel nc : channels) { @@ -3199,7 +3199,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.permanentlyDeleteNotificationChannels(PKG_N_MR1, UID_N_MR1); // Only default channel remains - assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList().size()); + assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true, true) + .getList().size()); } @Test @@ -3315,12 +3316,12 @@ public class PreferencesHelperTest extends UiServiceTestCase { // user 0 records remain for (int i = 0; i < user0Uids.length; i++) { assertEquals(notificationClassification() ? 5 : 1, - mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false) + mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false, true) .getList().size()); } // user 1 records are gone for (int i = 0; i < user1Uids.length; i++) { - assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false) + assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false, true) .getList().size()); } } @@ -3337,7 +3338,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { new int[]{UID_N_MR1})); assertEquals(0, mHelper.getNotificationChannels( - PKG_N_MR1, UID_N_MR1, true).getList().size()); + PKG_N_MR1, UID_N_MR1, true, true).getList().size()); // Not deleted mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false, @@ -3346,7 +3347,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertFalse(mHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{UID_N_MR1})); assertEquals(notificationClassification() ? 6 : 2, - mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size()); + mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true) + .getList().size()); } @Test @@ -3405,7 +3407,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(mHelper.canShowBadge(PKG_O, UID_O)); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); - assertEquals(0, mHelper.getNotificationChannels(PKG_O, UID_O, true).getList().size()); + assertEquals(0, mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size()); assertEquals(0, mHelper.getNotificationChannelGroups(PKG_O, UID_O).size()); NotificationChannel channel = getChannel(); @@ -3419,7 +3421,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testRecordDefaults() throws Exception { assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); assertEquals(notificationClassification() ? 5 : 1, - mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size()); + mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true) + .getList().size()); } @Test @@ -6361,6 +6364,15 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(0, actual.getChannels().stream().filter(c -> c.getId().equals("id2")).count()); } + @Test + @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) + public void testGetNotificationChannels_omitBundleChannels() { + // do something that triggers settings creation for an app + mHelper.setShowBadge(PKG_O, UID_O, true); + + assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, false).getList()).isEmpty(); + } + @Test @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) public void testNotificationBundles() { -- GitLab From 1b5ea919ab3d44ce0558062c94ffab3b1ae39fd1 Mon Sep 17 00:00:00 2001 From: Robert Wu Date: Tue, 15 Oct 2024 18:37:25 +0000 Subject: [PATCH 200/441] Log uncaught exceptions in MidiService Make sure this exceptions are loud for crash console. Test: compiles Bug: 150808347 Flag: EXEMPT safe: system_server wtf only logs, doesn't abort Change-Id: I2657acc9f6ede78235ecfe71d8e5a78ecbbb36c4 --- services/midi/java/com/android/server/midi/MidiService.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index cc340c0a5f79..891c3349a43f 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -58,6 +58,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.EventLog; import android.util.Log; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; @@ -1737,6 +1738,11 @@ public class MidiService extends IMidiManager.Stub { pw.decreaseIndent(); } + @Override + protected void onUnhandledException(int code, int flags, Exception e) { + Slog.wtf(TAG, "Uncaught exception in AudioService: " + code + ", " + flags, e); + } + @GuardedBy("mUsbMidiLock") private boolean isUsbMidiDeviceInUseLocked(MidiDeviceInfo info) { String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME); -- GitLab From 824fb783cc5e05c46d2d98875ebe1d75e60c5fb7 Mon Sep 17 00:00:00 2001 From: Matt Casey Date: Tue, 15 Oct 2024 15:02:37 +0000 Subject: [PATCH 201/441] Catch NotFoundException in getDrawableForDensity Callers already handle a null result. NotFoundException is an expected exception that shouldn't cause a crash. Bug: 361803053 Test: None, just catching exception. Flag: EXEMPT minor bugfix Change-Id: I19bb1d6e20ce98eb9af587b10ae82e9b52bc561d --- .../java/com/android/internal/app/ResolverListAdapter.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 18c8eb4ec46b..de7ad346a7cd 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -1195,7 +1195,12 @@ public class ResolverListAdapter extends BaseAdapter { @Nullable protected Drawable loadIconFromResource(Resources res, int resId) { - return res.getDrawableForDensity(resId, mIconDpi); + try { + return res.getDrawableForDensity(resId, mIconDpi); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Resource not found", e); + return null; + } } } -- GitLab From 0409fcd7419cdb7c3a5cfb70137a46cc3e1195d9 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Mon, 14 Oct 2024 20:19:16 +0000 Subject: [PATCH 202/441] [SB][Notifs] status_bar_ron_chips flag -> status_bar_notification_chips. Removes "RON" from the flag name and instead makes the flag name more generic. Also updates all the "demo RON" code to be "demo notification" instead. Bug: 364653005 Flag: com.android.systemui.status_bar_notification_chips Test: all tests under statusbar package Change-Id: I9174b97369917c33b0dde35a4ff99fb12729df8a --- packages/SystemUI/aconfig/systemui.aconfig | 4 +- .../viewmodel/DemoNotifChipViewModelTest.kt} | 42 +++++++-------- .../ui/viewmodel/NotifChipsViewModelTest.kt | 5 +- .../OngoingActivityChipsViewModelTest.kt | 16 +++--- ...ngActivityChipsWithNotifsViewModelTest.kt} | 52 +++++++++---------- .../statusbar/chips/StatusBarChipsModule.kt | 6 +-- .../ui/viewmodel/DemoNotifChipViewModel.kt} | 37 ++++++------- .../shared/StatusBarNotifChips.kt} | 10 ++-- .../ui/binder/OngoingActivityChipBinder.kt | 4 +- .../ui/model/OngoingActivityChipModel.kt | 7 +-- .../OngoingActivityChipsViewModel.kt | 48 ++++++++--------- .../ActiveNotificationsInteractor.kt | 4 +- .../fragment/CollapsedStatusBarFragment.java | 8 +-- .../ui/binder/CollapsedStatusBarViewBinder.kt | 5 +- .../CollapsedStatusBarFragmentTest.java | 52 +++++++++---------- .../DemoNotifChipViewModelKosmos.kt} | 6 +-- .../ui/viewmodel/NotifChipsViewModelKosmos.kt | 3 +- .../OngoingActivityChipsViewModelKosmos.kt | 6 +-- 18 files changed, 154 insertions(+), 161 deletions(-) rename packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/{ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt => notification/demo/ui/viewmodel/DemoNotifChipViewModelTest.kt} (74%) rename packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/{OngoingActivityChipsWithRonsViewModelTest.kt => OngoingActivityChipsWithNotifsViewModelTest.kt} (94%) rename packages/SystemUI/src/com/android/systemui/statusbar/chips/{ron/demo/ui/viewmodel/DemoRonChipViewModel.kt => notification/demo/ui/viewmodel/DemoNotifChipViewModel.kt} (83%) rename packages/SystemUI/src/com/android/systemui/statusbar/chips/{ron/shared/StatusBarRonChips.kt => notification/shared/StatusBarNotifChips.kt} (87%) rename packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/{ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt => notification/demo/ui/viewmodel/DemoNotifChipViewModelKosmos.kt} (85%) rename packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/{ron => notification}/ui/viewmodel/NotifChipsViewModelKosmos.kt (85%) diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a21a80506279..0138b5cdfd3f 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -395,9 +395,9 @@ flag { } flag { - name: "status_bar_ron_chips" + name: "status_bar_notification_chips" namespace: "systemui" - description: "Show rich ongoing notifications as chips in the status bar" + description: "Show promoted ongoing notifications as chips in the status bar" bug: "361346412" } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelTest.kt similarity index 74% rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelTest.kt index 118dea6376bb..69a76271f726 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelTest.kt @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel +package com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel import android.content.packageManager import android.graphics.drawable.BitmapDrawable import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.commandline.CommandRegistry @@ -40,13 +40,13 @@ import org.mockito.kotlin.any import org.mockito.kotlin.whenever @SmallTest -class DemoRonChipViewModelTest : SysuiTestCase() { +class DemoNotifChipViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val commandRegistry = kosmos.commandRegistry private val pw = PrintWriter(StringWriter()) - private val underTest = kosmos.demoRonChipViewModel + private val underTest = kosmos.demoNotifChipViewModel @Before fun setUp() { @@ -56,61 +56,61 @@ class DemoRonChipViewModelTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_flagOff_hidden() = testScope.runTest { val latest by collectLastValue(underTest.chip) - addDemoRonChip() + addDemoNotifChip() assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } @Test - @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_noPackage_hidden() = testScope.runTest { val latest by collectLastValue(underTest.chip) - commandRegistry.onShellCommand(pw, arrayOf("demo-ron")) + commandRegistry.onShellCommand(pw, arrayOf("demo-notif")) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } @Test - @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_hasPackage_shown() = testScope.runTest { val latest by collectLastValue(underTest.chip) - commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui")) + commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "-p", "com.android.systemui")) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) } @Test - @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_hasText_shownWithText() = testScope.runTest { val latest by collectLastValue(underTest.chip) commandRegistry.onShellCommand( pw, - arrayOf("demo-ron", "-p", "com.android.systemui", "-t", "test") + arrayOf("demo-notif", "-p", "com.android.systemui", "-t", "test"), ) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Text::class.java) } @Test - @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_supportsColor() = testScope.runTest { val latest by collectLastValue(underTest.chip) commandRegistry.onShellCommand( pw, - arrayOf("demo-ron", "-p", "com.android.systemui", "-c", "#434343") + arrayOf("demo-notif", "-p", "com.android.systemui", "-c", "#434343"), ) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) @@ -119,28 +119,28 @@ class DemoRonChipViewModelTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_hasHideArg_hidden() = testScope.runTest { val latest by collectLastValue(underTest.chip) // First, show a chip - addDemoRonChip() + addDemoNotifChip() assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) // Then, hide the chip - commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "--hide")) + commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "--hide")) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } - private fun addDemoRonChip() { - Companion.addDemoRonChip(commandRegistry, pw) + private fun addDemoNotifChip() { + addDemoNotifChip(commandRegistry, pw) } companion object { - fun addDemoRonChip(commandRegistry: CommandRegistry, pw: PrintWriter) { - commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui")) + fun addDemoNotifChip(commandRegistry: CommandRegistry, pw: PrintWriter) { + commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "-p", "com.android.systemui")) } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 2872900e01a5..eb5d9318c88f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -19,12 +19,11 @@ package com.android.systemui.statusbar.chips.notification.ui.viewmodel import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.chips.ron.ui.viewmodel.notifChipsViewModel +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore @@ -42,7 +41,7 @@ import org.mockito.kotlin.mock @SmallTest @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) -@EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) +@EnableFlags(StatusBarNotifChips.FLAG_NAME) class NotifChipsViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index 26ce7b956fde..e96def6d43a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -25,7 +25,6 @@ import android.platform.test.annotations.DisableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue @@ -40,7 +39,8 @@ import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.phone.SystemUIDialog @@ -66,13 +66,11 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -/** - * Tests for [OngoingActivityChipsViewModel] when the [FLAG_STATUS_BAR_RON_CHIPS] flag is disabled. - */ +/** Tests for [OngoingActivityChipsViewModel] when the [StatusBarNotifChips] flag is disabled. */ @SmallTest @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) -@DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) +@DisableFlags(StatusBarNotifChips.FLAG_NAME) class OngoingActivityChipsViewModelTest : SysuiTestCase() { private val kosmos = Kosmos().also { it.testCase = this } private val testScope = kosmos.testScope @@ -99,11 +97,11 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Before fun setUp() { setUpPackageManagerForMediaProjection(kosmos) - kosmos.demoRonChipViewModel.start() + kosmos.demoNotifChipViewModel.start() val icon = BitmapDrawable( context.resources, - Bitmap.createBitmap(/* width= */ 100, /* height= */ 100, Bitmap.Config.ARGB_8888) + Bitmap.createBitmap(/* width= */ 100, /* height= */ 100, Bitmap.Config.ARGB_8888), ) whenever(kosmos.packageManager.getApplicationIcon(any())).thenReturn(icon) } @@ -325,7 +323,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { latest: OngoingActivityChipModel?, chipView: View, dialog: SystemUIDialog, - kosmos: Kosmos + kosmos: Kosmos, ): DialogInterface.OnClickListener { // Capture the action that would get invoked when the user clicks "Stop" on the dialog lateinit var dialogStopAction: DialogInterface.OnClickListener diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt similarity index 94% rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index c5b857fc2b80..b12d7c57e1fd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -24,7 +24,6 @@ import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope @@ -37,9 +36,10 @@ import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModelTest.Companion.addDemoNotifChip +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModelTest.Companion.addDemoRonChip -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer @@ -73,14 +73,12 @@ import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.whenever -/** - * Tests for [OngoingActivityChipsViewModel] when the [FLAG_STATUS_BAR_RON_CHIPS] flag is enabled. - */ +/** Tests for [OngoingActivityChipsViewModel] when the [StatusBarNotifChips] flag is enabled. */ @SmallTest @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) -@EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) -class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { +@EnableFlags(StatusBarNotifChips.FLAG_NAME) +class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val systemClock = kosmos.fakeSystemClock @@ -110,7 +108,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { @Before fun setUp() { setUpPackageManagerForMediaProjection(kosmos) - kosmos.demoRonChipViewModel.start() + kosmos.demoNotifChipViewModel.start() val icon = BitmapDrawable( context.resources, @@ -119,7 +117,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { whenever(kosmos.packageManager.getApplicationIcon(any())).thenReturn(icon) } - // Even though the `primaryChip` flow isn't used when the RONs flag is on, still test that the + // Even though the `primaryChip` flow isn't used when the notifs flag is on, still test that the // flow has the right behavior to verify that we don't break any existing functionality. @Test @@ -256,13 +254,13 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { testScope.runTest { screenRecordState.value = ScreenRecordModel.Recording callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) - addDemoRonChip(commandRegistry, pw) + addDemoNotifChip(commandRegistry, pw) val latest by collectLastValue(underTest.chips) assertIsScreenRecordChip(latest!!.primary) assertIsCallChip(latest!!.secondary) - // Demo RON chip is dropped + // Demo notif chip is dropped } @Test @@ -389,7 +387,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() = testScope.runTest { // Start with just the lowest priority chip shown - addDemoRonChip(commandRegistry, pw) + addDemoNotifChip(commandRegistry, pw) // And everything else hidden callRepo.setOngoingCallState(OngoingCallModel.NoCall) mediaProjectionState.value = MediaProjectionState.NotProjecting @@ -397,7 +395,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.primaryChip) - assertIsDemoRonChip(latest) + assertIsDemoNotifChip(latest) // WHEN the higher priority call chip is added callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) @@ -431,7 +429,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) - addDemoRonChip(commandRegistry, pw) + addDemoNotifChip(commandRegistry, pw) val latest by collectLastValue(underTest.primaryChip) @@ -453,15 +451,15 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { // WHEN the higher priority call is removed callRepo.setOngoingCallState(OngoingCallModel.NoCall) - // THEN the lower priority demo RON is used - assertIsDemoRonChip(latest) + // THEN the lower priority demo notif is used + assertIsDemoNotifChip(latest) } @Test fun chips_movesChipsAroundAccordingToPriority() = testScope.runTest { // Start with just the lowest priority chip shown - addDemoRonChip(commandRegistry, pw) + addDemoNotifChip(commandRegistry, pw) // And everything else hidden callRepo.setOngoingCallState(OngoingCallModel.NoCall) mediaProjectionState.value = MediaProjectionState.NotProjecting @@ -469,16 +467,16 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.chips) - assertIsDemoRonChip(latest!!.primary) + assertIsDemoNotifChip(latest!!.primary) assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) // WHEN the higher priority call chip is added callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) - // THEN the higher priority call chip is used as primary and demo ron is demoted to + // THEN the higher priority call chip is used as primary and demo notif is demoted to // secondary assertIsCallChip(latest!!.primary) - assertIsDemoRonChip(latest!!.secondary) + assertIsDemoNotifChip(latest!!.secondary) // WHEN the higher priority media projection chip is added mediaProjectionState.value = @@ -489,7 +487,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { ) // THEN the higher priority media projection chip is used as primary and call is demoted - // to secondary (and demo RON is dropped altogether) + // to secondary (and demo notif is dropped altogether) assertIsShareToAppChip(latest!!.primary) assertIsCallChip(latest!!.secondary) @@ -503,15 +501,15 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { screenRecordState.value = ScreenRecordModel.DoingNothing callRepo.setOngoingCallState(OngoingCallModel.NoCall) - // THEN media projection and demo RON remain + // THEN media projection and demo notif remain assertIsShareToAppChip(latest!!.primary) - assertIsDemoRonChip(latest!!.secondary) + assertIsDemoNotifChip(latest!!.secondary) // WHEN media projection is dropped mediaProjectionState.value = MediaProjectionState.NotProjecting - // THEN demo RON is promoted to primary - assertIsDemoRonChip(latest!!.primary) + // THEN demo notif is promoted to primary + assertIsDemoNotifChip(latest!!.primary) assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } @@ -624,7 +622,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false)) } - private fun assertIsDemoRonChip(latest: OngoingActivityChipModel?) { + private fun assertIsDemoNotifChip(latest: OngoingActivityChipModel?) { assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) assertThat((latest as OngoingActivityChipModel.Shown).icon) .isInstanceOf(OngoingActivityChipModel.ChipIcon.FullColorAppIcon::class.java) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt index be733d4e1e8e..8ce0dbf8e171 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt @@ -20,7 +20,7 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModel import dagger.Binds import dagger.Module import dagger.Provides @@ -31,8 +31,8 @@ import dagger.multibindings.IntoMap abstract class StatusBarChipsModule { @Binds @IntoMap - @ClassKey(DemoRonChipViewModel::class) - abstract fun binds(impl: DemoRonChipViewModel): CoreStartable + @ClassKey(DemoNotifChipViewModel::class) + abstract fun binds(impl: DemoNotifChipViewModel): CoreStartable companion object { @Provides diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModel.kt similarity index 83% rename from packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModel.kt index cce9a1624d51..5fa19ddef1be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel +package com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel import android.content.pm.PackageManager import android.content.pm.PackageManager.NameNotFoundException @@ -22,7 +22,7 @@ import android.graphics.drawable.Drawable import com.android.systemui.CoreStartable import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel @@ -37,25 +37,25 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** - * A view model that will emit demo RON chips (rich ongoing notification chips) from [chip] based on - * adb commands sent by the user. + * A view model that will emit demo promoted ongoing notification chips from [chip] based on adb + * commands sent by the user. * * Example adb commands: * * To show a chip with the SysUI icon and custom text and color: * ``` - * adb shell cmd statusbar demo-ron -p com.android.systemui -t 10min -c "\\#434343" + * adb shell cmd statusbar demo-notif -p com.android.systemui -t 10min -c "\\#434343" * ``` * * To hide the chip: * ``` - * adb shell cmd statusbar demo-ron --hide + * adb shell cmd statusbar demo-notif --hide * ``` * - * See [DemoRonCommand] for more information on the adb command spec. + * See [DemoNotifCommand] for more information on the adb command spec. */ @SysUISingleton -class DemoRonChipViewModel +class DemoNotifChipViewModel @Inject constructor( private val commandRegistry: CommandRegistry, @@ -63,19 +63,19 @@ constructor( private val systemClock: SystemClock, ) : OngoingActivityChipViewModel, CoreStartable { override fun start() { - commandRegistry.registerCommand("demo-ron") { DemoRonCommand() } + commandRegistry.registerCommand(DEMO_COMMAND_NAME) { DemoNotifCommand() } } private val _chip = MutableStateFlow(OngoingActivityChipModel.Hidden()) override val chip: StateFlow = _chip.asStateFlow() - private inner class DemoRonCommand : ParseableCommand("demo-ron") { + private inner class DemoNotifCommand : ParseableCommand(DEMO_COMMAND_NAME) { private val packageName: String? by param( longName = "packageName", shortName = "p", - description = "The package name for the demo RON app", + description = "The package name for app \"posting\" the demo notification", valueParser = Type.String, ) @@ -99,15 +99,12 @@ constructor( ) private val hide by - flag( - longName = "hide", - description = "Hides any existing demo RON chip", - ) + flag(longName = "hide", description = "Hides any existing demo notification chip") override fun execute(pw: PrintWriter) { - if (!StatusBarRonChips.isEnabled) { + if (!StatusBarNotifChips.isEnabled) { pw.println( - "Error: com.android.systemui.status_bar_ron_chips must be enabled " + + "Error: com.android.systemui.status_bar_notification_chips must be enabled " + "before using this demo feature" ) return @@ -167,8 +164,12 @@ constructor( return null } return OngoingActivityChipModel.ChipIcon.FullColorAppIcon( - Icon.Loaded(drawable = iconDrawable, contentDescription = null), + Icon.Loaded(drawable = iconDrawable, contentDescription = null) ) } } + + companion object { + private const val DEMO_COMMAND_NAME = "demo-notif" + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt similarity index 87% rename from packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt index 4ef190991f19..47ffbafe3735 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.ron.shared +package com.android.systemui.statusbar.chips.notification.shared import com.android.systemui.Flags import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils -/** Helper for reading or using the status bar RON chips flag state. */ +/** Helper for reading or using the status bar promoted notification chips flag state. */ @Suppress("NOTHING_TO_INLINE") -object StatusBarRonChips { +object StatusBarNotifChips { /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_STATUS_BAR_RON_CHIPS + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_NOTIFICATION_CHIPS /** A token used for dependency declaration */ val token: FlagToken @@ -33,7 +33,7 @@ object StatusBarRonChips { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.statusBarRonChips() + get() = Flags.statusBarNotificationChips() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt index 2220caab00f3..f4462a434477 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt @@ -28,7 +28,7 @@ import android.widget.TextView import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.view.ChipChronometer @@ -102,7 +102,7 @@ object OngoingActivityChipBinder { defaultIconView.tintView(iconTint) } is OngoingActivityChipModel.ChipIcon.FullColorAppIcon -> { - StatusBarRonChips.assertInNewMode() + StatusBarNotifChips.assertInNewMode() IconViewBinder.bind(icon.impl, defaultIconView) defaultIconView.visibility = View.VISIBLE defaultIconView.untintView() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index 2366572f25e3..cf07af1fc5dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -20,7 +20,7 @@ import android.view.View import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips /** Model representing the display of an ongoing activity as a chip in the status bar. */ sealed class OngoingActivityChipModel { @@ -91,10 +91,7 @@ sealed class OngoingActivityChipModel { override val onClickListener: View.OnClickListener?, ) : Shown(icon, colors, onClickListener) { init { - check(StatusBarRonChips.isEnabled) { - "OngoingActivityChipModel.Shown.ShortTimeDelta created even though " + - "Flags.statusBarRonChips is not enabled" - } + StatusBarNotifChips.assertInNewMode() } override val logName = "Shown.ShortTimeDelta" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt index 954386e3259f..ed325970ebb2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.chips.ui.viewmodel -import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer @@ -24,9 +23,9 @@ import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModel import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModel +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModel -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel -import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel @@ -57,7 +56,7 @@ constructor( castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel, callChipViewModel: CallChipViewModel, notifChipsViewModel: NotifChipsViewModel, - demoRonChipViewModel: DemoRonChipViewModel, + demoNotifChipViewModel: DemoNotifChipViewModel, @StatusBarChipsLog private val logger: LogBuffer, ) { private enum class ChipType { @@ -66,8 +65,8 @@ constructor( CastToOtherDevice, Call, Notification, - /** A demo of a RON chip (rich ongoing notification chip), used just for testing. */ - DemoRon, + /** A demo of a notification chip, used just for testing. */ + DemoNotification, } /** Model that helps us internally track the various chip states from each of the types. */ @@ -89,7 +88,7 @@ constructor( val castToOtherDevice: OngoingActivityChipModel.Hidden, val call: OngoingActivityChipModel.Hidden, val notifs: OngoingActivityChipModel.Hidden, - val demoRon: OngoingActivityChipModel.Hidden, + val demoNotif: OngoingActivityChipModel.Hidden, ) : InternalChipModel } @@ -99,7 +98,7 @@ constructor( val castToOtherDevice: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(), val call: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(), val notifs: List = emptyList(), - val demoRon: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(), + val demoNotif: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(), ) /** Bundles all the incoming chips into one object to easily pass to various flows. */ @@ -110,8 +109,8 @@ constructor( castToOtherDeviceChipViewModel.chip, callChipViewModel.chip, notifChipsViewModel.chips, - demoRonChipViewModel.chip, - ) { screenRecord, shareToApp, castToOtherDevice, call, notifs, demoRon -> + demoNotifChipViewModel.chip, + ) { screenRecord, shareToApp, castToOtherDevice, call, notifs, demoNotif -> logger.log( TAG, LogLevel.INFO, @@ -129,9 +128,9 @@ constructor( str1 = call.logName // TODO(b/364653005): Log other information for notification chips. str2 = notifs.map { it.logName }.toString() - str3 = demoRon.logName + str3 = demoNotif.logName }, - { "... > Call=$str1 > Notifs=$str2 > DemoRon=$str3" }, + { "... > Call=$str1 > Notifs=$str2 > DemoNotif=$str3" }, ) ChipBundle( screenRecord = screenRecord, @@ -139,7 +138,7 @@ constructor( castToOtherDevice = castToOtherDevice, call = call, notifs = notifs, - demoRon = demoRon, + demoNotif = demoNotif, ) } // Some of the chips could have timers in them and we don't want the start time @@ -198,9 +197,9 @@ constructor( * actually displaying the chip. */ val chips: StateFlow = - if (!Flags.statusBarRonChips()) { - // Multiple chips are only allowed with RONs. If the flag isn't on, use just the - // primary chip. + if (!StatusBarNotifChips.isEnabled) { + // Multiple chips are only allowed with notification chips. If the flag isn't on, use + // just the primary chip. primaryChip .map { MultipleOngoingActivityChipsModel( @@ -282,11 +281,12 @@ constructor( remainingChips = bundle.copy(notifs = bundle.notifs.subList(1, bundle.notifs.size)), ) - bundle.demoRon is OngoingActivityChipModel.Shown -> { - StatusBarRonChips.assertInNewMode() + bundle.demoNotif is OngoingActivityChipModel.Shown -> { + StatusBarNotifChips.assertInNewMode() MostImportantChipResult( - mostImportantChip = InternalChipModel.Shown(ChipType.DemoRon, bundle.demoRon), - remainingChips = bundle.copy(demoRon = OngoingActivityChipModel.Hidden()), + mostImportantChip = + InternalChipModel.Shown(ChipType.DemoNotification, bundle.demoNotif), + remainingChips = bundle.copy(demoNotif = OngoingActivityChipModel.Hidden()), ) } else -> { @@ -296,7 +296,7 @@ constructor( check(bundle.castToOtherDevice is OngoingActivityChipModel.Hidden) check(bundle.call is OngoingActivityChipModel.Hidden) check(bundle.notifs.isEmpty()) - check(bundle.demoRon is OngoingActivityChipModel.Hidden) + check(bundle.demoNotif is OngoingActivityChipModel.Hidden) MostImportantChipResult( mostImportantChip = InternalChipModel.Hidden( @@ -305,7 +305,7 @@ constructor( castToOtherDevice = bundle.castToOtherDevice, call = bundle.call, notifs = OngoingActivityChipModel.Hidden(), - demoRon = bundle.demoRon, + demoNotif = bundle.demoNotif, ), // All the chips are already hidden, so no need to filter anything out of the // bundle. @@ -334,7 +334,7 @@ constructor( ChipType.CastToOtherDevice -> new.castToOtherDevice ChipType.Call -> new.call ChipType.Notification -> new.notifs - ChipType.DemoRon -> new.demoRon + ChipType.DemoNotification -> new.demoNotif } } else if (new is InternalChipModel.Shown) { // If we have a chip to show, always show it. @@ -356,7 +356,7 @@ constructor( castToOtherDevice = OngoingActivityChipModel.Hidden(), call = OngoingActivityChipModel.Hidden(), notifs = OngoingActivityChipModel.Hidden(), - demoRon = OngoingActivityChipModel.Hidden(), + demoNotif = OngoingActivityChipModel.Hidden(), ) private val DEFAULT_MULTIPLE_INTERNAL_HIDDEN_MODEL = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 5900fb034b4b..697a6ce52ba9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel @@ -78,7 +78,7 @@ constructor( /** The notifications that are promoted and ongoing. Sorted by priority order. */ val promotedOngoingNotifications: Flow> = - if (StatusBarRonChips.isEnabled) { + if (StatusBarNotifChips.isEnabled) { // TODO(b/364653005): Filter all the notifications down to just the promoted ones. // TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow // instead of being separate. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index c258095510c6..35e30b8134bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -55,7 +55,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameView; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips; +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.core.StatusBarSimpleFragment; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; @@ -642,7 +642,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } boolean showSecondaryOngoingActivityChip = Flags.statusBarScreenSharingChips() - && StatusBarRonChips.isEnabled() + && StatusBarNotifChips.isEnabled() && mHasSecondaryOngoingActivity; return new StatusBarVisibilityModel( @@ -684,7 +684,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue boolean showSecondaryOngoingActivityChip = // Secondary chips are only supported when RONs are enabled. - StatusBarRonChips.isEnabled() + StatusBarNotifChips.isEnabled() && visibilityModel.getShowSecondaryOngoingActivityChip() && !disableNotifications; if (showSecondaryOngoingActivityChip) { @@ -811,7 +811,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void showSecondaryOngoingActivityChip(boolean animate) { - StatusBarRonChips.assertInNewMode(); + StatusBarNotifChips.assertInNewMode(); StatusBarSimpleFragment.assertInLegacyMode(); animateShow(mSecondaryOngoingActivityChip, animate); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt index 9c168be0693f..3a07d9b6beaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarSimpleFragment @@ -83,7 +84,7 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa } } - if (Flags.statusBarScreenSharingChips() && !Flags.statusBarRonChips()) { + if (Flags.statusBarScreenSharingChips() && !StatusBarNotifChips.isEnabled) { val primaryChipView: View = view.requireViewById(R.id.ongoing_activity_chip_primary) launch { @@ -119,7 +120,7 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa } } - if (Flags.statusBarScreenSharingChips() && Flags.statusBarRonChips()) { + if (Flags.statusBarScreenSharingChips() && StatusBarNotifChips.isEnabled) { val primaryChipView: View = view.requireViewById(R.id.ongoing_activity_chip_primary) val secondaryChipView: View = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index e57e8d108529..15ef917343ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone.fragment; import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS; import static com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS; import static com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; @@ -61,6 +60,7 @@ import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder; @@ -633,7 +633,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, - FLAG_STATUS_BAR_RON_CHIPS, + StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void hasPrimaryOngoingActivity_viewsUnchangedWhenSimpleFragmentFlagOn() { resumeAndGetFragment(); @@ -660,8 +660,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void hasSecondaryOngoingActivity_butRonsFlagOff_secondaryChipHidden() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() { resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -673,7 +673,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() { resumeAndGetFragment(); @@ -689,8 +689,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_ronsFlagOff() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -705,9 +705,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_ronsFlagOn() { + public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -724,8 +724,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void hasOngoingActivityButAlsoHun_chipHidden_ronsFlagOff() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -740,9 +740,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - public void hasOngoingActivitiesButAlsoHun_chipsHidden_ronsFlagOn() { + public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -759,8 +759,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void primaryOngoingActivityEnded_chipHidden_ronsFlagOff() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() { resumeAndGetFragment(); // Ongoing activity started @@ -781,9 +781,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - public void primaryOngoingActivityEnded_chipHidden_ronsFlagOn() { + public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() { resumeAndGetFragment(); // Ongoing activity started @@ -804,7 +804,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void secondaryOngoingActivityEnded_chipHidden() { resumeAndGetFragment(); @@ -828,8 +828,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOff() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating fragment.enableAnimationsForTesting(); @@ -846,9 +846,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOn() { + public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating fragment.enableAnimationsForTesting(); @@ -866,8 +866,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOff() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN there *is* an ongoing call via old callback @@ -898,9 +898,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOn() { + public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN there *is* an ongoing call via old callback diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelKosmos.kt similarity index 85% rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelKosmos.kt index c0d65a076ca0..2316a2fdcd2b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelKosmos.kt @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel +package com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel import android.content.packageManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.commandline.commandRegistry import com.android.systemui.util.time.fakeSystemClock -val Kosmos.demoRonChipViewModel: DemoRonChipViewModel by +val Kosmos.demoNotifChipViewModel: DemoNotifChipViewModel by Kosmos.Fixture { - DemoRonChipViewModel( + DemoNotifChipViewModel( commandRegistry = commandRegistry, packageManager = packageManager, systemClock = fakeSystemClock, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt similarity index 85% rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/ui/viewmodel/NotifChipsViewModelKosmos.kt rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt index 11d1cb7e56c8..af24c371d62b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/ui/viewmodel/NotifChipsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.ron.ui.viewmodel +package com.android.systemui.statusbar.chips.notification.ui.viewmodel import com.android.systemui.kosmos.Kosmos -import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModel import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor val Kosmos.notifChipsViewModel: NotifChipsViewModel by diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt index b2be0b21964b..0300bf4636ea 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt @@ -20,8 +20,8 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.chips.call.ui.viewmodel.callChipViewModel import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.castToOtherDeviceChipViewModel -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel -import com.android.systemui.statusbar.chips.ron.ui.viewmodel.notifChipsViewModel +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel +import com.android.systemui.statusbar.chips.notification.ui.viewmodel.notifChipsViewModel import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.screenRecordChipViewModel import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel import com.android.systemui.statusbar.chips.statusBarChipsLogger @@ -35,7 +35,7 @@ val Kosmos.ongoingActivityChipsViewModel: OngoingActivityChipsViewModel by castToOtherDeviceChipViewModel = castToOtherDeviceChipViewModel, callChipViewModel = callChipViewModel, notifChipsViewModel = notifChipsViewModel, - demoRonChipViewModel = demoRonChipViewModel, + demoNotifChipViewModel = demoNotifChipViewModel, logger = statusBarChipsLogger, ) } -- GitLab From aec7dbd2e7f30bb87d42fa6354c53dcc645b6f56 Mon Sep 17 00:00:00 2001 From: Lucas Silva Date: Thu, 10 Oct 2024 10:08:27 -0400 Subject: [PATCH 203/441] Update drag-and-drop to support resizeable widgets We updated the hit-detection to consider if the dragging item contains the center of any other item, instead of the other way around. We also keep track of the previous target item to prevent rapidly oscillating between two items. Bug: 368056271 Test: UI update, manually on the device by dragging items of different sizes Flag: com.android.systemui.communal_widget_resizing Change-Id: Ibf0c24cfde7a098d305443dd79c729d098ecbc42 --- .../communal/ui/compose/CommunalHub.kt | 4 +- .../communal/ui/compose/GridDragDropState.kt | 67 +++++++++++++------ 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 7fb4c537641b..a9c19e27b216 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -766,7 +766,7 @@ private fun BoxScope.CommunalHubLazyGrid( alpha = { outlineAlpha }, modifier = Modifier.requiredSize(dpSize).thenIf( - dragDropState.draggingItemIndex != index + dragDropState.draggingItemKey != item.key ) { Modifier.animateItem( placementSpec = spring(stiffness = Spring.StiffnessMediumLow) @@ -779,7 +779,7 @@ private fun BoxScope.CommunalHubLazyGrid( dragDropState = dragDropState, selected = selected, enabled = item.isWidgetContent(), - index = index, + key = item.key, ) { isDragging -> CommunalContent( modifier = Modifier.fillMaxSize(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt index 101385f88825..0718bc331d41 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -38,8 +38,9 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.round import androidx.compose.ui.unit.toOffset import androidx.compose.ui.unit.toSize import com.android.systemui.Flags.communalWidgetResizing @@ -93,7 +94,7 @@ internal constructor( private val scope: CoroutineScope, private val updateDragPositionForRemove: (offset: Offset) -> Boolean, ) { - var draggingItemIndex by mutableStateOf(null) + var draggingItemKey by mutableStateOf(null) private set var isDraggingToRemove by mutableStateOf(false) @@ -105,6 +106,8 @@ internal constructor( private var draggingItemInitialOffset by mutableStateOf(Offset.Zero) private var dragStartPointerOffset by mutableStateOf(Offset.Zero) + private var previousTargetItemKey: Any? = null + internal val draggingItemOffset: Offset get() = draggingItemLayoutInfo?.let { item -> @@ -112,7 +115,7 @@ internal constructor( } ?: Offset.Zero private val draggingItemLayoutInfo: LazyGridItemInfo? - get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex } + get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.key == draggingItemKey } /** * Called when dragging is initiated. @@ -137,7 +140,7 @@ internal constructor( .firstItemAtOffset(normalizedOffset - contentOffset) ?.apply { dragStartPointerOffset = normalizedOffset - this.offset.toOffset() - draggingItemIndex = index + draggingItemKey = key draggingItemInitialOffset = this.offset.toOffset() return true } @@ -146,16 +149,19 @@ internal constructor( } internal fun onDragInterrupted() { - draggingItemIndex?.let { + draggingItemKey?.let { if (isDraggingToRemove) { - contentListState.onRemove(it) + contentListState.onRemove( + contentListState.list.indexOfFirst { it.key == draggingItemKey } + ) isDraggingToRemove = false updateDragPositionForRemove(Offset.Zero) } // persist list editing changes on dragging ends contentListState.onSaveList() - draggingItemIndex = null + draggingItemKey = null } + previousTargetItemKey = null draggingItemDraggedDelta = Offset.Zero draggingItemInitialOffset = Offset.Zero dragStartPointerOffset = Offset.Zero @@ -170,15 +176,29 @@ internal constructor( val startOffset = draggingItem.offset.toOffset() + draggingItemOffset val endOffset = startOffset + draggingItem.size.toSize() val middleOffset = startOffset + (endOffset - startOffset) / 2f + val draggingBoundingBox = + IntRect(draggingItem.offset + draggingItemOffset.round(), draggingItem.size) val targetItem = - state.layoutInfo.visibleItemsInfo - .asSequence() - .filter { item -> contentListState.isItemEditable(item.index) } - .filter { item -> draggingItem.index != item.index } - .firstItemAtOffset(middleOffset) + if (communalWidgetResizing()) { + state.layoutInfo.visibleItemsInfo.findLast { item -> + val itemBoundingBox = IntRect(item.offset, item.size) + draggingItemKey != item.key && + contentListState.isItemEditable(item.index) && + draggingBoundingBox.contains(itemBoundingBox.center) + } + } else { + state.layoutInfo.visibleItemsInfo + .asSequence() + .filter { item -> contentListState.isItemEditable(item.index) } + .filter { item -> draggingItem.index != item.index } + .firstItemAtOffset(middleOffset) + } - if (targetItem != null) { + if ( + targetItem != null && + (!communalWidgetResizing() || targetItem.key != previousTargetItemKey) + ) { val scrollToIndex = if (targetItem.index == state.firstVisibleItemIndex) { draggingItem.index @@ -187,6 +207,14 @@ internal constructor( } else { null } + if (communalWidgetResizing()) { + // Keep track of the previous target item, to avoid rapidly oscillating between + // items if the target item doesn't visually move as a result of the index change. + // In this case, even after the index changes, we'd still be colliding with the + // element, so it would be selected as the target item the next time this function + // runs again, which would trigger us to revert the index change we recently made. + previousTargetItemKey = targetItem.key + } if (scrollToIndex != null) { scope.launch { // this is needed to neutralize automatic keeping the first item first. @@ -196,20 +224,17 @@ internal constructor( } else { contentListState.onMove(draggingItem.index, targetItem.index) } - draggingItemIndex = targetItem.index isDraggingToRemove = false - } else { + } else if (targetItem == null) { val overscroll = checkForOverscroll(startOffset, endOffset) if (overscroll != 0f) { scrollChannel.trySend(overscroll) } isDraggingToRemove = checkForRemove(startOffset) + previousTargetItemKey = null } } - private val LazyGridItemInfo.offsetEnd: IntOffset - get() = this.offset + this.size - /** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled */ private fun checkForOverscroll(startOffset: Offset, endOffset: Offset): Float { return when { @@ -237,7 +262,7 @@ fun Modifier.dragContainer( viewModel: BaseCommunalViewModel, ): Modifier { return this.then( - pointerInput(dragDropState, contentOffset) { + Modifier.pointerInput(dragDropState, contentOffset) { detectDragGesturesAfterLongPress( onDrag = { change, offset -> change.consume() @@ -273,7 +298,7 @@ fun Modifier.dragContainer( @Composable fun LazyGridItemScope.DraggableItem( dragDropState: GridDragDropState, - index: Int, + key: Any, enabled: Boolean, selected: Boolean, modifier: Modifier = Modifier, @@ -283,7 +308,7 @@ fun LazyGridItemScope.DraggableItem( return content(false) } - val dragging = index == dragDropState.draggingItemIndex + val dragging = key == dragDropState.draggingItemKey val itemAlpha: Float by animateFloatAsState( targetValue = if (dragDropState.isDraggingToRemove) 0.5f else 1f, -- GitLab From 75f17d4fbd59f6a605c4ea3668177408a94c75dd Mon Sep 17 00:00:00 2001 From: Lee Shombert Date: Thu, 10 Oct 2024 11:28:09 -0700 Subject: [PATCH 204/441] Refactor PropertyInvalidatedCache Refactor PropertyInvalidatedCache in preparation for putting nonces in shared memory. The refactoring is as follows: 1. A NonceHandler is created for every property key. Handles are saved in a global map, indexed by the property name. Statistics associated with the key are moved into the NonceHandler. 2. The invalidate, cork, and disable operations are now methods on the NonceHandler. 3. Testing is accomplished by using a NonceHandler that does not write to system properties. Two test APIs are obsolete but because they are in the API list, they will be updated in a later CL. Unit tests have been updated; CTS tests will be updated later, although they pass. Flag: EXEMPT refactor Bug: 360897450 Test: atest * FrameworksCoreTests:PropertyInvalidatedCacheTests * FrameworksCoreTests:IpcDataCacheTest * CtsOsTestCases:IpcDataCacheTest Change-Id: I55824fbffb9fee919298ddf3ec7ea1cba3c9bb3a --- .../android/app/PropertyInvalidatedCache.java | 725 ++++++++++-------- .../app/PropertyInvalidatedCacheTests.java | 30 +- .../src/android/os/IpcDataCacheTest.java | 35 +- 3 files changed, 432 insertions(+), 358 deletions(-) diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index c17da249f322..55296ebbf18e 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -16,6 +16,8 @@ package android.app; +import static android.text.TextUtils.formatSimple; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; @@ -30,11 +32,10 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FastPrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -42,12 +43,14 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; /** @@ -224,12 +227,24 @@ public class PropertyInvalidatedCache { } /** - * Reserved nonce values. Use isReservedNonce() to test for a reserved value. Note - * that all values cause the cache to be skipped. + * Reserved nonce values. Use isReservedNonce() to test for a reserved value. Note that all + * reserved values cause the cache to be skipped. */ + // This is the initial value of all cache keys. It is changed when a cache is invalidated. private static final int NONCE_UNSET = 0; + // This value is used in two ways. First, it is used internally to indicate that the cache is + // disabled for the current query. Secondly, it is used to global disable the cache across the + // entire system. Once a cache is disabled, there is no way to enable it again. The global + // behavior is unused and will likely be removed in the future. private static final int NONCE_DISABLED = 1; + // The cache is corked, which means that clients must act as though the cache is always + // invalid. This is used when the server is processing updates that continuously invalidate + // caches. Rather than issuing individual invalidations (which has a performance penalty), + // the server corks the caches at the start of the process and uncorks at the end of the + // process. private static final int NONCE_CORKED = 2; + // The cache is bypassed for the current query. Unlike UNSET and CORKED, this value is never + // written to global store. private static final int NONCE_BYPASS = 3; private static boolean isReservedNonce(long n) { @@ -237,7 +252,7 @@ public class PropertyInvalidatedCache { } /** - * The names of the nonces + * The names of the reserved nonces. */ private static final String[] sNonceName = new String[]{ "unset", "disabled", "corked", "bypass" }; @@ -276,23 +291,6 @@ public class PropertyInvalidatedCache { */ private static final Object sCorkLock = new Object(); - /** - * Record the number of invalidate or cork calls that were nops because the cache was already - * corked. This is static because invalidation is done in a static context. Entries are - * indexed by the cache property. - */ - @GuardedBy("sCorkLock") - private static final HashMap sCorkedInvalidates = new HashMap<>(); - - /** - * A map of cache keys that we've "corked". (The values are counts.) When a cache key is - * corked, we skip the cache invalidate when the cache key is in the unset state --- that - * is, when a cache key is corked, an invalidation does not enable the cache if somebody - * else hasn't disabled it. - */ - @GuardedBy("sCorkLock") - private static final HashMap sCorks = new HashMap<>(); - /** * A lock for the global list of caches and cache keys. This must never be taken inside mLock * or sCorkLock. @@ -300,9 +298,11 @@ public class PropertyInvalidatedCache { private static final Object sGlobalLock = new Object(); /** - * A map of cache keys that have been disabled in the local process. When a key is - * disabled locally, existing caches are disabled and the key is saved in this map. - * Future cache instances that use the same key will be disabled in their constructor. + * A map of cache keys that have been disabled in the local process. When a key is disabled + * locally, existing caches are disabled and the key is saved in this map. Future cache + * instances that use the same key will be disabled in their constructor. Note that "disabled" + * means the cache is not used in this process. Invalidation still proceeds normally, because + * the cache may be used in other processes. */ @GuardedBy("sGlobalLock") private static final HashSet sDisabledKeys = new HashSet<>(); @@ -314,14 +314,6 @@ public class PropertyInvalidatedCache { @GuardedBy("sGlobalLock") private static final WeakHashMap sCaches = new WeakHashMap<>(); - /** - * Counts of the number of times a cache key was invalidated. Invalidation occurs in a static - * context with no cache object available, so this is a static map. Entries are indexed by - * the cache property. - */ - @GuardedBy("sGlobalLock") - private static final HashMap sInvalidates = new HashMap<>(); - /** * If sEnabled is false then all cache operations are stubbed out. Set * it to false inside test processes. @@ -333,12 +325,6 @@ public class PropertyInvalidatedCache { */ private final String mPropertyName; - /** - * Handle to the {@code mPropertyName} property, transitioning to non-{@code null} once the - * property exists on the system. - */ - private volatile SystemProperties.Handle mPropertyHandle; - /** * The name by which this cache is known. This should normally be the * binder call that is being cached, but the constructors default it to @@ -369,7 +355,13 @@ public class PropertyInvalidatedCache { private final LinkedHashMap mCache; /** - * The last value of the {@code mPropertyHandle} that we observed. + * The nonce handler for this cache. + */ + @GuardedBy("mLock") + private final NonceHandler mNonce; + + /** + * The last nonce value that was observed. */ @GuardedBy("mLock") private long mLastSeenNonce = NONCE_UNSET; @@ -384,6 +376,297 @@ public class PropertyInvalidatedCache { */ private final int mMaxEntries; + /** + * A class to manage cache keys. There is a single instance of this class for each unique key + * that is shared by all cache instances that use that key. This class is abstract; subclasses + * use different storage mechanisms for the nonces. + */ + private static abstract class NonceHandler { + // The name of the nonce. + final String mName; + + // A lock to synchronize corking and invalidation. + protected final Object mLock = new Object(); + + // Count the number of times the property name was invalidated. + @GuardedBy("mLock") + private int mInvalidated = 0; + + // Count the number of times invalidate or cork calls were nops because the cache was + // already corked. + @GuardedBy("mLock") + private int mCorkedInvalidates = 0; + + // Count the number of corks against this property name. This is not a statistic. It + // increases when the property is corked and decreases when the property is uncorked. + // Invalidation requests are ignored when the cork count is greater than zero. + @GuardedBy("mLock") + private int mCorks = 0; + + // The methods to get and set a nonce from whatever storage is being used. + abstract long getNonce(); + abstract void setNonce(long value); + + NonceHandler(@NonNull String name) { + mName = name; + } + + /** + * Write the invalidation nonce for the property. + */ + void invalidate() { + if (!sEnabled) { + if (DEBUG) { + Log.d(TAG, formatSimple("cache invalidate %s suppressed", mName)); + } + return; + } + + synchronized (mLock) { + if (mCorks > 0) { + if (DEBUG) { + Log.d(TAG, "ignoring invalidation due to cork: " + mName); + } + mCorkedInvalidates++; + return; + } + + final long nonce = getNonce(); + if (nonce == NONCE_DISABLED) { + if (DEBUG) { + Log.d(TAG, "refusing to invalidate disabled cache: " + mName); + } + return; + } + + long newValue; + do { + newValue = NoPreloadHolder.next(); + } while (isReservedNonce(newValue)); + if (DEBUG) { + Log.d(TAG, formatSimple( + "invalidating cache [%s]: [%s] -> [%s]", + mName, nonce, Long.toString(newValue))); + } + // There is a small race with concurrent disables here. A compare-and-exchange + // property operation would be required to eliminate the race condition. + setNonce(newValue); + mInvalidated++; + } + } + + void cork() { + if (!sEnabled) { + if (DEBUG) { + Log.d(TAG, formatSimple("cache corking %s suppressed", mName)); + } + return; + } + + synchronized (mLock) { + int numberCorks = mCorks; + if (DEBUG) { + Log.d(TAG, formatSimple( + "corking %s: numberCorks=%s", mName, numberCorks)); + } + + // If we're the first ones to cork this cache, set the cache to the corked state so + // existing caches talk directly to their services while we've corked updates. + // Make sure we don't clobber a disabled cache value. + + // TODO: we can skip this property write and leave the cache enabled if the + // caller promises not to make observable changes to the cache backing state before + // uncorking the cache, e.g., by holding a read lock across the cork-uncork pair. + // Implement this more dangerous mode of operation if necessary. + if (numberCorks == 0) { + final long nonce = getNonce(); + if (nonce != NONCE_UNSET && nonce != NONCE_DISABLED) { + setNonce(NONCE_CORKED); + } + } else { + mCorkedInvalidates++; + } + mCorks++; + if (DEBUG) { + Log.d(TAG, "corked: " + mName); + } + } + } + + void uncork() { + if (!sEnabled) { + if (DEBUG) { + Log.d(TAG, formatSimple("cache uncorking %s suppressed", mName)); + } + return; + } + + synchronized (mLock) { + int numberCorks = --mCorks; + if (DEBUG) { + Log.d(TAG, formatSimple( + "uncorking %s: numberCorks=%s", mName, numberCorks)); + } + + if (numberCorks < 0) { + throw new AssertionError("cork underflow: " + mName); + } + if (numberCorks == 0) { + // The property is fully uncorked and can be invalidated normally. + invalidate(); + if (DEBUG) { + Log.d(TAG, "uncorked: " + mName); + } + } + } + } + + void disable() { + if (!sEnabled) { + return; + } + synchronized (mLock) { + setNonce(NONCE_DISABLED); + } + } + + record Stats(int invalidated, int corkedInvalidates) {} + Stats getStats() { + synchronized (mLock) { + return new Stats(mInvalidated, mCorkedInvalidates); + } + } + } + + /** + * Manage nonces that are stored in a system property. + */ + private static final class NonceSysprop extends NonceHandler { + // A handle to the property, for fast lookups. + private volatile SystemProperties.Handle mHandle; + + NonceSysprop(@NonNull String name) { + super(name); + } + + @Override + long getNonce() { + if (mHandle == null) { + synchronized (mLock) { + mHandle = SystemProperties.find(mName); + if (mHandle == null) { + return NONCE_UNSET; + } + } + } + return mHandle.getLong(NONCE_UNSET); + } + + @Override + void setNonce(long value) { + // Failing to set the nonce is a fatal error. Failures setting a system property have + // been reported; given that the failure is probably transient, this function includes + // a retry. + final String str = Long.toString(value); + RuntimeException failure = null; + for (int attempt = 0; attempt < PROPERTY_FAILURE_RETRY_LIMIT; attempt++) { + try { + SystemProperties.set(mName, str); + if (attempt > 0) { + // This log is not guarded. Based on known bug reports, it should + // occur once a week or less. The purpose of the log message is to + // identify the retries as a source of delay that might be otherwise + // be attributed to the cache itself. + Log.w(TAG, "Nonce set after " + attempt + " tries"); + } + return; + } catch (RuntimeException e) { + if (failure == null) { + failure = e; + } + try { + Thread.sleep(PROPERTY_FAILURE_RETRY_DELAY_MILLIS); + } catch (InterruptedException x) { + // Ignore this exception. The desired delay is only approximate and + // there is no issue if the sleep sometimes terminates early. + } + } + } + // This point is reached only if SystemProperties.set() fails at least once. + // Rethrow the first exception that was received. + throw failure; + } + } + + /** + * SystemProperties and shared storage are protected and cannot be written by random + * processes. So, for testing purposes, the NonceTest handler stores the nonce locally. + */ + private static class NonceTest extends NonceHandler { + // The saved nonce. + private long mValue; + + // If this flag is false, the handler has been shutdown during a test. Access to the + // handler in this state is an error. + private boolean mIsActive = true; + + NonceTest(@NonNull String name) { + super(name); + } + + void shutdown() { + // The handler has been discarded as part of test cleanup. Further access is an + // error. + mIsActive = false; + } + + @Override + long getNonce() { + if (!mIsActive) { + throw new IllegalStateException("handler " + mName + " is shutdown"); + } + return mValue; + } + + @Override + void setNonce(long value) { + if (!mIsActive) { + throw new IllegalStateException("handler " + mName + " is shutdown"); + } + mValue = value; + } + } + + /** + * A static list of nonce handlers, indexed by name. NonceHandlers can be safely shared by + * multiple threads, and can therefore be shared by multiple instances of the same cache, and + * with static calls (see {@link #invalidateCache}. Addition and removal are guarded by the + * global lock, to ensure that duplicates are not created. + */ + private static final ConcurrentHashMap sHandlers + = new ConcurrentHashMap<>(); + + /** + * Return the proper nonce handler, based on the property name. + */ + private static NonceHandler getNonceHandler(@NonNull String name) { + NonceHandler h = sHandlers.get(name); + if (h == null) { + synchronized (sGlobalLock) { + h = sHandlers.get(name); + if (h == null) { + if (name.startsWith("cache_key.test.")) { + h = new NonceTest(name); + } else { + h = new NonceSysprop(name); + } + sHandlers.put(name, h); + } + } + } + return h; + } + /** * Make a new property invalidated cache. This constructor names the cache after the * property name. New clients should prefer the constructor that takes an explicit @@ -417,6 +700,7 @@ public class PropertyInvalidatedCache { mPropertyName = propertyName; validateCacheKey(mPropertyName); mCacheName = cacheName; + mNonce = getNonceHandler(mPropertyName); mMaxEntries = maxEntries; mComputer = new DefaultComputer<>(this); mCache = createMap(); @@ -441,6 +725,7 @@ public class PropertyInvalidatedCache { mPropertyName = createPropertyName(module, api); validateCacheKey(mPropertyName); mCacheName = cacheName; + mNonce = getNonceHandler(mPropertyName); mMaxEntries = maxEntries; mComputer = computer; mCache = createMap(); @@ -484,130 +769,58 @@ public class PropertyInvalidatedCache { } /** - * SystemProperties are protected and cannot be written (or read, usually) by random - * processes. So, for testing purposes, the methods have a bypass mode that reads and - * writes to a HashMap and does not go out to the SystemProperties at all. - */ - - // If true, the cache might be under test. If false, there is no testing in progress. - private static volatile boolean sTesting = false; - - // If sTesting is true then keys that are under test are in this map. - private static final HashMap sTestingPropertyMap = new HashMap<>(); - - /** - * Enable or disable testing. The testing property map is cleared every time this - * method is called. + * Enable or disable testing. At this time, no action is taken when testing begins. * @hide */ @TestApi public static void setTestMode(boolean mode) { - sTesting = mode; - synchronized (sTestingPropertyMap) { - sTestingPropertyMap.clear(); - } - } - - /** - * Enable testing the specific cache key. Only keys in the map are subject to testing. - * There is no method to stop testing a property name. Just disable the test mode. - */ - private static void testPropertyName(@NonNull String name) { - synchronized (sTestingPropertyMap) { - sTestingPropertyMap.put(name, (long) NONCE_UNSET); + if (mode) { + // No action when testing begins. + } else { + resetAfterTest(); } } /** - * Enable testing the specific cache key. Only keys in the map are subject to testing. - * There is no method to stop testing a property name. Just disable the test mode. + * Enable testing the specific cache key. This is a legacy API that will be removed as part of + * b/360897450. * @hide */ @TestApi public void testPropertyName() { - testPropertyName(mPropertyName); - } - - // Read the system property associated with the current cache. This method uses the - // handle for faster reading. - private long getCurrentNonce() { - if (sTesting) { - synchronized (sTestingPropertyMap) { - Long n = sTestingPropertyMap.get(mPropertyName); - if (n != null) { - return n; - } - } - } - - SystemProperties.Handle handle = mPropertyHandle; - if (handle == null) { - handle = SystemProperties.find(mPropertyName); - if (handle == null) { - return NONCE_UNSET; - } - mPropertyHandle = handle; - } - return handle.getLong(NONCE_UNSET); } - // Write the nonce in a static context. No handle is available. - private static void setNonce(String name, long val) { - if (sTesting) { - synchronized (sTestingPropertyMap) { - Long n = sTestingPropertyMap.get(name); - if (n != null) { - sTestingPropertyMap.put(name, val); - return; - } - } - } - RuntimeException failure = null; - for (int attempt = 0; attempt < PROPERTY_FAILURE_RETRY_LIMIT; attempt++) { - try { - SystemProperties.set(name, Long.toString(val)); - if (attempt > 0) { - // This log is not guarded. Based on known bug reports, it should - // occur once a week or less. The purpose of the log message is to - // identify the retries as a source of delay that might be otherwise - // be attributed to the cache itself. - Log.w(TAG, "Nonce set after " + attempt + " tries"); - } - return; - } catch (RuntimeException e) { - if (failure == null) { - failure = e; - } - try { - Thread.sleep(PROPERTY_FAILURE_RETRY_DELAY_MILLIS); - } catch (InterruptedException x) { - // Ignore this exception. The desired delay is only approximate and - // there is no issue if the sleep sometimes terminates early. + /** + * Clean up when testing ends. All NonceTest handlers are erased from the global list and are + * poisoned, just in case the test program has retained a handle to one of the associated + * caches. + * @hide + */ + @VisibleForTesting + public static void resetAfterTest() { + synchronized (sGlobalLock) { + for (Iterator e = sHandlers.keys().asIterator(); e.hasNext(); ) { + String s = e.next(); + final NonceHandler h = sHandlers.get(s); + if (h instanceof NonceTest t) { + t.shutdown(); + sHandlers.remove(s); } } } - // This point is reached only if SystemProperties.set() fails at least once. - // Rethrow the first exception that was received. - throw failure; } - // Set the nonce in a static context. No handle is available. - private static long getNonce(String name) { - if (sTesting) { - synchronized (sTestingPropertyMap) { - Long n = sTestingPropertyMap.get(name); - if (n != null) { - return n; - } - } - } - return SystemProperties.getLong(name, NONCE_UNSET); + // Read the nonce associated with the current cache. + @GuardedBy("mLock") + private long getCurrentNonce() { + return mNonce.getNonce(); } /** - * Forget all cached values. - * TODO(216112648) remove this as a public API. Clients should invalidate caches, not clear - * them. + * Forget all cached values. This is used by a client when the server exits. Since the + * server has exited, the cache values are no longer valid, but the server is no longer + * present to invalidate the cache. Note that this is not necessary if the server is + * system_server, because the entire operating system reboots if that process exits. * @hide */ public final void clear() { @@ -674,7 +887,7 @@ public class PropertyInvalidatedCache { } /** - * Disable the use of this cache in this process. This method is using internally and during + * Disable the use of this cache in this process. This method is used internally and during * testing. To disable a cache in normal code, use disableLocal(). A disabled cache cannot * be re-enabled. * @hide @@ -783,7 +996,7 @@ public class PropertyInvalidatedCache { if (DEBUG) { if (!mDisabled) { - Log.d(TAG, TextUtils.formatSimple( + Log.d(TAG, formatSimple( "cache %s %s for %s", cacheName(), sNonceName[(int) currentNonce], queryToString(query))); } @@ -798,7 +1011,7 @@ public class PropertyInvalidatedCache { if (cachedResult != null) mHits++; } else { if (DEBUG) { - Log.d(TAG, TextUtils.formatSimple( + Log.d(TAG, formatSimple( "clearing cache %s of %d entries because nonce changed [%s] -> [%s]", cacheName(), mCache.size(), mLastSeenNonce, currentNonce)); @@ -824,7 +1037,7 @@ public class PropertyInvalidatedCache { if (currentNonce != afterRefreshNonce) { currentNonce = afterRefreshNonce; if (DEBUG) { - Log.d(TAG, TextUtils.formatSimple( + Log.d(TAG, formatSimple( "restarting %s %s because nonce changed in refresh", cacheName(), queryToString(query))); @@ -895,10 +1108,7 @@ public class PropertyInvalidatedCache { * @param name Name of the cache-key property to invalidate */ private static void disableSystemWide(@NonNull String name) { - if (!sEnabled) { - return; - } - setNonce(name, NONCE_DISABLED); + getNonceHandler(name).disable(); } /** @@ -908,7 +1118,7 @@ public class PropertyInvalidatedCache { */ @TestApi public void invalidateCache() { - invalidateCache(mPropertyName); + mNonce.invalidate(); } /** @@ -931,59 +1141,7 @@ public class PropertyInvalidatedCache { * @hide */ public static void invalidateCache(@NonNull String name) { - if (!sEnabled) { - if (DEBUG) { - Log.w(TAG, TextUtils.formatSimple( - "cache invalidate %s suppressed", name)); - } - return; - } - - // Take the cork lock so invalidateCache() racing against corkInvalidations() doesn't - // clobber a cork-written NONCE_UNSET with a cache key we compute before the cork. - // The property service is single-threaded anyway, so we don't lose any concurrency by - // taking the cork lock around cache invalidations. If we see contention on this lock, - // we're invalidating too often. - synchronized (sCorkLock) { - Integer numberCorks = sCorks.get(name); - if (numberCorks != null && numberCorks > 0) { - if (DEBUG) { - Log.d(TAG, "ignoring invalidation due to cork: " + name); - } - final long count = sCorkedInvalidates.getOrDefault(name, (long) 0); - sCorkedInvalidates.put(name, count + 1); - return; - } - invalidateCacheLocked(name); - } - } - - @GuardedBy("sCorkLock") - private static void invalidateCacheLocked(@NonNull String name) { - // There's no race here: we don't require that values strictly increase, but instead - // only that each is unique in a single runtime-restart session. - final long nonce = getNonce(name); - if (nonce == NONCE_DISABLED) { - if (DEBUG) { - Log.d(TAG, "refusing to invalidate disabled cache: " + name); - } - return; - } - - long newValue; - do { - newValue = NoPreloadHolder.next(); - } while (isReservedNonce(newValue)); - if (DEBUG) { - Log.d(TAG, TextUtils.formatSimple( - "invalidating cache [%s]: [%s] -> [%s]", - name, nonce, Long.toString(newValue))); - } - // There is a small race with concurrent disables here. A compare-and-exchange - // property operation would be required to eliminate the race condition. - setNonce(name, newValue); - long invalidateCount = sInvalidates.getOrDefault(name, (long) 0); - sInvalidates.put(name, ++invalidateCount); + getNonceHandler(name).invalidate(); } /** @@ -1000,43 +1158,7 @@ public class PropertyInvalidatedCache { * @hide */ public static void corkInvalidations(@NonNull String name) { - if (!sEnabled) { - if (DEBUG) { - Log.w(TAG, TextUtils.formatSimple( - "cache cork %s suppressed", name)); - } - return; - } - - synchronized (sCorkLock) { - int numberCorks = sCorks.getOrDefault(name, 0); - if (DEBUG) { - Log.d(TAG, TextUtils.formatSimple( - "corking %s: numberCorks=%s", name, numberCorks)); - } - - // If we're the first ones to cork this cache, set the cache to the corked state so - // existing caches talk directly to their services while we've corked updates. - // Make sure we don't clobber a disabled cache value. - - // TODO(dancol): we can skip this property write and leave the cache enabled if the - // caller promises not to make observable changes to the cache backing state before - // uncorking the cache, e.g., by holding a read lock across the cork-uncork pair. - // Implement this more dangerous mode of operation if necessary. - if (numberCorks == 0) { - final long nonce = getNonce(name); - if (nonce != NONCE_UNSET && nonce != NONCE_DISABLED) { - setNonce(name, NONCE_CORKED); - } - } else { - final long count = sCorkedInvalidates.getOrDefault(name, (long) 0); - sCorkedInvalidates.put(name, count + 1); - } - sCorks.put(name, numberCorks + 1); - if (DEBUG) { - Log.d(TAG, "corked: " + name); - } - } + getNonceHandler(name).cork(); } /** @@ -1048,34 +1170,7 @@ public class PropertyInvalidatedCache { * @hide */ public static void uncorkInvalidations(@NonNull String name) { - if (!sEnabled) { - if (DEBUG) { - Log.w(TAG, TextUtils.formatSimple( - "cache uncork %s suppressed", name)); - } - return; - } - - synchronized (sCorkLock) { - int numberCorks = sCorks.getOrDefault(name, 0); - if (DEBUG) { - Log.d(TAG, TextUtils.formatSimple( - "uncorking %s: numberCorks=%s", name, numberCorks)); - } - - if (numberCorks < 1) { - throw new AssertionError("cork underflow: " + name); - } - if (numberCorks == 1) { - sCorks.remove(name); - invalidateCacheLocked(name); - if (DEBUG) { - Log.d(TAG, "uncorked: " + name); - } - } else { - sCorks.put(name, numberCorks - 1); - } - } + getNonceHandler(name).uncork(); } /** @@ -1104,6 +1199,8 @@ public class PropertyInvalidatedCache { @GuardedBy("mLock") private Handler mHandler; + private NonceHandler mNonce; + public AutoCorker(@NonNull String propertyName) { this(propertyName, DEFAULT_AUTO_CORK_DELAY_MS); } @@ -1117,31 +1214,35 @@ public class PropertyInvalidatedCache { } public void autoCork() { + synchronized (mLock) { + if (mNonce == null) { + mNonce = getNonceHandler(mPropertyName); + } + } + if (getLooper() == null) { // We're not ready to auto-cork yet, so just invalidate the cache immediately. if (DEBUG) { Log.w(TAG, "invalidating instead of autocorking early in init: " + mPropertyName); } - PropertyInvalidatedCache.invalidateCache(mPropertyName); + mNonce.invalidate(); return; } synchronized (mLock) { boolean alreadyQueued = mUncorkDeadlineMs >= 0; if (DEBUG) { - Log.w(TAG, TextUtils.formatSimple( + Log.d(TAG, formatSimple( "autoCork %s mUncorkDeadlineMs=%s", mPropertyName, mUncorkDeadlineMs)); } mUncorkDeadlineMs = SystemClock.uptimeMillis() + mAutoCorkDelayMs; if (!alreadyQueued) { getHandlerLocked().sendEmptyMessageAtTime(0, mUncorkDeadlineMs); - PropertyInvalidatedCache.corkInvalidations(mPropertyName); + mNonce.cork(); } else { - synchronized (sCorkLock) { - final long count = sCorkedInvalidates.getOrDefault(mPropertyName, (long) 0); - sCorkedInvalidates.put(mPropertyName, count + 1); - } + // Count this as a corked invalidation. + mNonce.invalidate(); } } } @@ -1149,7 +1250,7 @@ public class PropertyInvalidatedCache { private void handleMessage(Message msg) { synchronized (mLock) { if (DEBUG) { - Log.w(TAG, TextUtils.formatSimple( + Log.d(TAG, formatSimple( "handleMsesage %s mUncorkDeadlineMs=%s", mPropertyName, mUncorkDeadlineMs)); } @@ -1161,7 +1262,7 @@ public class PropertyInvalidatedCache { if (mUncorkDeadlineMs > nowMs) { mUncorkDeadlineMs = nowMs + mAutoCorkDelayMs; if (DEBUG) { - Log.w(TAG, TextUtils.formatSimple( + Log.d(TAG, formatSimple( "scheduling uncork at %s", mUncorkDeadlineMs)); } @@ -1169,10 +1270,10 @@ public class PropertyInvalidatedCache { return; } if (DEBUG) { - Log.w(TAG, "automatic uncorking " + mPropertyName); + Log.d(TAG, "automatic uncorking " + mPropertyName); } mUncorkDeadlineMs = -1; - PropertyInvalidatedCache.uncorkInvalidations(mPropertyName); + mNonce.uncork(); } } @@ -1207,7 +1308,7 @@ public class PropertyInvalidatedCache { Result resultToCompare = recompute(query); boolean nonceChanged = (getCurrentNonce() != mLastSeenNonce); if (!nonceChanged && !resultEquals(proposedResult, resultToCompare)) { - Log.e(TAG, TextUtils.formatSimple( + Log.e(TAG, formatSimple( "cache %s inconsistent for %s is %s should be %s", cacheName(), queryToString(query), proposedResult, resultToCompare)); @@ -1284,17 +1385,9 @@ public class PropertyInvalidatedCache { /** * Returns a list of caches alive at the current time. */ - @GuardedBy("sGlobalLock") private static @NonNull ArrayList getActiveCaches() { - return new ArrayList(sCaches.keySet()); - } - - /** - * Returns a list of the active corks in a process. - */ - private static @NonNull ArrayList> getActiveCorks() { - synchronized (sCorkLock) { - return new ArrayList>(sCorks.entrySet()); + synchronized (sGlobalLock) { + return new ArrayList(sCaches.keySet()); } } @@ -1361,32 +1454,27 @@ public class PropertyInvalidatedCache { return; } - long invalidateCount; - long corkedInvalidates; - synchronized (sCorkLock) { - invalidateCount = sInvalidates.getOrDefault(mPropertyName, (long) 0); - corkedInvalidates = sCorkedInvalidates.getOrDefault(mPropertyName, (long) 0); - } + NonceHandler.Stats stats = mNonce.getStats(); synchronized (mLock) { - pw.println(TextUtils.formatSimple(" Cache Name: %s", cacheName())); - pw.println(TextUtils.formatSimple(" Property: %s", mPropertyName)); + pw.println(formatSimple(" Cache Name: %s", cacheName())); + pw.println(formatSimple(" Property: %s", mPropertyName)); final long skips = mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED] + mSkips[NONCE_BYPASS]; - pw.println(TextUtils.formatSimple( + pw.println(formatSimple( " Hits: %d, Misses: %d, Skips: %d, Clears: %d", mHits, mMisses, skips, mClears)); - pw.println(TextUtils.formatSimple( + pw.println(formatSimple( " Skip-corked: %d, Skip-unset: %d, Skip-bypass: %d, Skip-other: %d", mSkips[NONCE_CORKED], mSkips[NONCE_UNSET], mSkips[NONCE_BYPASS], mSkips[NONCE_DISABLED])); - pw.println(TextUtils.formatSimple( + pw.println(formatSimple( " Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d", - mLastSeenNonce, invalidateCount, corkedInvalidates)); - pw.println(TextUtils.formatSimple( + mLastSeenNonce, stats.invalidated, stats.corkedInvalidates)); + pw.println(formatSimple( " Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d", mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow)); - pw.println(TextUtils.formatSimple(" Enabled: %s", mDisabled ? "false" : "true")); + pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true")); pw.println(""); // No specific cache was requested. This is the default, and no details @@ -1404,23 +1492,7 @@ public class PropertyInvalidatedCache { String key = Objects.toString(entry.getKey()); String value = Objects.toString(entry.getValue()); - pw.println(TextUtils.formatSimple(" Key: %s\n Value: %s\n", key, value)); - } - } - } - - /** - * Dump the corking status. - */ - @GuardedBy("sCorkLock") - private static void dumpCorkInfo(PrintWriter pw) { - ArrayList> activeCorks = getActiveCorks(); - if (activeCorks.size() > 0) { - pw.println(" Corking Status:"); - for (int i = 0; i < activeCorks.size(); i++) { - Map.Entry entry = activeCorks.get(i); - pw.println(TextUtils.formatSimple(" Property Name: %s Count: %d", - entry.getKey(), entry.getValue())); + pw.println(formatSimple(" Key: %s\n Value: %s\n", key, value)); } } } @@ -1441,14 +1513,7 @@ public class PropertyInvalidatedCache { // then only that cache is reported. boolean detail = anyDetailed(args); - ArrayList activeCaches; - synchronized (sGlobalLock) { - activeCaches = getActiveCaches(); - if (!detail) { - dumpCorkInfo(pw); - } - } - + ArrayList activeCaches = getActiveCaches(); for (int i = 0; i < activeCaches.size(); i++) { PropertyInvalidatedCache currentCache = activeCaches.get(i); currentCache.dumpContents(pw, detail, args); diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java index b5ee1302fc1d..228647ae9094 100644 --- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java @@ -19,6 +19,7 @@ package android.app; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; @@ -26,6 +27,7 @@ import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -90,11 +92,10 @@ public class PropertyInvalidatedCacheTests { } } - // Clear the test mode after every test, in case this process is used for other - // tests. This also resets the test property map. + // Ensure all test nonces are cleared after the test ends. @After public void tearDown() throws Exception { - PropertyInvalidatedCache.setTestMode(false); + PropertyInvalidatedCache.resetAfterTest(); } // This test is disabled pending an sepolicy change that allows any app to set the @@ -111,9 +112,6 @@ public class PropertyInvalidatedCacheTests { new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", new ServerQuery(tester)); - PropertyInvalidatedCache.setTestMode(true); - testCache.testPropertyName(); - tester.verify(0); assertEquals(tester.value(3), testCache.query(3)); tester.verify(1); @@ -223,22 +221,16 @@ public class PropertyInvalidatedCacheTests { TestCache(String module, String api) { this(module, api, new TestQuery()); - setTestMode(true); - testPropertyName(); } TestCache(String module, String api, TestQuery query) { super(4, module, api, api, query); mQuery = query; - setTestMode(true); - testPropertyName(); } public int getRecomputeCount() { return mQuery.getRecomputeCount(); } - - } @Test @@ -375,4 +367,18 @@ public class PropertyInvalidatedCacheTests { PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState"); assertEquals(n1, "cache_key.bluetooth.get_state"); } + + // It is illegal to continue to use a cache with a test key after calling setTestMode(false). + // This test verifies the code detects errors in calling setTestMode(). + @Test + public void testTestMode() { + TestCache cache = new TestCache(); + cache.invalidateCache(); + PropertyInvalidatedCache.resetAfterTest(); + try { + cache.invalidateCache(); + fail("expected an IllegalStateException"); + } catch (IllegalStateException expected) { + } + } } diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java index 64f77b309829..5852bee53778 100644 --- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java +++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java @@ -17,6 +17,7 @@ package android.os; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import android.multiuser.Flags; import android.platform.test.annotations.IgnoreUnderRavenwood; @@ -26,6 +27,7 @@ import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -92,17 +94,17 @@ public class IpcDataCacheTest { public Boolean apply(Integer x) { return mServer.query(x); } + @Override public boolean shouldBypassCache(Integer x) { return x % 13 == 0; } } - // Clear the test mode after every test, in case this process is used for other - // tests. This also resets the test property map. + // Ensure all test nonces are cleared after the test ends. @After public void tearDown() throws Exception { - IpcDataCache.setTestMode(false); + IpcDataCache.resetAfterTest(); } // This test is disabled pending an sepolicy change that allows any app to set the @@ -119,9 +121,6 @@ public class IpcDataCacheTest { new IpcDataCache<>(4, MODULE, API, "testCache1", new ServerQuery(tester)); - IpcDataCache.setTestMode(true); - testCache.testPropertyName(); - tester.verify(0); assertEquals(tester.value(3), testCache.query(3)); tester.verify(1); @@ -165,9 +164,6 @@ public class IpcDataCacheTest { IpcDataCache testCache = new IpcDataCache<>(config, (x) -> tester.query(x, x % 10 == 9)); - IpcDataCache.setTestMode(true); - testCache.testPropertyName(); - tester.verify(0); assertEquals(tester.value(3), testCache.query(3)); tester.verify(1); @@ -205,9 +201,6 @@ public class IpcDataCacheTest { IpcDataCache testCache = new IpcDataCache<>(config, (x) -> tester.query(x), (x) -> x % 9 == 0); - IpcDataCache.setTestMode(true); - testCache.testPropertyName(); - tester.verify(0); assertEquals(tester.value(3), testCache.query(3)); tester.verify(1); @@ -313,8 +306,6 @@ public class IpcDataCacheTest { TestCache(String module, String api, TestQuery query) { super(4, module, api, "testCache7", query); mQuery = query; - setTestMode(true); - testPropertyName(); } TestCache(IpcDataCache.Config c) { @@ -324,8 +315,6 @@ public class IpcDataCacheTest { TestCache(IpcDataCache.Config c, TestQuery query) { super(c, query); mQuery = query; - setTestMode(true); - testPropertyName(); } int getRecomputeCount() { @@ -456,4 +445,18 @@ public class IpcDataCacheTest { TestCache ec = new TestCache(e); assertEquals(ec.isDisabled(), true); } + + // It is illegal to continue to use a cache with a test key after calling setTestMode(false). + // This test verifies the code detects errors in calling setTestMode(). + @Test + public void testTestMode() { + TestCache cache = new TestCache(); + cache.invalidateCache(); + IpcDataCache.resetAfterTest(); + try { + cache.invalidateCache(); + fail("expected an IllegalStateException"); + } catch (IllegalStateException expected) { + } + } } -- GitLab From 1f50d0aad07ada0cef04c53b771c854b86f9a0b2 Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Mon, 14 Oct 2024 10:40:04 -0700 Subject: [PATCH 205/441] Fix inconsistent tracing of bitmap count/memory Bug: 373408586 Flag: EXEMPT bugfix Bitmap count/memory usage should be updated regardless of whether tracing is enabled or not. Change-Id: Ie98ab19ddbbb2d87d89e70f81bd743d7f377c4f8 --- libs/hwui/hwui/Bitmap.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 84bd45dfc012..b73380e38fd1 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -582,20 +582,22 @@ size_t Bitmap::mTotalBitmapBytes = 0; size_t Bitmap::mTotalBitmapCount = 0; void Bitmap::traceBitmapCreate() { + size_t bytes = getAllocationByteCount(); + std::lock_guard lock{mLock}; + mTotalBitmapBytes += bytes; + mTotalBitmapCount++; if (ATRACE_ENABLED()) { - std::lock_guard lock{mLock}; - mTotalBitmapBytes += getAllocationByteCount(); - mTotalBitmapCount++; ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes); ATRACE_INT64("Bitmap Count", mTotalBitmapCount); } } void Bitmap::traceBitmapDelete() { + size_t bytes = getAllocationByteCount(); + std::lock_guard lock{mLock}; + mTotalBitmapBytes -= getAllocationByteCount(); + mTotalBitmapCount--; if (ATRACE_ENABLED()) { - std::lock_guard lock{mLock}; - mTotalBitmapBytes -= getAllocationByteCount(); - mTotalBitmapCount--; ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes); ATRACE_INT64("Bitmap Count", mTotalBitmapCount); } -- GitLab From 6ca88590ec02c7dbede3992a58a89623aa389911 Mon Sep 17 00:00:00 2001 From: Felix Stern Date: Sat, 12 Oct 2024 22:04:11 +0000 Subject: [PATCH 206/441] Fix showing the IME on another display, when requested from a virtual display. A virtual display might initially have no registered (IME) insets source provider. In this case, a show request (from an app on the virtual display) was not executed, as it happened before the input target is set (only thereafter the source provider is set). If the IME is on another display, we're setting the requested IME state on the remote target after updating the IME input target. Test: atest VirtualDeviceImeTest Bug: 372218080 Flag: android.view.inputmethod.refactor_insets_controller Change-Id: Ieacd52fecd0acf82b1ee0cc9be80305d526bc77c --- .../server/wm/ImeInsetsSourceProvider.java | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 6a23aaa663ef..e9c6e93891df 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -274,34 +274,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { if (caller != controlTarget) { if (Flags.refactorInsetsController()) { if (isImeInputTarget(caller)) { - // In case of the multi window mode, update the requestedVisibleTypes from - // the controlTarget (=RemoteInsetsControlTarget) via DisplayImeController. - // Then, trigger onRequestedVisibleTypesChanged for the controlTarget with - // its new requested visibility for the IME - boolean imeVisible = caller.isRequestedVisible(WindowInsets.Type.ime()); - if (controlTarget != null) { - ImeTracker.forLogging().onProgress(statsToken, - ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); - controlTarget.setImeInputTargetRequestedVisibility(imeVisible); - } else if (caller instanceof InsetsControlTarget) { - // In case of a virtual display that cannot show the IME, the - // controlTarget will be null here, as no controlTarget was set yet. In - // that case, proceed similar to the multi window mode (fallback = - // RemoteInsetsControlTarget of the default display) - controlTarget = mDisplayContent.getImeHostOrFallback( - ((InsetsControlTarget) caller).getWindow()); - - if (controlTarget != caller) { - ImeTracker.forLogging().onProgress(statsToken, - ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); - controlTarget.setImeInputTargetRequestedVisibility(imeVisible); - } else { - ImeTracker.forLogging().onFailed(statsToken, - ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); - } - } - - invokeOnImeRequestedChangedListener(caller, statsToken); + reportImeInputTargetStateToControlTarget(caller, controlTarget, statsToken); } else { // TODO(b/353463205) add ImeTracker? } @@ -332,14 +305,42 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { if (Flags.refactorInsetsController() && target != null) { InsetsControlTarget imeControlTarget = getControlTarget(); if (target != imeControlTarget) { - // If the targetWin is not the imeControlTarget (=RemoteInsetsControlTarget) let it - // know about the new requestedVisibleTypes for the IME. - if (imeControlTarget != null) { - imeControlTarget.setImeInputTargetRequestedVisibility( - (target.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); - } + // TODO(b/353463205): start new request here? + reportImeInputTargetStateToControlTarget(target, imeControlTarget, + null /* statsToken */); + } + } + } + + private void reportImeInputTargetStateToControlTarget(@NonNull InsetsTarget imeInsetsTarget, + InsetsControlTarget controlTarget, @Nullable ImeTracker.Token statsToken) { + // In case of the multi window mode, update the requestedVisibleTypes from + // the controlTarget (=RemoteInsetsControlTarget) via DisplayImeController. + // Then, trigger onRequestedVisibleTypesChanged for the controlTarget with + // its new requested visibility for the IME + boolean imeVisible = imeInsetsTarget.isRequestedVisible(WindowInsets.Type.ime()); + if (controlTarget != null) { + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); + controlTarget.setImeInputTargetRequestedVisibility(imeVisible); + } else if (imeInsetsTarget instanceof InsetsControlTarget) { + // In case of a virtual display that cannot show the IME, the + // controlTarget will be null here, as no controlTarget was set yet. In + // that case, proceed similar to the multi window mode (fallback = + // RemoteInsetsControlTarget of the default display) + controlTarget = mDisplayContent.getImeHostOrFallback( + ((InsetsControlTarget) imeInsetsTarget).getWindow()); + + if (controlTarget != imeInsetsTarget) { + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); + controlTarget.setImeInputTargetRequestedVisibility(imeVisible); + } else { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); } } + invokeOnImeRequestedChangedListener(imeInsetsTarget, statsToken); } // TODO(b/353463205) check callers to see if we can make statsToken @NonNull -- GitLab From 7c6980cb38999434b355e689475aa4d2e3a637ff Mon Sep 17 00:00:00 2001 From: Jared Finder Date: Sat, 12 Oct 2024 00:02:36 +0000 Subject: [PATCH 207/441] Add new flag for XR-specific manifest entries This flag will be used to add new XR-specific permission strings and feature strings. Bug: 364416355 Flag: android.xr.xr_manifest_entries Test: Checked that project still built. Change-Id: I554ab816d81dbe329deedf304218691109f7c7c5 --- AconfigFlags.bp | 15 +++++++++++++++ core/java/android/content/pm/xr.aconfig | 9 +++++++++ 2 files changed, 24 insertions(+) create mode 100644 core/java/android/content/pm/xr.aconfig diff --git a/AconfigFlags.bp b/AconfigFlags.bp index c6ce799f0a24..6105ab5518c8 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -81,6 +81,7 @@ aconfig_declarations_group { "android.view.inputmethod.flags-aconfig-java", "android.webkit.flags-aconfig-java", "android.widget.flags-aconfig-java", + "android.xr.flags-aconfig-java", "art_exported_aconfig_flags_lib", "backstage_power_flags_lib", "backup_flags_lib", @@ -893,6 +894,20 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// XR +aconfig_declarations { + name: "android.xr.flags-aconfig", + package: "android.xr", + container: "system", + srcs: ["core/java/android/content/pm/xr.aconfig"], +} + +java_aconfig_library { + name: "android.xr.flags-aconfig-java", + aconfig_declarations: "android.xr.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // android.app aconfig_declarations { name: "android.app.flags-aconfig", diff --git a/core/java/android/content/pm/xr.aconfig b/core/java/android/content/pm/xr.aconfig new file mode 100644 index 000000000000..61835c162c49 --- /dev/null +++ b/core/java/android/content/pm/xr.aconfig @@ -0,0 +1,9 @@ +package: "android.xr" +container: "system" + +flag { + namespace: "xr" + name: "xr_manifest_entries" + description: "Adds manifest entries used by Android XR" + bug: "364416355" +} \ No newline at end of file -- GitLab From 8a498d23fd2d15fa9386b57e116161588d4754db Mon Sep 17 00:00:00 2001 From: Felix Stern Date: Mon, 14 Oct 2024 13:03:11 +0000 Subject: [PATCH 208/441] Deprecating ResultsReceiver usage in InputMethodManager Previously, the ResultReceiver was passed to InputMethodManager#{show,hide}SoftInput and send along to IMS. With the changes of [1], requests from IMM are forwarded to InsetsController, where the resultReceiver was not passed, as we only send the requestedVisibleTypes to the system. With the changes of this CL, we notify the resultReceiver in InputMethodManager, before passing the request to InsetsController. Reason for deprecating: The ResultReceiver is not reliable to be used for determining whether the IME is shown or not. The recommended usage would be to set a View.OnApplyWindowInsetsListener, which is invoked whenever the IME will show. [1]: I8e3a74ee579f085cb582040fdba725e7a63d6b85 Test: blaze test \ --android_platforms=//buildenv/platforms/android:armeabi-v7a,//buildenv/platforms/android:arm64-v8a \ --config=android_local_adb \ --nocache_test_results \ --test_arg=--install_test_services \ --test_arg=--install_basic_services \ --runs_per_test=1 \ //javatests/com/google/android/apps/gmail/espresso:AccountSetupTest_hub_as_gmail_dev_offline_sapi_appcompat_master Bug: 372199387 Flag: android.view.inputmethod.refactor_insets_controller Change-Id: I6031229f2069e565360100a9a166c34272dcaff6 --- core/api/current.txt | 4 +-- .../view/inputmethod/InputMethodManager.java | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 75c9d7108b08..0b8e139cf85a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -56599,7 +56599,7 @@ package android.view.inputmethod { method public java.util.Map> getShortcutInputMethodsAndSubtypes(); method @Deprecated public void hideSoftInputFromInputMethod(android.os.IBinder, int); method public boolean hideSoftInputFromWindow(android.os.IBinder, int); - method public boolean hideSoftInputFromWindow(android.os.IBinder, int, android.os.ResultReceiver); + method @Deprecated public boolean hideSoftInputFromWindow(android.os.IBinder, int, android.os.ResultReceiver); method @Deprecated public void hideStatusIcon(android.os.IBinder); method public void invalidateInput(@NonNull android.view.View); method public boolean isAcceptingText(); @@ -56623,7 +56623,7 @@ package android.view.inputmethod { method public void showInputMethodAndSubtypeEnabler(@Nullable String); method public void showInputMethodPicker(); method public boolean showSoftInput(android.view.View, int); - method public boolean showSoftInput(android.view.View, int, android.os.ResultReceiver); + method @Deprecated public boolean showSoftInput(android.view.View, int, android.os.ResultReceiver); method @Deprecated public void showSoftInputFromInputMethod(android.os.IBinder, int); method @Deprecated public void showStatusIcon(android.os.IBinder, String, @DrawableRes int); method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public void startConnectionlessStylusHandwriting(@NonNull android.view.View, @Nullable android.view.inputmethod.CursorAnchorInfo, @NonNull java.util.concurrent.Executor, @NonNull android.view.inputmethod.ConnectionlessHandwritingCallback); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 1e5c6d8177e1..47fc43735c4d 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2352,6 +2352,13 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note: * this does not return result of the request. For result use {@param resultReceiver} instead. + * + * @deprecated The {@link ResultReceiver} is not a reliable way of determining whether the + * Input Method is actually shown or hidden. If result is needed, use + * {@link android.view.WindowInsetsController#show} instead and set a + * {@link View.OnApplyWindowInsetsListener} and verify the provided {@link WindowInsets} for + * the visibility of IME. If result is not needed, use {@link #showSoftInput(View, int)} + * instead. */ public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) { return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT); @@ -2399,6 +2406,14 @@ public final class InputMethodManager { & WindowInsets.Type.ime()) == 0) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_NO_ONGOING_USER_ANIMATION); + if (resultReceiver != null) { + final boolean imeReqVisible = + (viewRootImpl.getInsetsController().getRequestedVisibleTypes() + & WindowInsets.Type.ime()) != 0; + resultReceiver.send( + imeReqVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN + : InputMethodManager.RESULT_SHOWN, null); + } // TODO(b/322992891) handle case of SHOW_IMPLICIT viewRootImpl.getInsetsController().show(WindowInsets.Type.ime(), false /* fromIme */, statsToken); @@ -2531,6 +2546,13 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note: * this does not return result of the request. For result use {@param resultReceiver} instead. + * + * @deprecated The {@link ResultReceiver} is not a reliable way of determining whether the + * Input Method is actually shown or hidden. If result is needed, use + * {@link android.view.WindowInsetsController#hide} instead and set a + * {@link View.OnApplyWindowInsetsListener} and verify the provided {@link WindowInsets} for + * the visibility of IME. If result is not needed, use + * {@link #hideSoftInputFromView(View, int)} instead. */ public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags, ResultReceiver resultReceiver) { @@ -2569,6 +2591,14 @@ public final class InputMethodManager { // TODO(b/322992891) handle case of HIDE_IMPLICIT_ONLY final var viewRootImpl = servedView.getViewRootImpl(); if (viewRootImpl != null) { + if (resultReceiver != null) { + final boolean imeReqVisible = + (viewRootImpl.getInsetsController().getRequestedVisibleTypes() + & WindowInsets.Type.ime()) != 0; + resultReceiver.send( + !imeReqVisible ? InputMethodManager.RESULT_UNCHANGED_HIDDEN + : InputMethodManager.RESULT_HIDDEN, null); + } viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime()); } return true; -- GitLab From fabffd0920cf47faf629e1b1584094976459e780 Mon Sep 17 00:00:00 2001 From: Ale Nijamkin Date: Tue, 15 Oct 2024 20:57:46 +0000 Subject: [PATCH 209/441] [flexiglass] Removes device_entry_udfps_refactor from scene.md Flag has shipped. Bug: 279440316 Change-Id: I60dcac15b55c8fc5eecf42de86dfcd7172976a36 Test: N/A Flag: NONE documentation change only --- packages/SystemUI/docs/scene.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md index 234c7a032d2e..bf15b4feadfb 100644 --- a/packages/SystemUI/docs/scene.md +++ b/packages/SystemUI/docs/scene.md @@ -63,7 +63,7 @@ the instructions below to turn it on. NOTE: in case these instructions become stale and don't actually enable the framework, please make sure `SceneContainerFlag.isEnabled` in the [`SceneContainerFlag.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt) -file evalutes to `true`. +file evaluates to `true`. 1. Set a collection of **aconfig flags** to `true` by running the following commands: @@ -73,7 +73,6 @@ file evalutes to `true`. $ adb shell device_config override systemui com.android.systemui.migrate_clocks_to_blueprint true $ adb shell device_config override systemui com.android.systemui.notification_avalanche_throttle_hun true $ adb shell device_config override systemui com.android.systemui.predictive_back_sysui true - $ adb shell device_config override systemui com.android.systemui.device_entry_udfps_refactor true $ adb shell device_config override systemui com.android.systemui.scene_container true ``` 2. **Restart** System UI by issuing the following command: -- GitLab From 1ff4d2ffd2613af0095dc57eb843c5f0967d0f28 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 15 Oct 2024 21:12:45 +0000 Subject: [PATCH 210/441] KeyGestureControllerTests: Cleanup InputManagerGlobal test session The test fails to clean up properly after changing the InputManagerGlobal session for testing. Bug: 370828465 Test: atest InputTests Flag: TEST_ONLY Change-Id: Ibbec7e72116117dcc725f7fd079041620b2556a9 --- .../com/android/server/input/KeyGestureControllerTests.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 4ae06a4f9812..7526737f60bf 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -46,6 +46,7 @@ import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule import junitparams.JUnitParamsRunner import junitparams.Parameters +import org.junit.After import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -128,6 +129,13 @@ class KeyGestureControllerTests { currentPid = Process.myPid() } + @After + fun teardown() { + if (this::inputManagerGlobalSession.isInitialized) { + inputManagerGlobalSession.close() + } + } + private fun setupBehaviors() { Mockito.`when`( resources.getBoolean( -- GitLab From 231f87ca18f35ef04e4dcbd74af0fe53bc90b819 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 15 Oct 2024 21:15:05 +0000 Subject: [PATCH 211/441] UinputRecordingIntegrationTests: Remove FlakyTest annotation The culprit for the test failure was resolved. Bug: 370828465 Test: Presubmit Flag: TEST_ONLY Change-Id: I8dda7134e891b1053cc261990309585c41f94cd2 --- .../com/android/test/input/UinputRecordingIntegrationTests.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt index 1a29c0f24edf..c61a25021949 100644 --- a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt +++ b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt @@ -21,7 +21,6 @@ import android.cts.input.EventVerifier import android.graphics.PointF import android.hardware.input.InputManager import android.os.ParcelFileDescriptor -import android.platform.test.annotations.FlakyTest import android.util.Log import android.util.Size import android.view.InputEvent @@ -108,7 +107,6 @@ class UinputRecordingIntegrationTests { parser = InputJsonParser(instrumentation.context) } - @FlakyTest(bugId = 366602644) @Test fun testEvemuRecording() { VirtualDisplayActivityScenario.AutoClose( -- GitLab From 6fe30c4440a9702adec10e7fcc70c1e10f97c484 Mon Sep 17 00:00:00 2001 From: Shashwat Razdan Date: Wed, 9 Oct 2024 14:03:37 -0700 Subject: [PATCH 212/441] Add per-package lock in setAppFunctionEnabled. Bug: 357551503 Test: CTS Flag: android.app.appfunctions.flags.enable_app_function_manager Change-Id: I3c46a6ef1cdb07e6e1a30a9d1417fac8c55574f3 --- .../AppFunctionManagerServiceImpl.java | 34 +++++-- services/tests/appfunctions/Android.bp | 2 + .../AppFunctionManagerServiceImplTest.kt | 89 +++++++++++++++++++ 3 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index c5fef191c52c..2ee7561bd631 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -65,12 +65,13 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.DumpUtils; import com.android.server.SystemService.TargetUser; -import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback; -import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.Collections; +import java.util.Map; import java.util.Objects; +import java.util.WeakHashMap; import java.util.concurrent.CompletionException; import java.util.concurrent.Executor; @@ -83,7 +84,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { private final ServiceHelper mInternalServiceHelper; private final ServiceConfig mServiceConfig; private final Context mContext; - private final Object mLock = new Object(); + private final Map mLocks = new WeakHashMap<>(); + public AppFunctionManagerServiceImpl(@NonNull Context context) { this( @@ -321,9 +323,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { THREAD_POOL_EXECUTOR.execute( () -> { try { - // TODO(357551503): Instead of holding a global lock, hold a per-package - // lock. - synchronized (mLock) { + synchronized (getLockForPackage(callingPackage)) { setAppFunctionEnabledInternalLocked( callingPackage, functionIdentifier, userHandle, enabledState); } @@ -351,7 +351,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { * process. */ @WorkerThread - @GuardedBy("mLock") + @GuardedBy("getLockForPackage(callingPackage)") private void setAppFunctionEnabledInternalLocked( @NonNull String callingPackage, @NonNull String functionIdentifier, @@ -545,6 +545,26 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { }); } } + /** + * Retrieves the lock object associated with the given package name. + * + * This method returns the lock object from the {@code mLocks} map if it exists. + * If no lock is found for the given package name, a new lock object is created, + * stored in the map, and returned. + */ + @VisibleForTesting + @NonNull + Object getLockForPackage(String callingPackage) { + // Synchronized the access to mLocks to prevent race condition. + synchronized (mLocks) { + // By using a WeakHashMap, we allow the garbage collector to reclaim memory by removing + // entries associated with unused callingPackage keys. Therefore, we remove the null + // values before getting/computing a new value. The goal is to not let the size of this + // map grow without an upper bound. + mLocks.values().removeAll(Collections.singleton(null)); // Remove null values + return mLocks.computeIfAbsent(callingPackage, k -> new Object()); + } + } private static class AppFunctionMetadataObserver implements ObserverCallback { @Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter; diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp index c841643c6654..836f90b992d6 100644 --- a/services/tests/appfunctions/Android.bp +++ b/services/tests/appfunctions/Android.bp @@ -36,7 +36,9 @@ android_test { "androidx.test.core", "androidx.test.runner", "androidx.test.ext.truth", + "androidx.core_core-ktx", "kotlin-test", + "kotlinx_coroutines_test", "platform-test-annotations", "services.appfunctions", "servicestests-core-utils", diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt new file mode 100644 index 000000000000..a69e9025bfa0 --- /dev/null +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2024 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.appfunctions + +import android.app.appfunctions.flags.Flags +import android.content.Context +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@RequiresFlagsEnabled(Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) +class AppFunctionManagerServiceImplTest { + @get:Rule + val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + + private val context: Context + get() = ApplicationProvider.getApplicationContext() + + private val serviceImpl = AppFunctionManagerServiceImpl(context) + + @Test + fun testGetLockForPackage_samePackage() { + val packageName = "com.example.app" + val lock1 = serviceImpl.getLockForPackage(packageName) + val lock2 = serviceImpl.getLockForPackage(packageName) + + // Assert that the same lock object is returned for the same package name + assertThat(lock1).isEqualTo(lock2) + } + + @Test + fun testGetLockForPackage_differentPackages() { + val packageName1 = "com.example.app1" + val packageName2 = "com.example.app2" + val lock1 = serviceImpl.getLockForPackage(packageName1) + val lock2 = serviceImpl.getLockForPackage(packageName2) + + // Assert that different lock objects are returned for different package names + assertThat(lock1).isNotEqualTo(lock2) + } + + @Ignore("Hard to deterministically trigger the garbage collector.") + @Test + fun testWeakReference_garbageCollected_differentLockAfterGC() = runTest { + // Create a large number of temporary objects to put pressure on the GC + val tempObjects = MutableList(10000000) { Any() } + var callingPackage: String? = "com.example.app" + var lock1: Any? = serviceImpl.getLockForPackage(callingPackage) + callingPackage = null // Set the key to null + val lock1Hash = lock1.hashCode() + lock1 = null + + // Create memory pressure + repeat(3) { + for (i in 1..100) { + "a".repeat(10000) + } + System.gc() // Suggest garbage collection + System.runFinalization() + } + // Get the lock again - it should be a different object now + val lock2 = serviceImpl.getLockForPackage("com.example.app") + // Assert that the lock objects are different + assertThat(lock1Hash).isNotEqualTo(lock2.hashCode()) + } +} -- GitLab From 9d6d6f52563518584c58a076245051643664cab4 Mon Sep 17 00:00:00 2001 From: Jared Duke Date: Fri, 26 Jul 2024 22:27:15 +0000 Subject: [PATCH 213/441] Use WeaklyReferencedCallback annotation Apply this annotation to several callback types to ensure write-only member fields are kept during optimization, helping preserve lifecycle semantics. A follow-up change will clean up some of the @KeepForWeakReference field annotations that should no longer be necessary. Bug: 349245577 Test: FULL_SYSTEM_OPTIMIZE_JAVA=true m services Flag: EXEMPT bugfix Change-Id: Ied71a08d2b1a71b0e0789d863af6b61ae9b0918f --- core/java/android/telephony/TelephonyCallback.java | 2 ++ core/java/android/view/DisplayEventReceiver.java | 2 ++ .../com/android/server/display/color/ColorDisplayService.java | 2 ++ 3 files changed, 6 insertions(+) diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index b8b84d93c97c..c6ee4973f37e 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -33,6 +33,7 @@ import android.telephony.ims.MediaThreshold; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.WeaklyReferencedCallback; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.flags.Flags; @@ -69,6 +70,7 @@ import java.util.stream.Collectors; * its manifest file. Where permissions apply, they are noted in the * appropriate sub-interfaces. */ +@WeaklyReferencedCallback public class TelephonyCallback { private static final String LOG_TAG = "TelephonyCallback"; /** diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index 18080e4478fc..fc7a65dbdc41 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -24,6 +24,7 @@ import android.os.MessageQueue; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.WeaklyReferencedCallback; import dalvik.annotation.optimization.FastNative; @@ -40,6 +41,7 @@ import java.lang.ref.WeakReference; * * @hide */ +@WeaklyReferencedCallback public abstract class DisplayEventReceiver { /** diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 3883604b7134..d23e76f4eb51 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -74,6 +74,7 @@ import android.view.animation.AnimationUtils; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.WeaklyReferencedCallback; import com.android.internal.util.DumpUtils; import com.android.server.DisplayThread; import com.android.server.LocalServices; @@ -1730,6 +1731,7 @@ public final class ColorDisplayService extends SystemService { /** * Interface for applying transforms to a given AppWindow. */ + @WeaklyReferencedCallback public interface ColorTransformController { /** -- GitLab From 23a67d4a33c299730923afa320586c29aee32f23 Mon Sep 17 00:00:00 2001 From: Jorge Gil Date: Tue, 15 Oct 2024 17:58:11 +0000 Subject: [PATCH 214/441] Force show system bars while menus are open in desktop immersive Keeps the bars showing while the Handle Menu or Manage Windows menus are open, and adjusts their position on screen to account for App Header padding. This requires changing these windows to use AdditionalSystemViewContainer because setting forciblyShownTypes on windowless windows has no effect on the system bars' visibility. Flag: com.android.window.flags.enable_fully_immersive_in_desktop Bug: 372319957 Test: enter desktop immersive with YT on Chrome, check menus force-show the system bars and they're anchored to the App Chip instead of the top of the screen/task. Change-Id: I2cfd7b416c66ed89d2e2556d644c01bcab846dcd --- .../DesktopHandleManageWindowsMenu.kt | 16 +-- .../DesktopHeaderManageWindowsMenu.kt | 58 +++++++-- .../DesktopModeWindowDecoration.java | 22 +++- .../wm/shell/windowdecor/HandleMenu.kt | 48 +++++--- .../shell/windowdecor/WindowDecoration.java | 6 + .../AdditionalSystemViewContainer.kt | 3 + .../DesktopHeaderManageWindowsMenuTest.kt | 111 ++++++++++++++++++ .../DesktopModeWindowDecorationTests.java | 38 +++++- .../wm/shell/windowdecor/HandleMenuTest.kt | 57 +++++++-- .../windowdecor/WindowDecorationTests.java | 17 +++ 10 files changed, 323 insertions(+), 53 deletions(-) create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt index 13a805aef0f1..e71b4f3abf14 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt @@ -95,15 +95,15 @@ class DesktopHandleManageWindowsMenu( override fun addToContainer(menuView: ManageWindowsView) { val menuPosition = calculateMenuPosition() menuViewContainer = AdditionalSystemViewContainer( - windowManagerWrapper, - callerTaskInfo.taskId, - menuPosition.x, - menuPosition.y, - menuView.menuWidth, - menuView.menuHeight, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + windowManagerWrapper = windowManagerWrapper, + taskId = callerTaskInfo.taskId, + x = menuPosition.x, + y = menuPosition.y, + width = menuView.menuWidth, + height = menuView.menuHeight, + flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, - menuView.rootView + view = menuView.rootView, ) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt index 05391a8343a5..173bc08970ca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt @@ -22,14 +22,19 @@ import android.graphics.PixelFormat import android.graphics.Point import android.view.SurfaceControl import android.view.SurfaceControlViewHost +import android.view.WindowInsets.Type.systemBars import android.view.WindowManager import android.view.WindowlessWindowManager import android.window.TaskConstants import android.window.TaskSnapshot import androidx.compose.ui.graphics.toArgb +import com.android.internal.annotations.VisibleForTesting +import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer import com.android.wm.shell.windowdecor.common.DecorThemeUtil @@ -41,9 +46,12 @@ import java.util.function.Supplier */ class DesktopHeaderManageWindowsMenu( private val callerTaskInfo: RunningTaskInfo, + private val x: Int, + private val y: Int, private val displayController: DisplayController, private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer, context: Context, + private val desktopRepository: DesktopRepository, private val surfaceControlBuilderSupplier: Supplier, private val surfaceControlTransactionSupplier: Supplier, snapshotList: List>, @@ -53,7 +61,8 @@ class DesktopHeaderManageWindowsMenu( context, DecorThemeUtil(context).getColorScheme(callerTaskInfo).background.toArgb() ) { - private var menuViewContainer: AdditionalViewContainer? = null + @VisibleForTesting + var menuViewContainer: AdditionalViewContainer? = null init { show(snapshotList, onIconClickListener, onOutsideClickListener) @@ -64,8 +73,37 @@ class DesktopHeaderManageWindowsMenu( } override fun addToContainer(menuView: ManageWindowsView) { - val taskBounds = callerTaskInfo.getConfiguration().windowConfiguration.bounds - val menuPosition = Point(taskBounds.left, taskBounds.top) + val menuPosition = Point(x, y) + val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or + WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + menuViewContainer = if (Flags.enableFullyImmersiveInDesktop() + && desktopRepository.isTaskInFullImmersiveState(callerTaskInfo.taskId)) { + // Use system view container so that forcibly shown system bars take effect in + // immersive. + createAsSystemViewContainer(menuPosition, flags) + } else { + createAsViewHostContainer(menuPosition, flags) + } + } + + private fun createAsSystemViewContainer(position: Point, flags: Int): AdditionalViewContainer { + return AdditionalSystemViewContainer( + windowManagerWrapper = WindowManagerWrapper( + context.getSystemService(WindowManager::class.java) + ), + taskId = callerTaskInfo.taskId, + x = position.x, + y = position.y, + width = menuView.menuWidth, + height = menuView.menuHeight, + flags = flags, + forciblyShownTypes = systemBars(), + view = menuView.rootView + ) + } + + private fun createAsViewHostContainer(position: Point, flags: Int): AdditionalViewContainer { val builder = surfaceControlBuilderSupplier.get() rootTdaOrganizer.attachToDisplayArea(callerTaskInfo.displayId, builder) val leash = builder @@ -73,11 +111,10 @@ class DesktopHeaderManageWindowsMenu( .setContainerLayer() .build() val lp = WindowManager.LayoutParams( - menuView.menuWidth, menuView.menuHeight, + menuView.menuWidth, + menuView.menuHeight, WindowManager.LayoutParams.TYPE_APPLICATION, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, + flags, PixelFormat.TRANSPARENT ) val windowManager = WindowlessWindowManager( @@ -93,11 +130,12 @@ class DesktopHeaderManageWindowsMenu( menuView.let { viewHost.setView(it.rootView, lp) } val t = surfaceControlTransactionSupplier.get() t.setLayer(leash, TaskConstants.TASK_CHILD_LAYER_FLOATING_MENU) - .setPosition(leash, menuPosition.x.toFloat(), menuPosition.y.toFloat()) + .setPosition(leash, position.x.toFloat(), position.y.toFloat()) .show(leash) t.apply() - menuViewContainer = AdditionalViewHostViewContainer( - leash, viewHost, + return AdditionalViewHostViewContainer( + leash, + viewHost, surfaceControlTransactionSupplier ) } 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 d3b7ca15856f..6eb20b9e3ae5 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 @@ -454,7 +454,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration { closeHandleMenu(); return Unit.INSTANCE; - } + }, + /* forceShowSystemBars= */ inDesktopImmersive ); if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) { notifyCaptionStateChanged(); @@ -1316,9 +1329,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration Unit, onCloseMenuClickListener: () -> Unit, onOutsideTouchListener: () -> Unit, + forceShowSystemBars: Boolean = false, ) { val ssg = SurfaceSyncGroup(TAG) val t = SurfaceControl.Transaction() @@ -139,6 +142,7 @@ class HandleMenu( onOpenByDefaultClickListener = onOpenByDefaultClickListener, onCloseMenuClickListener = onCloseMenuClickListener, onOutsideTouchListener = onOutsideTouchListener, + forceShowSystemBars = forceShowSystemBars, ) ssg.addTransaction(t) ssg.markSyncReady() @@ -157,7 +161,8 @@ class HandleMenu( openInBrowserClickListener: (Intent) -> Unit, onOpenByDefaultClickListener: () -> Unit, onCloseMenuClickListener: () -> Unit, - onOutsideTouchListener: () -> Unit + onOutsideTouchListener: () -> Unit, + forceShowSystemBars: Boolean = false, ) { val handleMenuView = HandleMenuView( context = context, @@ -185,7 +190,7 @@ class HandleMenu( val x = handleMenuPosition.x.toInt() val y = handleMenuPosition.y.toInt() handleMenuViewContainer = - if (!taskInfo.isFreeform && Flags.enableHandleInputFix()) { + if ((!taskInfo.isFreeform && Flags.enableHandleInputFix()) || forceShowSystemBars) { AdditionalSystemViewContainer( windowManagerWrapper = windowManagerWrapper, taskId = taskInfo.taskId, @@ -196,7 +201,8 @@ class HandleMenu( flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - view = handleMenuView.rootView + view = handleMenuView.rootView, + forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 } ) } else { parentDecor.addWindow( @@ -210,15 +216,15 @@ class HandleMenu( /** * Updates handle menu's position variables to reflect its next position. */ - private fun updateHandleMenuPillPositions(captionX: Int) { + private fun updateHandleMenuPillPositions(captionX: Int, captionY: Int) { val menuX: Int val menuY: Int val taskBounds = taskInfo.getConfiguration().windowConfiguration.bounds - updateGlobalMenuPosition(taskBounds, captionX) + updateGlobalMenuPosition(taskBounds, captionX, captionY) if (layoutResId == R.layout.desktop_mode_app_header) { // Align the handle menu to the left side of the caption. menuX = marginMenuStart - menuY = marginMenuTop + menuY = captionY + marginMenuTop } else { if (Flags.enableHandleInputFix()) { // In a focused decor, we use global coordinates for handle menu. Therefore we @@ -228,26 +234,26 @@ class HandleMenu( menuY = globalMenuPosition.y } else { menuX = (taskBounds.width() / 2) - (menuWidth / 2) - menuY = marginMenuTop + menuY = captionY + marginMenuTop } } // Handle Menu position setup. handleMenuPosition.set(menuX.toFloat(), menuY.toFloat()) } - private fun updateGlobalMenuPosition(taskBounds: Rect, captionX: Int) { + private fun updateGlobalMenuPosition(taskBounds: Rect, captionX: Int, captionY: Int) { val nonFreeformX = captionX + (captionWidth / 2) - (menuWidth / 2) when { taskInfo.isFreeform -> { globalMenuPosition.set( /* x = */ taskBounds.left + marginMenuStart, - /* y = */ taskBounds.top + marginMenuTop + /* y = */ taskBounds.top + captionY + marginMenuTop ) } taskInfo.isFullscreen -> { globalMenuPosition.set( /* x = */ nonFreeformX, - /* y = */ marginMenuTop + /* y = */ marginMenuTop + captionY ) } taskInfo.isMultiWindow -> { @@ -261,13 +267,13 @@ class HandleMenu( SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> { globalMenuPosition.set( /* x = */ leftOrTopStageBounds.width() + nonFreeformX, - /* y = */ marginMenuTop + /* y = */ captionY + marginMenuTop ) } SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> { globalMenuPosition.set( /* x = */ nonFreeformX, - /* y = */ marginMenuTop + /* y = */ captionY + marginMenuTop ) } } @@ -280,10 +286,11 @@ class HandleMenu( */ fun relayout( t: SurfaceControl.Transaction, - captionX: Int + captionX: Int, + captionY: Int, ) { handleMenuViewContainer?.let { container -> - updateHandleMenuPillPositions(captionX) + updateHandleMenuPillPositions(captionX, captionY) container.setPosition(t, handleMenuPosition.x, handleMenuPosition.y) } } @@ -675,7 +682,8 @@ interface HandleMenuFactory { openInBrowserIntent: Intent?, captionWidth: Int, captionHeight: Int, - captionX: Int + captionX: Int, + captionY: Int, ): HandleMenu } @@ -694,7 +702,8 @@ object DefaultHandleMenuFactory : HandleMenuFactory { openInBrowserIntent: Intent?, captionWidth: Int, captionHeight: Int, - captionX: Int + captionX: Int, + captionY: Int, ): HandleMenu { return HandleMenu( parentDecor, @@ -709,7 +718,8 @@ object DefaultHandleMenuFactory : HandleMenuFactory { openInBrowserIntent, captionWidth, captionHeight, - captionX + captionX, + captionY, ) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index ce5cfd0bdc36..6b3b357f2f7b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -256,6 +256,8 @@ public abstract class WindowDecoration outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width(); outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2; + outResult.mCaptionY = 0; + outResult.mCaptionTopPadding = params.mCaptionTopPadding; updateDecorationContainerSurface(startT, outResult); updateCaptionContainerSurface(startT, outResult); @@ -786,6 +788,8 @@ public abstract class WindowDecoration int mCaptionHeight; int mCaptionWidth; int mCaptionX; + int mCaptionY; + int mCaptionTopPadding; final Region mCustomizableCaptionRegion = Region.obtain(); int mWidth; int mHeight; @@ -797,6 +801,8 @@ public abstract class WindowDecoration mCaptionHeight = 0; mCaptionWidth = 0; mCaptionX = 0; + mCaptionY = 0; + mCaptionTopPadding = 0; mCustomizableCaptionRegion.setEmpty(); mRootView = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt index 1be26f080ac8..8b6aaaf619e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt @@ -23,6 +23,7 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.SurfaceControl import android.view.View +import android.view.WindowInsets import android.view.WindowManager import com.android.wm.shell.windowdecor.WindowManagerWrapper @@ -38,6 +39,7 @@ class AdditionalSystemViewContainer( width: Int, height: Int, flags: Int, + @WindowInsets.Type.InsetsType forciblyShownTypes: Int = 0, override val view: View ) : AdditionalViewContainer() { val lp: WindowManager.LayoutParams = WindowManager.LayoutParams( @@ -49,6 +51,7 @@ class AdditionalSystemViewContainer( title = "Additional view container of Task=$taskId" gravity = Gravity.LEFT or Gravity.TOP setTrustedOverlay() + this.forciblyShownTypes = forciblyShownTypes } constructor( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt new file mode 100644 index 000000000000..f9f760e3f482 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 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.windowdecor + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.SurfaceControl +import androidx.test.filters.SmallTest +import com.android.window.flags.Flags +import com.android.wm.shell.MockToken +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +/** + * Tests for [DesktopHeaderManageWindowsMenu]. + * + * Build/Install/Run: + * atest WMShellUnitTests:DesktopHeaderManageWindowsMenuTest + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { + + @JvmField + @Rule + val setFlagsRule: SetFlagsRule = SetFlagsRule() + + private lateinit var desktopRepository: DesktopRepository + private lateinit var menu: DesktopHeaderManageWindowsMenu + + @Before + fun setUp() { + desktopRepository = DesktopRepository( + context = context, + shellInit = ShellInit(TestShellExecutor()), + persistentRepository = mock(), + mainCoroutineScope = mock() + ) + } + + @After + fun tearDown() { + menu.close() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun testShow_forImmersiveTask_usesSystemViewContainer() { + val task = createFreeformTask() + desktopRepository.setTaskInFullImmersiveState( + displayId = task.displayId, + taskId = task.taskId, + immersive = true + ) + + menu = createMenu(task) + + assertThat(menu.menuViewContainer).isInstanceOf(AdditionalSystemViewContainer::class.java) + } + + private fun createMenu(task: RunningTaskInfo) = DesktopHeaderManageWindowsMenu( + callerTaskInfo = task, + x = 0, + y = 0, + displayController = mock(), + rootTdaOrganizer = mock(), + context = context, + desktopRepository = desktopRepository, + surfaceControlBuilderSupplier = { SurfaceControl.Builder() }, + surfaceControlTransactionSupplier = { SurfaceControl.Transaction() }, + snapshotList = emptyList(), + onIconClickListener = {}, + onOutsideClickListener = {}, + ) + + private fun createFreeformTask(): RunningTaskInfo = TestRunningTaskInfoBuilder() + .setToken(MockToken().token()) + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_FREEFORM) + .build() +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 1d11d2e8ff06..320887212f54 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -259,7 +259,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY); doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(), - anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt())) + anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt(), + anyInt())) .thenReturn(mMockHandleMenu); when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(), @@ -1070,7 +1071,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { openInBrowserCaptor.capture(), any(), any(), - any() + any(), + anyBoolean() ); openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1)); @@ -1099,7 +1101,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { openInBrowserCaptor.capture(), any(), any(), - any() + any(), + anyBoolean() ); openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1)); @@ -1151,7 +1154,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { any(), any(), closeClickListener.capture(), - any() + any(), + anyBoolean() ); closeClickListener.getValue().invoke(); @@ -1160,6 +1164,30 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { assertFalse(decoration.isHandleMenuActive()); } + @Test + public void createHandleMenu_immersiveWindow_forceShowsSystemBars() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + true /* relayout */); + when(mMockDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) + .thenReturn(true); + + createHandleMenu(decoration); + + verify(mMockHandleMenu).show( + any(), + any(), + any(), + any(), + any(), + any(), + any(), + any(), + any(), + /* forceShowSystemBars= */ eq(true) + ); + } + @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) public void notifyCaptionStateChanged_flagDisabled_doNoNotify() { @@ -1301,7 +1329,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), argThat(intent -> (uri == null && intent == null) || intent.getData().equals(uri)), - anyInt(), anyInt(), anyInt()); + anyInt(), anyInt(), anyInt(), anyInt()); } private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt index 1820133a4795..9544fa823b5a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt @@ -33,6 +33,7 @@ import android.view.LayoutInflater import android.view.SurfaceControl import android.view.SurfaceControlViewHost import android.view.View +import android.view.WindowInsets.Type.systemBars import android.view.WindowManager import androidx.core.graphics.toPointF import androidx.test.filters.SmallTest @@ -186,13 +187,35 @@ class HandleMenuTest : ShellTestCase() { assertEquals(expected.toPointF(), handleMenu.handleMenuPosition) } - private fun createTaskInfo(windowingMode: Int, splitPosition: Int) { + @Test + fun testCreate_forceShowSystemBars_usesSystemViewContainer() { + createTaskInfo(WINDOWING_MODE_FREEFORM) + + handleMenu = createAndShowHandleMenu(forceShowSystemBars = true) + + // Only AdditionalSystemViewContainer supports force showing system bars. + assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer) + } + + @Test + fun testCreate_forceShowSystemBars() { + createTaskInfo(WINDOWING_MODE_FREEFORM) + + handleMenu = createAndShowHandleMenu(forceShowSystemBars = true) + + val types = (handleMenu.handleMenuViewContainer as AdditionalSystemViewContainer) + .lp.forciblyShownTypes + assertTrue((types and systemBars()) != 0) + } + + private fun createTaskInfo(windowingMode: Int, splitPosition: Int? = null) { val taskDescriptionBuilder = ActivityManager.TaskDescription.Builder() .setBackgroundColor(Color.YELLOW) val bounds = when (windowingMode) { WINDOWING_MODE_FULLSCREEN -> DISPLAY_BOUNDS WINDOWING_MODE_FREEFORM -> FREEFORM_BOUNDS WINDOWING_MODE_MULTI_WINDOW -> { + checkNotNull(splitPosition) if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) { SPLIT_LEFT_BOUNDS } else { @@ -208,14 +231,19 @@ class HandleMenuTest : ShellTestCase() { .setBounds(bounds) .setVisible(true) .build() - whenever(splitScreenController.getSplitPosition(any())).thenReturn(splitPosition) - whenever(splitScreenController.getStageBounds(any(), any())).thenAnswer { - (it.arguments.first() as Rect).set(SPLIT_LEFT_BOUNDS) - (it.arguments[1] as Rect).set(SPLIT_RIGHT_BOUNDS) + if (windowingMode == WINDOWING_MODE_MULTI_WINDOW) { + whenever(splitScreenController.getSplitPosition(any())).thenReturn(splitPosition) + whenever(splitScreenController.getStageBounds(any(), any())).thenAnswer { + (it.arguments.first() as Rect).set(SPLIT_LEFT_BOUNDS) + (it.arguments[1] as Rect).set(SPLIT_RIGHT_BOUNDS) + } } } - private fun createAndShowHandleMenu(splitPosition: Int): HandleMenu { + private fun createAndShowHandleMenu( + splitPosition: Int? = null, + forceShowSystemBars: Boolean = false, + ): HandleMenu { val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) { R.layout.desktop_mode_app_header } else { @@ -225,6 +253,7 @@ class HandleMenuTest : ShellTestCase() { WINDOWING_MODE_FULLSCREEN -> (DISPLAY_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2) WINDOWING_MODE_FREEFORM -> 0 WINDOWING_MODE_MULTI_WINDOW -> { + checkNotNull(splitPosition) if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) { (SPLIT_LEFT_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2) } else { @@ -238,9 +267,21 @@ class HandleMenuTest : ShellTestCase() { layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true, shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false, null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50, - captionX = captionX + captionX = captionX, + captionY = 0, + ) + handleMenu.show( + onToDesktopClickListener = mock(), + onToFullscreenClickListener = mock(), + onToSplitScreenClickListener = mock(), + onNewWindowClickListener = mock(), + onManageWindowsClickListener = mock(), + openInBrowserClickListener = mock(), + onOpenByDefaultClickListener = mock(), + onCloseMenuClickListener = mock(), + onOutsideTouchListener = mock(), + forceShowSystemBars = forceShowSystemBars ) - handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock(), mock()) return handleMenu } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index bb41e9c81ece..fb17ae93030d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -514,6 +514,23 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); } + @Test + public void testRelayout_withPadding_setsOnResult() { + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setBounds(TASK_BOUNDS) + .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y) + .setVisible(true) + .build(); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); + mRelayoutParams.mCaptionTopPadding = 50; + + windowDecor.relayout(taskInfo, false /* applyStartTransactionOnDraw */, + true /* hasGlobalFocus */); + + assertEquals(50, mRelayoutResult.mCaptionTopPadding); + } + @Test public void testRelayout_fluidResizeEnabled_freeformTask_setTaskSurfaceColor() { StaticMockitoSession mockitoSession = mockitoSession().mockStatic( -- GitLab From 51be14deb1445aa7c643be660ccaa17559d37941 Mon Sep 17 00:00:00 2001 From: Xiaowen Lei Date: Fri, 11 Oct 2024 17:41:24 +0000 Subject: [PATCH 215/441] Use NotificationProgressDrawable for ProgressStyle notifications. Also tint the indeterminate drawable with the color passed from the API. Fixed a couple of bugs in NotificationProgressDrawable, caught by the tests. Flag: android.app.api_rich_ongoing Bug: 367805202 Fix: 372907485 Test: post a ProgressStyle Notification Test: patch ag/29675286 and run ProgressStyleNotificationScreenshotTest Change-Id: I2449f755fa359278351986f17b8744f6e2bc5827 --- .../widget/NotificationProgressBar.java | 49 +++++++++++++++++-- .../widget/NotificationProgressDrawable.java | 21 ++++++-- .../res/drawable/notification_progress.xml | 37 ++++++++++++++ ...otification_template_material_progress.xml | 7 ++- ...ion_template_notification_progress_bar.xml | 23 +++++++++ core/res/res/values/dimens.xml | 16 ++++++ core/res/res/values/styles_material.xml | 4 ++ core/res/res/values/symbols.xml | 8 +++ .../widget/NotificationProgressBarTest.java | 1 - 9 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 core/res/res/drawable/notification_progress.xml create mode 100644 core/res/res/layout/notification_template_notification_progress_bar.xml diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java index 3e597d73f217..d3b1f972a955 100644 --- a/core/java/com/android/internal/widget/NotificationProgressBar.java +++ b/core/java/com/android/internal/widget/NotificationProgressBar.java @@ -20,16 +20,20 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification.ProgressStyle; import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.util.AttributeSet; +import android.util.Log; import android.view.RemotableViewMethod; import android.widget.ProgressBar; import android.widget.RemoteViews; import androidx.annotation.ColorInt; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.internal.widget.NotificationProgressDrawable.Part; @@ -49,7 +53,13 @@ import java.util.TreeSet; */ @RemoteViews.RemoteView public class NotificationProgressBar extends ProgressBar { + private static final String TAG = "NotificationProgressBar"; + private NotificationProgressModel mProgressModel; + + @Nullable + private List mProgressDrawableParts = null; + @Nullable private Drawable mProgressTrackerDrawable = null; @@ -58,7 +68,7 @@ public class NotificationProgressBar extends ProgressBar { } public NotificationProgressBar(Context context, AttributeSet attrs) { - this(context, attrs, com.android.internal.R.attr.progressBarStyle); + this(context, attrs, R.attr.progressBarStyle); } public NotificationProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { @@ -82,10 +92,42 @@ public class NotificationProgressBar extends ProgressBar { "Bundle shouldn't be null"); mProgressModel = NotificationProgressModel.fromBundle(bundle); + + if (mProgressModel.isIndeterminate()) { + final int indeterminateColor = mProgressModel.getIndeterminateColor(); + setIndeterminateTintList(ColorStateList.valueOf(indeterminateColor)); + } else { + mProgressDrawableParts = processAndConvertToDrawableParts(mProgressModel.getSegments(), + mProgressModel.getPoints(), + mProgressModel.getProgress(), mProgressModel.isStyledByProgress()); + + try { + final NotificationProgressDrawable drawable = getNotificationProgressDrawable(); + drawable.setParts(mProgressDrawableParts); + } catch (IllegalStateException ex) { + Log.e(TAG, "Can't set parts because can't get NotificationProgressDrawable", ex); + } + } } - private void setProgressModel(@NonNull NotificationProgressModel model) { - mProgressModel = model; + @NonNull + private NotificationProgressDrawable getNotificationProgressDrawable() { + final Drawable d = getProgressDrawable(); + if (d == null) { + throw new IllegalStateException("getProgressDrawable() returns null"); + } + if (!(d instanceof LayerDrawable)) { + throw new IllegalStateException("getProgressDrawable() doesn't return a LayerDrawable"); + } + + final Drawable layer = ((LayerDrawable) d).findDrawableByLayerId(R.id.background); + if (!(layer instanceof NotificationProgressDrawable)) { + throw new IllegalStateException( + "Couldn't get NotificationProgressDrawable, retrieved drawable is: " + ( + layer != null ? layer.toString() : null)); + } + + return (NotificationProgressDrawable) layer; } /** @@ -97,7 +139,6 @@ public class NotificationProgressBar extends ProgressBar { public void setProgressTrackerIcon(@Nullable Icon icon) { } - /** * Async version of {@link #setProgressTrackerIcon} */ diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java index 89ef8759a169..2be7273e5d10 100644 --- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java +++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java @@ -45,6 +45,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Objects; /** @@ -156,11 +157,18 @@ public final class NotificationProgressDrawable extends Drawable { } /** - * + * Set the segments and points that constitute the drawable. */ - public void setParts(@NonNull Part... parts) { + public void setParts(List parts) { mParts.clear(); - mParts.addAll(Arrays.asList(parts)); + mParts.addAll(parts); + } + + /** + * Set the segments and points that constitute the drawable. + */ + public void setParts(@NonNull Part... parts) { + setParts(Arrays.asList(parts)); } @Override @@ -379,7 +387,7 @@ public final class NotificationProgressDrawable extends Drawable { if (state.mThemeAttrsPoints != null) { final TypedArray a = t.resolveAttributes( state.mThemeAttrsPoints, R.styleable.NotificationProgressDrawablePoints); - updateSegmentsFromTypedArray(a); + updatePointsFromTypedArray(a); a.recycle(); } } @@ -651,9 +659,11 @@ public final class NotificationProgressDrawable extends Drawable { State(@NonNull State orig, @Nullable Resources res) { mChangingConfigurations = orig.mChangingConfigurations; + mSegSegGap = orig.mSegSegGap; + mSegPointGap = orig.mSegPointGap; + mStrokeWidth = orig.mStrokeWidth; mStrokeColor = orig.mStrokeColor; mFadedStrokeColor = orig.mFadedStrokeColor; - mStrokeWidth = orig.mStrokeWidth; mStrokeDashWidth = orig.mStrokeDashWidth; mStrokeDashGap = orig.mStrokeDashGap; mPointRadius = orig.mPointRadius; @@ -791,6 +801,7 @@ public final class NotificationProgressDrawable extends Drawable { final State state = mState; mStrokePaint.setStrokeWidth(state.mStrokeWidth); + mDashedStrokePaint.setStrokeWidth(state.mStrokeWidth); if (state.mStrokeDashWidth != 0.0f) { final DashPathEffect e = new DashPathEffect( diff --git a/core/res/res/drawable/notification_progress.xml b/core/res/res/drawable/notification_progress.xml new file mode 100644 index 000000000000..3a6b60077045 --- /dev/null +++ b/core/res/res/drawable/notification_progress.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/core/res/res/layout/notification_template_material_progress.xml b/core/res/res/layout/notification_template_material_progress.xml index fdcefccdbf16..75827a279ff7 100644 --- a/core/res/res/layout/notification_template_material_progress.xml +++ b/core/res/res/layout/notification_template_material_progress.xml @@ -75,12 +75,11 @@ /> - + + + diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index b92aa2f355ed..7184d9a8c890 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -817,6 +817,22 @@ 40dp 20dp + + 2dp + + 4dp + + 9dp + + 3dp + + 6dp + + 10dp + + 4dp + + 0dp @dimen/notification_small_icon_size diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index eec6ae3fb521..cb8e4aa9b2a9 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -502,6 +502,10 @@ please see styles_device_defaults.xml. +