Remove recycling HeadsUpManagerPhone objects
With this flag enabled HeadsUpEntry#mEntry could be non null.
Bug: 328390331
Test: atest AvalancheControllerTest BaseHeadsUpManagerTest HeadsUpManagerPhoneTest
Flag: ACONFIG com.android.systemui.notifications_heads_up_refactor DEVELOPMENT
Change-Id: I3a1cd242e22e46ce9f47406bdf78cf9192971faf
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 79ae389..e1f6dbf 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -104,6 +104,13 @@
}
flag {
+ name: "notifications_heads_up_refactor"
+ namespace: "systemui"
+ description: "Use HeadsUpInteractor to feed HUN updates to the NSSL."
+ bug: "325936094"
+}
+
+flag {
name: "pss_app_selector_abrupt_exit_fix"
namespace: "systemui"
description: "Fixes the app selector abruptly disappearing without an animation, when the"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index be63301..30564bb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -60,7 +60,7 @@
private val mGlobalSettings = FakeGlobalSettings()
private val mSystemClock = FakeSystemClock()
private val mExecutor = FakeExecutor(mSystemClock)
- private var testableHeadsUpManager: BaseHeadsUpManager? = null
+ private lateinit var testableHeadsUpManager: BaseHeadsUpManager
@Before
fun setUp() {
@@ -88,20 +88,15 @@
}
private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
- val entry = testableHeadsUpManager!!.createHeadsUpEntry()
-
- entry.setEntry(
+ return testableHeadsUpManager.createHeadsUpEntry(
NotificationEntryBuilder()
.setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
.build()
)
- return entry
}
private fun createFsiHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
- val entry = testableHeadsUpManager!!.createHeadsUpEntry()
- entry.setEntry(createFullScreenIntentEntry(id, mContext))
- return entry
+ return testableHeadsUpManager.createHeadsUpEntry(createFullScreenIntentEntry(id, mContext))
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index ed0d272..3dc4495 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -38,7 +38,6 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
-import android.content.Intent;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -498,16 +497,16 @@
public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final BaseHeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry();
- ongoingCall.setEntry(new NotificationEntryBuilder()
- .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
- new Notification.Builder(mContext, "")
- .setCategory(Notification.CATEGORY_CALL)
- .setOngoing(true)))
- .build());
+ final BaseHeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry(
+ new NotificationEntryBuilder()
+ .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
+ new Notification.Builder(mContext, "")
+ .setCategory(Notification.CATEGORY_CALL)
+ .setOngoing(true)))
+ .build());
- final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
- activeRemoteInput.setEntry(HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
+ final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(
+ HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
activeRemoteInput.mRemoteInputActive = true;
assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -518,18 +517,18 @@
public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final BaseHeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry();
final Person person = new Person.Builder().setName("person").build();
final PendingIntent intent = mock(PendingIntent.class);
- incomingCall.setEntry(new NotificationEntryBuilder()
- .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
- new Notification.Builder(mContext, "")
- .setStyle(Notification.CallStyle
- .forIncomingCall(person, intent, intent))))
- .build());
+ final BaseHeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry(
+ new NotificationEntryBuilder()
+ .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
+ new Notification.Builder(mContext, "")
+ .setStyle(Notification.CallStyle
+ .forIncomingCall(person, intent, intent))))
+ .build());
- final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
- activeRemoteInput.setEntry(HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
+ final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(
+ HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
activeRemoteInput.mRemoteInputActive = true;
assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -541,8 +540,7 @@
final BaseHeadsUpManager hum = createHeadsUpManager();
// Needs full screen intent in order to be pinned
- final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry();
- entryToPin.setEntry(
+ final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
// Note: the standard way to show a notification would be calling showNotification rather
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index d8f77f0..3c9dc63 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -54,9 +54,10 @@
mStickyForSomeTimeAutoDismissTime = BaseHeadsUpManagerTest.TEST_STICKY_AUTO_DISMISS_TIME;
}
+ @NonNull
@Override
- protected HeadsUpEntry createHeadsUpEntry() {
- mLastCreatedEntry = spy(super.createHeadsUpEntry());
+ protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
+ mLastCreatedEntry = spy(super.createHeadsUpEntry(entry));
return mLastCreatedEntry;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt
new file mode 100644
index 0000000..62641fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsHeadsUpRefactor.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notifications heads up refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationsHeadsUpRefactor {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HEADS_UP_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.notificationsHeadsUpRefactor()
+
+ /**
+ * 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/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 24be3db..86bb844 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.AnimationStateHandler;
import com.android.systemui.statusbar.policy.AvalancheController;
@@ -94,6 +95,7 @@
@Override
public HeadsUpEntryPhone acquire() {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
if (!mPoolObjects.isEmpty()) {
return mPoolObjects.pop();
}
@@ -102,6 +104,7 @@
@Override
public boolean release(@NonNull HeadsUpEntryPhone instance) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
mPoolObjects.push(instance);
return true;
}
@@ -371,15 +374,24 @@
///////////////////////////////////////////////////////////////////////////////////////////////
// HeadsUpManager utility (protected) methods overrides:
+ @NonNull
@Override
- protected HeadsUpEntry createHeadsUpEntry() {
- return mEntryPool.acquire();
+ protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ return new HeadsUpEntryPhone(entry);
+ } else {
+ HeadsUpEntryPhone headsUpEntry = mEntryPool.acquire();
+ headsUpEntry.setEntry(entry);
+ return headsUpEntry;
+ }
}
@Override
protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
super.onEntryRemoved(headsUpEntry);
- mEntryPool.release((HeadsUpEntryPhone) headsUpEntry);
+ if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ mEntryPool.release((HeadsUpEntryPhone) headsUpEntry);
+ }
}
@Override
@@ -439,14 +451,22 @@
*/
private boolean extended;
-
@Override
public boolean isSticky() {
return super.isSticky() || mGutsShownPinned;
}
- public void setEntry(@NonNull final NotificationEntry entry) {
- Runnable removeHeadsUpRunnable = () -> {
+ public HeadsUpEntryPhone() {
+ super();
+ }
+
+ public HeadsUpEntryPhone(NotificationEntry entry) {
+ super(entry);
+ }
+
+ @Override
+ protected Runnable createRemoveRunnable(NotificationEntry entry) {
+ return () -> {
if (!mVisualStabilityProvider.isReorderingAllowed()
// We don't want to allow reordering while pulsing, but headsup need to
// time out anyway
@@ -460,8 +480,6 @@
removeEntry(entry.getKey());
}
};
-
- setEntry(entry, removeHeadsUpRunnable);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 50de3cb..6f7e046 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -39,6 +39,7 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.settings.GlobalSettings;
@@ -162,11 +163,7 @@
*/
@Override
public void showNotification(@NonNull NotificationEntry entry) {
- HeadsUpEntry headsUpEntry = createHeadsUpEntry();
-
- // Attach NotificationEntry for AvalancheController to log key and
- // record mPostTime for AvalancheController sorting
- headsUpEntry.setEntry(entry);
+ HeadsUpEntry headsUpEntry = createHeadsUpEntry(entry);
Runnable runnable = () -> {
// TODO(b/315362456) log outside runnable too
@@ -375,7 +372,7 @@
}
/**
- * Remove a notification and reset the entry.
+ * Remove a notification from the alerting entries.
* @param key key of notification to remove
*/
protected final void removeEntry(@NonNull String key) {
@@ -395,7 +392,11 @@
mHeadsUpEntryMap.remove(key);
onEntryRemoved(headsUpEntry);
entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- headsUpEntry.reset();
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ headsUpEntry.cancelAutoRemovalCallbacks("removeEntry");
+ } else {
+ headsUpEntry.reset();
+ }
};
mAvalancheController.delete(headsUpEntry, runnable, "removeEntry");
}
@@ -657,8 +658,8 @@
}
@NonNull
- protected HeadsUpEntry createHeadsUpEntry() {
- return new HeadsUpEntry();
+ protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
+ return new HeadsUpEntry(entry);
}
/**
@@ -694,11 +695,23 @@
@Nullable private Runnable mCancelRemoveRunnable;
- public void setEntry(@NonNull final NotificationEntry entry) {
- setEntry(entry, () -> removeEntry(entry.getKey()));
+ public HeadsUpEntry() {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
}
- public void setEntry(@NonNull final NotificationEntry entry,
+ public HeadsUpEntry(NotificationEntry entry) {
+ // Attach NotificationEntry for AvalancheController to log key and
+ // record mPostTime for AvalancheController sorting
+ setEntry(entry, createRemoveRunnable(entry));
+ }
+
+ /** Attach a NotificationEntry. */
+ public void setEntry(@NonNull final NotificationEntry entry) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
+ setEntry(entry, createRemoveRunnable(entry));
+ }
+
+ private void setEntry(@NonNull final NotificationEntry entry,
@Nullable Runnable removeRunnable) {
mEntry = entry;
mRemoveRunnable = removeRunnable;
@@ -847,6 +860,7 @@
}
public void reset() {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
cancelAutoRemovalCallbacks("reset()");
mEntry = null;
mRemoveRunnable = null;
@@ -919,6 +933,11 @@
}
}
+ /** Creates a runnable to remove this notification from the alerting entries. */
+ protected Runnable createRemoveRunnable(NotificationEntry entry) {
+ return () -> removeEntry(entry.getKey());
+ }
+
/**
* Calculate what the post time of a notification is at some current time.
* @return the post time