Loading packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java +40 −9 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; import static android.app.Flags.lifetimeExtensionRefactor; import android.annotation.NonNull; import android.app.Notification; import android.app.RemoteInputHistoryItem; Loading @@ -29,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.ArrayList; import java.util.Arrays; import java.util.stream.Stream; Loading Loading @@ -68,7 +71,7 @@ public class RemoteInputNotificationRebuilder { @NonNull public StatusBarNotification rebuildForCanceledSmartReplies( NotificationEntry entry) { return rebuildWithRemoteInputInserted(entry, null /* remoteInputTest */, return rebuildWithRemoteInputInserted(entry, null /* remoteInputText */, false /* showSpinner */, null /* mimeType */, null /* uri */); } Loading Loading @@ -97,9 +100,36 @@ public class RemoteInputNotificationRebuilder { StatusBarNotification rebuildWithRemoteInputInserted(NotificationEntry entry, CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) { StatusBarNotification sbn = entry.getSbn(); Notification.Builder b = Notification.Builder .recoverBuilder(mContext, sbn.getNotification().clone()); if (lifetimeExtensionRefactor()) { if (entry.remoteInputs == null) { entry.remoteInputs = new ArrayList<RemoteInputHistoryItem>(); } // Append new remote input information to remoteInputs list if (remoteInputText != null || uri != null) { RemoteInputHistoryItem newItem = uri != null ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText) : new RemoteInputHistoryItem(remoteInputText); // The list is latest-first, so new elements should be added as the first element. entry.remoteInputs.add(0, newItem); } // Read the whole remoteInputs list from the entry, then append all of those to the sbn. Parcelable[] oldHistoryItems = sbn.getNotification().extras .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null ? Stream.concat( entry.remoteInputs.stream(), Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p)) .toArray(RemoteInputHistoryItem[]::new) : entry.remoteInputs.toArray(RemoteInputHistoryItem[]::new); b.setRemoteInputHistory(newHistoryItems); } else { if (remoteInputText != null || uri != null) { RemoteInputHistoryItem newItem = uri != null ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText) Loading @@ -114,6 +144,7 @@ public class RemoteInputNotificationRebuilder { : new RemoteInputHistoryItem[]{newItem}; b.setRemoteInputHistory(newHistoryItems); } } b.setShowRemoteInputSpinner(showSpinner); b.setHideSmartReplies(true); Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +2 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.app.NotificationChannel; import android.app.NotificationManager.Policy; import android.app.Person; import android.app.RemoteInput; import android.app.RemoteInputHistoryItem; import android.content.Context; import android.content.pm.ShortcutInfo; import android.net.Uri; Loading Loading @@ -127,6 +128,7 @@ public final class NotificationEntry extends ListEntry { public int targetSdk; private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; public CharSequence remoteInputText; public List<RemoteInputHistoryItem> remoteInputs = null; public String remoteInputMimeType; public Uri remoteInputUri; public ContentInfo remoteInputAttachment; Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt +51 −10 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Flags.lifetimeExtensionRefactor import android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY import android.os.Handler import android.service.notification.NotificationListenerService.REASON_CANCEL import android.service.notification.NotificationListenerService.REASON_CLICK Loading Loading @@ -88,11 +90,21 @@ class RemoteInputCoordinator @Inject constructor( override fun attach(pipeline: NotifPipeline) { mNotificationRemoteInputManager.setRemoteInputListener(this) mRemoteInputLifetimeExtenders.forEach { pipeline.addNotificationLifetimeExtender(it) } if (lifetimeExtensionRefactor()) { pipeline.addNotificationLifetimeExtender(mRemoteInputActiveExtender) } else { mRemoteInputLifetimeExtenders.forEach { pipeline.addNotificationLifetimeExtender(it) } } mNotifUpdater = pipeline.getInternalNotifUpdater(TAG) pipeline.addCollectionListener(mCollectionListener) } /* * Listener that updates the appearance of the notification if it has been lifetime extended * by a a direct reply or a smart reply, and cancelled. */ val mCollectionListener = object : NotifCollectionListener { override fun onEntryUpdated(entry: NotificationEntry, fromSystem: Boolean) { if (DEBUG) { Loading @@ -100,11 +112,34 @@ class RemoteInputCoordinator @Inject constructor( " fromSystem=$fromSystem)") } if (fromSystem) { if (lifetimeExtensionRefactor()) { if ((entry.getSbn().getNotification().flags and FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) { if (mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory( entry)) { val newSbn = mRebuilder.rebuildForRemoteInputReply(entry) entry.onRemoteInputInserted() mNotifUpdater.onInternalNotificationUpdate(newSbn, "Extending lifetime of notification with remote input") } else if (mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory( entry)) { val newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry) mSmartReplyController.stopSending(entry) mNotifUpdater.onInternalNotificationUpdate(newSbn, "Extending lifetime of notification with smart reply") } } else { // Notifications updated without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY // should have their remote inputs list cleared. entry.remoteInputs = null } } else { // Mark smart replies as sent whenever a notification is updated by the app, // otherwise the smart replies are never marked as sent. mSmartReplyController.stopSending(entry) } } } override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { if (DEBUG) Log.d(TAG, "mCollectionListener.onEntryRemoved(entry=${entry.key})") Loading @@ -130,8 +165,10 @@ class RemoteInputCoordinator @Inject constructor( // NOTE: This is some trickery! By removing the lifetime extensions when we know they should // be immediately re-upped, we ensure that the side-effects of the lifetime extenders get to // fire again, thus ensuring that we add subsequent replies to the notification. if (!lifetimeExtensionRefactor()) { mRemoteInputHistoryExtender.endLifetimeExtension(entry.key) mSmartReplyHistoryExtender.endLifetimeExtension(entry.key) } // If we're extending for remote input being active, then from the apps point of // view it is already canceled, so we'll need to cancel it on the apps behalf Loading Loading @@ -160,15 +197,19 @@ class RemoteInputCoordinator @Inject constructor( } override fun isNotificationKeptForRemoteInputHistory(key: String) = if (!lifetimeExtensionRefactor()) { mRemoteInputHistoryExtender.isExtending(key) || mSmartReplyHistoryExtender.isExtending(key) } else false override fun releaseNotificationIfKeptForRemoteInputHistory(entry: NotificationEntry) { if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entry.key})") if (!lifetimeExtensionRefactor()) { mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(entry.key, REMOTE_INPUT_EXTENDER_RELEASE_DELAY) mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(entry.key, REMOTE_INPUT_EXTENDER_RELEASE_DELAY) } mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key, REMOTE_INPUT_EXTENDER_RELEASE_DELAY) } Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt +96 −5 Original line number Diff line number Diff line Loading @@ -15,7 +15,13 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Flags.lifetimeExtensionRefactor import android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR import android.app.Notification import android.app.RemoteInputHistoryItem import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper Loading @@ -34,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.captureMany import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import org.junit.Before Loading @@ -42,6 +49,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks Loading @@ -57,6 +65,7 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { private lateinit var entry2: NotificationEntry @Mock private lateinit var lifetimeExtensionCallback: OnEndLifetimeExtensionCallback @Mock private lateinit var rebuilder: RemoteInputNotificationRebuilder @Mock private lateinit var remoteInputManager: NotificationRemoteInputManager @Mock private lateinit var mainHandler: Handler Loading Loading @@ -84,9 +93,6 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { listener = withArgCaptor { verify(remoteInputManager).setRemoteInputListener(capture()) } collectionListener = withArgCaptor { verify(pipeline).addCollectionListener(capture()) } entry1 = NotificationEntryBuilder().setId(1).build() entry2 = NotificationEntryBuilder().setId(2).build() `when`(rebuilder.rebuildForCanceledSmartReplies(any())).thenReturn(sbn) Loading @@ -98,16 +104,23 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { val remoteInputHistoryExtender get() = coordinator.mRemoteInputHistoryExtender val smartReplyHistoryExtender get() = coordinator.mSmartReplyHistoryExtender val collectionListeners get() = captureMany { verify(pipeline, times(1)).addCollectionListener(capture()) } @Test fun testRemoteInputActive() { `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true) assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue() if (!lifetimeExtensionRefactor()) { assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() } assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isFalse() } @Test @DisableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testRemoteInputHistory() { `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry1)).thenReturn(true) assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse() Loading @@ -117,6 +130,7 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { } @Test @DisableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testSmartReplyHistory() { `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry1)).thenReturn(true) assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse() Loading @@ -142,4 +156,81 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { verify(lifetimeExtensionCallback).onEndLifetimeExtension(remoteInputActiveExtender, entry1) assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse() } @Test @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testOnlyRemoteInputActiveLifetimeExtenderExtends() { `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true) assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue() assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isTrue() listener.onPanelCollapsed() assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse() // Checks that lifetimeExtensionCallback is only called the expected number of times, // by the remoteInputActiveExtender. // Checks that the remote input history extender and smart reply history extenders // aren't attached to the pipeline. verify(lifetimeExtensionCallback, times(1)).onEndLifetimeExtension(any(), any()) } @Test @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testRemoteInputLifetimeExtensionListenerTrigger() { // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag. val entry = NotificationEntryBuilder() .setId(3) .setTag("entry") .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true) .build() `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(true) `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false) collectionListeners.forEach { it.onEntryUpdated(entry, true) } verify(rebuilder, times(1)).rebuildForRemoteInputReply(entry) } @Test @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testSmartReplyLifetimeExtensionListenerTrigger() { // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag. val entry = NotificationEntryBuilder() .setId(3) .setTag("entry") .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true) .build() `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false) `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(true) collectionListeners.forEach { it.onEntryUpdated(entry, true) } verify(rebuilder, times(1)).rebuildForCanceledSmartReplies(entry) verify(smartReplyController, times(1)).stopSending(entry) } @Test @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testLifetimeExtensionListenerClearsRemoteInputs() { // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag. val entry = NotificationEntryBuilder() .setId(3) .setTag("entry") .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false) .build() entry.remoteInputs = ArrayList<RemoteInputHistoryItem>() entry.remoteInputs.add(RemoteInputHistoryItem("Test Text")) `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false) `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false) collectionListeners.forEach { it.onEntryUpdated(entry, true) } assertThat(entry.remoteInputs).isNull() } } services/core/java/com/android/server/notification/ManagedServices.java +9 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.notification; import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR; import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT; import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_FOREGROUND_SERVICE; Loading @@ -24,6 +25,7 @@ import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityOptions; Loading Loading @@ -1802,6 +1804,8 @@ abstract public class ManagedServices { public ComponentName component; public int userid; public boolean isSystem; @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR) public boolean isSystemUi; public ServiceConnection connection; public int targetSdkVersion; public Pair<ComponentName, Integer> mKey; Loading Loading @@ -1836,6 +1840,11 @@ abstract public class ManagedServices { return isSystem; } @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR) public boolean isSystemUi() { return isSystemUi; } @Override public String toString() { return new StringBuilder("ManagedServiceInfo[") Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java +40 −9 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; import static android.app.Flags.lifetimeExtensionRefactor; import android.annotation.NonNull; import android.app.Notification; import android.app.RemoteInputHistoryItem; Loading @@ -29,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.ArrayList; import java.util.Arrays; import java.util.stream.Stream; Loading Loading @@ -68,7 +71,7 @@ public class RemoteInputNotificationRebuilder { @NonNull public StatusBarNotification rebuildForCanceledSmartReplies( NotificationEntry entry) { return rebuildWithRemoteInputInserted(entry, null /* remoteInputTest */, return rebuildWithRemoteInputInserted(entry, null /* remoteInputText */, false /* showSpinner */, null /* mimeType */, null /* uri */); } Loading Loading @@ -97,9 +100,36 @@ public class RemoteInputNotificationRebuilder { StatusBarNotification rebuildWithRemoteInputInserted(NotificationEntry entry, CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) { StatusBarNotification sbn = entry.getSbn(); Notification.Builder b = Notification.Builder .recoverBuilder(mContext, sbn.getNotification().clone()); if (lifetimeExtensionRefactor()) { if (entry.remoteInputs == null) { entry.remoteInputs = new ArrayList<RemoteInputHistoryItem>(); } // Append new remote input information to remoteInputs list if (remoteInputText != null || uri != null) { RemoteInputHistoryItem newItem = uri != null ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText) : new RemoteInputHistoryItem(remoteInputText); // The list is latest-first, so new elements should be added as the first element. entry.remoteInputs.add(0, newItem); } // Read the whole remoteInputs list from the entry, then append all of those to the sbn. Parcelable[] oldHistoryItems = sbn.getNotification().extras .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null ? Stream.concat( entry.remoteInputs.stream(), Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p)) .toArray(RemoteInputHistoryItem[]::new) : entry.remoteInputs.toArray(RemoteInputHistoryItem[]::new); b.setRemoteInputHistory(newHistoryItems); } else { if (remoteInputText != null || uri != null) { RemoteInputHistoryItem newItem = uri != null ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText) Loading @@ -114,6 +144,7 @@ public class RemoteInputNotificationRebuilder { : new RemoteInputHistoryItem[]{newItem}; b.setRemoteInputHistory(newHistoryItems); } } b.setShowRemoteInputSpinner(showSpinner); b.setHideSmartReplies(true); Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +2 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.app.NotificationChannel; import android.app.NotificationManager.Policy; import android.app.Person; import android.app.RemoteInput; import android.app.RemoteInputHistoryItem; import android.content.Context; import android.content.pm.ShortcutInfo; import android.net.Uri; Loading Loading @@ -127,6 +128,7 @@ public final class NotificationEntry extends ListEntry { public int targetSdk; private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; public CharSequence remoteInputText; public List<RemoteInputHistoryItem> remoteInputs = null; public String remoteInputMimeType; public Uri remoteInputUri; public ContentInfo remoteInputAttachment; Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt +51 −10 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Flags.lifetimeExtensionRefactor import android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY import android.os.Handler import android.service.notification.NotificationListenerService.REASON_CANCEL import android.service.notification.NotificationListenerService.REASON_CLICK Loading Loading @@ -88,11 +90,21 @@ class RemoteInputCoordinator @Inject constructor( override fun attach(pipeline: NotifPipeline) { mNotificationRemoteInputManager.setRemoteInputListener(this) mRemoteInputLifetimeExtenders.forEach { pipeline.addNotificationLifetimeExtender(it) } if (lifetimeExtensionRefactor()) { pipeline.addNotificationLifetimeExtender(mRemoteInputActiveExtender) } else { mRemoteInputLifetimeExtenders.forEach { pipeline.addNotificationLifetimeExtender(it) } } mNotifUpdater = pipeline.getInternalNotifUpdater(TAG) pipeline.addCollectionListener(mCollectionListener) } /* * Listener that updates the appearance of the notification if it has been lifetime extended * by a a direct reply or a smart reply, and cancelled. */ val mCollectionListener = object : NotifCollectionListener { override fun onEntryUpdated(entry: NotificationEntry, fromSystem: Boolean) { if (DEBUG) { Loading @@ -100,11 +112,34 @@ class RemoteInputCoordinator @Inject constructor( " fromSystem=$fromSystem)") } if (fromSystem) { if (lifetimeExtensionRefactor()) { if ((entry.getSbn().getNotification().flags and FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) { if (mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory( entry)) { val newSbn = mRebuilder.rebuildForRemoteInputReply(entry) entry.onRemoteInputInserted() mNotifUpdater.onInternalNotificationUpdate(newSbn, "Extending lifetime of notification with remote input") } else if (mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory( entry)) { val newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry) mSmartReplyController.stopSending(entry) mNotifUpdater.onInternalNotificationUpdate(newSbn, "Extending lifetime of notification with smart reply") } } else { // Notifications updated without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY // should have their remote inputs list cleared. entry.remoteInputs = null } } else { // Mark smart replies as sent whenever a notification is updated by the app, // otherwise the smart replies are never marked as sent. mSmartReplyController.stopSending(entry) } } } override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { if (DEBUG) Log.d(TAG, "mCollectionListener.onEntryRemoved(entry=${entry.key})") Loading @@ -130,8 +165,10 @@ class RemoteInputCoordinator @Inject constructor( // NOTE: This is some trickery! By removing the lifetime extensions when we know they should // be immediately re-upped, we ensure that the side-effects of the lifetime extenders get to // fire again, thus ensuring that we add subsequent replies to the notification. if (!lifetimeExtensionRefactor()) { mRemoteInputHistoryExtender.endLifetimeExtension(entry.key) mSmartReplyHistoryExtender.endLifetimeExtension(entry.key) } // If we're extending for remote input being active, then from the apps point of // view it is already canceled, so we'll need to cancel it on the apps behalf Loading Loading @@ -160,15 +197,19 @@ class RemoteInputCoordinator @Inject constructor( } override fun isNotificationKeptForRemoteInputHistory(key: String) = if (!lifetimeExtensionRefactor()) { mRemoteInputHistoryExtender.isExtending(key) || mSmartReplyHistoryExtender.isExtending(key) } else false override fun releaseNotificationIfKeptForRemoteInputHistory(entry: NotificationEntry) { if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entry.key})") if (!lifetimeExtensionRefactor()) { mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(entry.key, REMOTE_INPUT_EXTENDER_RELEASE_DELAY) mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(entry.key, REMOTE_INPUT_EXTENDER_RELEASE_DELAY) } mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key, REMOTE_INPUT_EXTENDER_RELEASE_DELAY) } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt +96 −5 Original line number Diff line number Diff line Loading @@ -15,7 +15,13 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Flags.lifetimeExtensionRefactor import android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR import android.app.Notification import android.app.RemoteInputHistoryItem import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper Loading @@ -34,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.captureMany import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import org.junit.Before Loading @@ -42,6 +49,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks Loading @@ -57,6 +65,7 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { private lateinit var entry2: NotificationEntry @Mock private lateinit var lifetimeExtensionCallback: OnEndLifetimeExtensionCallback @Mock private lateinit var rebuilder: RemoteInputNotificationRebuilder @Mock private lateinit var remoteInputManager: NotificationRemoteInputManager @Mock private lateinit var mainHandler: Handler Loading Loading @@ -84,9 +93,6 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { listener = withArgCaptor { verify(remoteInputManager).setRemoteInputListener(capture()) } collectionListener = withArgCaptor { verify(pipeline).addCollectionListener(capture()) } entry1 = NotificationEntryBuilder().setId(1).build() entry2 = NotificationEntryBuilder().setId(2).build() `when`(rebuilder.rebuildForCanceledSmartReplies(any())).thenReturn(sbn) Loading @@ -98,16 +104,23 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { val remoteInputHistoryExtender get() = coordinator.mRemoteInputHistoryExtender val smartReplyHistoryExtender get() = coordinator.mSmartReplyHistoryExtender val collectionListeners get() = captureMany { verify(pipeline, times(1)).addCollectionListener(capture()) } @Test fun testRemoteInputActive() { `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true) assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue() if (!lifetimeExtensionRefactor()) { assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() } assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isFalse() } @Test @DisableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testRemoteInputHistory() { `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry1)).thenReturn(true) assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse() Loading @@ -117,6 +130,7 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { } @Test @DisableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testSmartReplyHistory() { `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry1)).thenReturn(true) assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse() Loading @@ -142,4 +156,81 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { verify(lifetimeExtensionCallback).onEndLifetimeExtension(remoteInputActiveExtender, entry1) assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse() } @Test @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testOnlyRemoteInputActiveLifetimeExtenderExtends() { `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true) assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue() assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isTrue() listener.onPanelCollapsed() assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse() // Checks that lifetimeExtensionCallback is only called the expected number of times, // by the remoteInputActiveExtender. // Checks that the remote input history extender and smart reply history extenders // aren't attached to the pipeline. verify(lifetimeExtensionCallback, times(1)).onEndLifetimeExtension(any(), any()) } @Test @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testRemoteInputLifetimeExtensionListenerTrigger() { // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag. val entry = NotificationEntryBuilder() .setId(3) .setTag("entry") .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true) .build() `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(true) `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false) collectionListeners.forEach { it.onEntryUpdated(entry, true) } verify(rebuilder, times(1)).rebuildForRemoteInputReply(entry) } @Test @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testSmartReplyLifetimeExtensionListenerTrigger() { // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag. val entry = NotificationEntryBuilder() .setId(3) .setTag("entry") .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true) .build() `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false) `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(true) collectionListeners.forEach { it.onEntryUpdated(entry, true) } verify(rebuilder, times(1)).rebuildForCanceledSmartReplies(entry) verify(smartReplyController, times(1)).stopSending(entry) } @Test @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR) fun testLifetimeExtensionListenerClearsRemoteInputs() { // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag. val entry = NotificationEntryBuilder() .setId(3) .setTag("entry") .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false) .build() entry.remoteInputs = ArrayList<RemoteInputHistoryItem>() entry.remoteInputs.add(RemoteInputHistoryItem("Test Text")) `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false) `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false) collectionListeners.forEach { it.onEntryUpdated(entry, true) } assertThat(entry.remoteInputs).isNull() } }
services/core/java/com/android/server/notification/ManagedServices.java +9 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.notification; import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR; import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT; import static android.content.Context.BIND_AUTO_CREATE; import static android.content.Context.BIND_FOREGROUND_SERVICE; Loading @@ -24,6 +25,7 @@ import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityOptions; Loading Loading @@ -1802,6 +1804,8 @@ abstract public class ManagedServices { public ComponentName component; public int userid; public boolean isSystem; @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR) public boolean isSystemUi; public ServiceConnection connection; public int targetSdkVersion; public Pair<ComponentName, Integer> mKey; Loading Loading @@ -1836,6 +1840,11 @@ abstract public class ManagedServices { return isSystem; } @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR) public boolean isSystemUi() { return isSystemUi; } @Override public String toString() { return new StringBuilder("ManagedServiceInfo[") Loading