New Pipeline: Remote Input 2/4: Add ability to internally update notifications
Bug: 203938360 Test: atest NotifCollectionTest Change-Id: Ie15f7565b76e7314221d18e540d2ed8a5ce3e4a4
This commit is contained in:
@@ -689,8 +689,9 @@ public class NotificationEntryManager implements
|
||||
for (NotificationEntryListener listener : mNotificationEntryListeners) {
|
||||
listener.onPreEntryUpdated(entry);
|
||||
}
|
||||
final boolean fromSystem = ranking != null;
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onEntryUpdated(entry);
|
||||
listener.onEntryUpdated(entry, fromSystem);
|
||||
}
|
||||
|
||||
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
|
||||
|
||||
@@ -47,6 +47,7 @@ import android.annotation.MainThread;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.Notification;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
@@ -61,6 +62,7 @@ import androidx.annotation.NonNull;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.dump.LogBufferEulogizer;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
@@ -75,6 +77,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.En
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.EntryRemovedEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.EntryUpdatedEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.InitEntryEvent;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
|
||||
@@ -130,6 +133,7 @@ public class NotifCollection implements Dumpable {
|
||||
private final SystemClock mClock;
|
||||
private final FeatureFlags mFeatureFlags;
|
||||
private final NotifCollectionLogger mLogger;
|
||||
private final Handler mMainHandler;
|
||||
private final LogBufferEulogizer mEulogizer;
|
||||
|
||||
private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
|
||||
@@ -153,6 +157,7 @@ public class NotifCollection implements Dumpable {
|
||||
SystemClock clock,
|
||||
FeatureFlags featureFlags,
|
||||
NotifCollectionLogger logger,
|
||||
@Main Handler mainHandler,
|
||||
LogBufferEulogizer logBufferEulogizer,
|
||||
DumpManager dumpManager) {
|
||||
Assert.isMainThread();
|
||||
@@ -160,6 +165,7 @@ public class NotifCollection implements Dumpable {
|
||||
mClock = clock;
|
||||
mFeatureFlags = featureFlags;
|
||||
mLogger = logger;
|
||||
mMainHandler = mainHandler;
|
||||
mEulogizer = logBufferEulogizer;
|
||||
|
||||
dumpManager.registerDumpable(TAG, this);
|
||||
@@ -441,7 +447,7 @@ public class NotifCollection implements Dumpable {
|
||||
mEventQueue.add(new BindEntryEvent(entry, sbn));
|
||||
|
||||
mLogger.logNotifUpdated(sbn.getKey());
|
||||
mEventQueue.add(new EntryUpdatedEvent(entry));
|
||||
mEventQueue.add(new EntryUpdatedEvent(entry, true /* fromSystem */));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,6 +794,51 @@ public class NotifCollection implements Dumpable {
|
||||
|
||||
private static final String TAG = "NotifCollection";
|
||||
|
||||
/**
|
||||
* Get an object which can be used to update a notification (internally to the pipeline)
|
||||
* in response to a user action.
|
||||
*
|
||||
* @param name the name of the component that will update notifiations
|
||||
* @return an updater
|
||||
*/
|
||||
public InternalNotifUpdater getInternalNotifUpdater(String name) {
|
||||
return (sbn, reason) -> mMainHandler.post(
|
||||
() -> updateNotificationInternally(sbn, name, reason));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an updated StatusBarNotification for an existing entry. If no entry exists for the
|
||||
* given notification key, this method does nothing.
|
||||
*
|
||||
* @param sbn the updated notification
|
||||
* @param name the component which is updating the notification
|
||||
* @param reason the reason the notification is being updated
|
||||
*/
|
||||
private void updateNotificationInternally(StatusBarNotification sbn, String name,
|
||||
String reason) {
|
||||
Assert.isMainThread();
|
||||
checkForReentrantCall();
|
||||
|
||||
// Make sure we have the notification to update
|
||||
NotificationEntry entry = mNotificationSet.get(sbn.getKey());
|
||||
if (entry == null) {
|
||||
mLogger.logNotifInternalUpdateFailed(sbn.getKey(), name, reason);
|
||||
return;
|
||||
}
|
||||
mLogger.logNotifInternalUpdate(sbn.getKey(), name, reason);
|
||||
|
||||
// First do the pieces of postNotification which are not about assuming the notification
|
||||
// was sent by the app
|
||||
entry.setSbn(sbn);
|
||||
mEventQueue.add(new BindEntryEvent(entry, sbn));
|
||||
|
||||
mLogger.logNotifUpdated(sbn.getKey());
|
||||
mEventQueue.add(new EntryUpdatedEvent(entry, false /* fromSystem */));
|
||||
|
||||
// Skip the applyRanking step and go straight to dispatching the events
|
||||
dispatchEventsAndRebuildList();
|
||||
}
|
||||
|
||||
@IntDef(prefix = { "REASON_" }, value = {
|
||||
REASON_NOT_CANCELED,
|
||||
REASON_UNKNOWN,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.systemui.statusbar.notification.collection;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
@@ -30,6 +32,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
|
||||
@@ -222,6 +225,17 @@ public class NotifPipeline implements CommonNotifCollection {
|
||||
mShadeListBuilder.addPreRenderInvalidator(invalidator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an object which can be used to update a notification (internally to the pipeline)
|
||||
* in response to a user action.
|
||||
*
|
||||
* @param name the name of the component that will update notifiations
|
||||
* @return an updater
|
||||
*/
|
||||
public InternalNotifUpdater getInternalNotifUpdater(String name) {
|
||||
return mNotifCollection.getInternalNotifUpdater(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a read-only view in to the current shade list, i.e. the list of notifications that
|
||||
* are currently present in the shade. If this method is called during pipeline execution it
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.statusbar.notification.collection.notifcollection;
|
||||
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
/**
|
||||
* An object that allows Coordinators to update notifications internally to SystemUI.
|
||||
* This is used when part of the UI involves updating the underlying appearance of a notification
|
||||
* on behalf of an app, such as to add a spinner or remote input history.
|
||||
*/
|
||||
public interface InternalNotifUpdater {
|
||||
/**
|
||||
* Called when an already-existing notification needs to be updated to a new temporary
|
||||
* appearance.
|
||||
* This update is local to the SystemUI process.
|
||||
* This has no effect if no notification with the given key exists in the pipeline.
|
||||
*
|
||||
* @param sbn a notification to update
|
||||
* @param reason a debug reason for the update
|
||||
*/
|
||||
void onInternalNotificationUpdate(StatusBarNotification sbn, String reason);
|
||||
}
|
||||
@@ -53,6 +53,17 @@ public interface NotifCollectionListener {
|
||||
default void onEntryAdded(@NonNull NotificationEntry entry) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a notification with the same key as an existing notification is posted. By
|
||||
* the time this listener is called, the entry's SBN and Ranking will already have been updated.
|
||||
* This delegates to {@link #onEntryUpdated(NotificationEntry)} by default.
|
||||
* @param fromSystem If true, this update came from the NotificationManagerService.
|
||||
* If false, the notification update is an internal change within systemui.
|
||||
*/
|
||||
default void onEntryUpdated(@NonNull NotificationEntry entry, boolean fromSystem) {
|
||||
onEntryUpdated(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a notification with the same key as an existing notification is posted. By
|
||||
* the time this listener is called, the entry's SBN and Ranking will already have been updated.
|
||||
|
||||
@@ -121,6 +121,26 @@ class NotifCollectionLogger @Inject constructor(
|
||||
})
|
||||
}
|
||||
|
||||
fun logNotifInternalUpdate(key: String, name: String, reason: String) {
|
||||
buffer.log(TAG, INFO, {
|
||||
str1 = key
|
||||
str2 = name
|
||||
str3 = reason
|
||||
}, {
|
||||
"UPDATED INTERNALLY $str1 BY $str2 BECAUSE $str3"
|
||||
})
|
||||
}
|
||||
|
||||
fun logNotifInternalUpdateFailed(key: String, name: String, reason: String) {
|
||||
buffer.log(TAG, INFO, {
|
||||
str1 = key
|
||||
str2 = name
|
||||
str3 = reason
|
||||
}, {
|
||||
"FAILED INTERNAL UPDATE $str1 BY $str2 BECAUSE $str3"
|
||||
})
|
||||
}
|
||||
|
||||
fun logNoNotificationToRemoveWithKey(key: String) {
|
||||
buffer.log(TAG, ERROR, {
|
||||
str1 = key
|
||||
|
||||
@@ -64,10 +64,11 @@ data class EntryAddedEvent(
|
||||
}
|
||||
|
||||
data class EntryUpdatedEvent(
|
||||
val entry: NotificationEntry
|
||||
val entry: NotificationEntry,
|
||||
val fromSystem: Boolean
|
||||
) : NotifEvent() {
|
||||
override fun dispatchToListener(listener: NotifCollectionListener) {
|
||||
listener.onEntryUpdated(entry)
|
||||
listener.onEntryUpdated(entry, fromSystem)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ class OngoingCallController @Inject constructor(
|
||||
//
|
||||
// TODO(b/183229367): Remove this function override when b/178406514 is fixed.
|
||||
override fun onEntryAdded(entry: NotificationEntry) {
|
||||
onEntryUpdated(entry)
|
||||
onEntryUpdated(entry, true)
|
||||
}
|
||||
|
||||
override fun onEntryUpdated(entry: NotificationEntry) {
|
||||
|
||||
@@ -35,6 +35,7 @@ import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
@@ -50,6 +51,7 @@ import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.service.notification.NotificationListenerService.Ranking;
|
||||
import android.service.notification.NotificationListenerService.RankingMap;
|
||||
@@ -77,6 +79,7 @@ import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoa
|
||||
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
|
||||
@@ -107,6 +110,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
@Mock private FeatureFlags mFeatureFlags;
|
||||
@Mock private NotifCollectionLogger mLogger;
|
||||
@Mock private LogBufferEulogizer mEulogizer;
|
||||
@Mock private Handler mMainHandler;
|
||||
|
||||
@Mock private GroupCoalescer mGroupCoalescer;
|
||||
@Spy private RecordingCollectionListener mCollectionListener;
|
||||
@@ -152,6 +156,7 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
mClock,
|
||||
mFeatureFlags,
|
||||
mLogger,
|
||||
mMainHandler,
|
||||
mEulogizer,
|
||||
mock(DumpManager.class));
|
||||
mCollection.attach(mGroupCoalescer);
|
||||
@@ -1322,6 +1327,78 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
verify(mCollectionListener, never()).onEntryRemoved(any(NotificationEntry.class), anyInt());
|
||||
}
|
||||
|
||||
private Runnable getInternalNotifUpdateRunnable(StatusBarNotification sbn) {
|
||||
InternalNotifUpdater updater = mCollection.getInternalNotifUpdater("Test");
|
||||
updater.onInternalNotificationUpdate(sbn, "reason");
|
||||
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
|
||||
verify(mMainHandler).post(runnableCaptor.capture());
|
||||
return runnableCaptor.getValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInternalNotifUpdaterPostsToMainHandler() {
|
||||
InternalNotifUpdater updater = mCollection.getInternalNotifUpdater("Test");
|
||||
updater.onInternalNotificationUpdate(mock(StatusBarNotification.class), "reason");
|
||||
verify(mMainHandler).post(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecondPostCallsUpdateWithTrue() {
|
||||
// GIVEN a pipeline with one notification
|
||||
NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
|
||||
NotificationEntry entry = mCollectionListener.getEntry(notifEvent.key);
|
||||
|
||||
// KNOWING that it already called listener methods once
|
||||
verify(mCollectionListener).onEntryAdded(eq(entry));
|
||||
verify(mCollectionListener).onRankingApplied();
|
||||
|
||||
// WHEN we update the notification via the system
|
||||
mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
|
||||
|
||||
// THEN entry updated gets called, added does not, and ranking is called again
|
||||
verify(mCollectionListener).onEntryUpdated(eq(entry));
|
||||
verify(mCollectionListener).onEntryUpdated(eq(entry), eq(true));
|
||||
verify(mCollectionListener).onEntryAdded((entry));
|
||||
verify(mCollectionListener, times(2)).onRankingApplied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternalNotifUpdaterCallsUpdate() {
|
||||
// GIVEN a pipeline with one notification
|
||||
NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
|
||||
NotificationEntry entry = mCollectionListener.getEntry(notifEvent.key);
|
||||
|
||||
// KNOWING that it will call listener methods once
|
||||
verify(mCollectionListener).onEntryAdded(eq(entry));
|
||||
verify(mCollectionListener).onRankingApplied();
|
||||
|
||||
// WHEN we update that notification internally
|
||||
StatusBarNotification sbn = notifEvent.sbn;
|
||||
getInternalNotifUpdateRunnable(sbn).run();
|
||||
|
||||
// THEN only entry updated gets called a second time
|
||||
verify(mCollectionListener).onEntryAdded(eq(entry));
|
||||
verify(mCollectionListener).onRankingApplied();
|
||||
verify(mCollectionListener).onEntryUpdated(eq(entry));
|
||||
verify(mCollectionListener).onEntryUpdated(eq(entry), eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternalNotifUpdaterIgnoresNew() {
|
||||
// GIVEN a pipeline without any notifications
|
||||
StatusBarNotification sbn = buildNotif(TEST_PACKAGE, 47, "myTag").build().getSbn();
|
||||
|
||||
// WHEN we internally update an unknown notification
|
||||
getInternalNotifUpdateRunnable(sbn).run();
|
||||
|
||||
// THEN only entry updated gets called a second time
|
||||
verify(mCollectionListener, never()).onEntryAdded(any());
|
||||
verify(mCollectionListener, never()).onRankingUpdate(any());
|
||||
verify(mCollectionListener, never()).onRankingApplied();
|
||||
verify(mCollectionListener, never()).onEntryUpdated(any());
|
||||
verify(mCollectionListener, never()).onEntryUpdated(any(), anyBoolean());
|
||||
}
|
||||
|
||||
private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
|
||||
return new NotificationEntryBuilder()
|
||||
.setPkg(pkg)
|
||||
@@ -1371,6 +1448,11 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
mLastSeenEntries.put(entry.getKey(), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryUpdated(NotificationEntry entry, boolean fromSystem) {
|
||||
onEntryUpdated(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryRemoved(NotificationEntry entry, int reason) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user