New Pipeline: Remote Input 2/4: Add ability to internally update notifications am: fac5da2f77

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16167380

Change-Id: I1f5a819b708895b70c94a69cf784a28c9f49bfe7
This commit is contained in:
Jeff DeCew
2021-11-02 15:27:25 +00:00
committed by Automerger Merge Worker
9 changed files with 222 additions and 5 deletions

View File

@@ -689,8 +689,9 @@ public class NotificationEntryManager implements
for (NotificationEntryListener listener : mNotificationEntryListeners) { for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPreEntryUpdated(entry); listener.onPreEntryUpdated(entry);
} }
final boolean fromSystem = ranking != null;
for (NotifCollectionListener listener : mNotifCollectionListeners) { for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryUpdated(entry); listener.onEntryUpdated(entry, fromSystem);
} }
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {

View File

@@ -47,6 +47,7 @@ import android.annotation.MainThread;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.app.Notification; import android.app.Notification;
import android.os.Handler;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.Trace; import android.os.Trace;
import android.os.UserHandle; import android.os.UserHandle;
@@ -62,6 +63,7 @@ import androidx.annotation.NonNull;
import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dumpable; import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.LogBufferEulogizer; import com.android.systemui.dump.LogBufferEulogizer;
import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FeatureFlags;
@@ -76,6 +78,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.EntryRemovedEvent;
import com.android.systemui.statusbar.notification.collection.notifcollection.EntryUpdatedEvent; 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.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.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
@@ -131,6 +134,7 @@ public class NotifCollection implements Dumpable {
private final SystemClock mClock; private final SystemClock mClock;
private final FeatureFlags mFeatureFlags; private final FeatureFlags mFeatureFlags;
private final NotifCollectionLogger mLogger; private final NotifCollectionLogger mLogger;
private final Handler mMainHandler;
private final LogBufferEulogizer mEulogizer; private final LogBufferEulogizer mEulogizer;
private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>(); private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
@@ -154,6 +158,7 @@ public class NotifCollection implements Dumpable {
SystemClock clock, SystemClock clock,
FeatureFlags featureFlags, FeatureFlags featureFlags,
NotifCollectionLogger logger, NotifCollectionLogger logger,
@Main Handler mainHandler,
LogBufferEulogizer logBufferEulogizer, LogBufferEulogizer logBufferEulogizer,
DumpManager dumpManager) { DumpManager dumpManager) {
Assert.isMainThread(); Assert.isMainThread();
@@ -161,6 +166,7 @@ public class NotifCollection implements Dumpable {
mClock = clock; mClock = clock;
mFeatureFlags = featureFlags; mFeatureFlags = featureFlags;
mLogger = logger; mLogger = logger;
mMainHandler = mainHandler;
mEulogizer = logBufferEulogizer; mEulogizer = logBufferEulogizer;
dumpManager.registerDumpable(TAG, this); dumpManager.registerDumpable(TAG, this);
@@ -442,7 +448,7 @@ public class NotifCollection implements Dumpable {
mEventQueue.add(new BindEntryEvent(entry, sbn)); mEventQueue.add(new BindEntryEvent(entry, sbn));
mLogger.logNotifUpdated(sbn.getKey()); mLogger.logNotifUpdated(sbn.getKey());
mEventQueue.add(new EntryUpdatedEvent(entry)); mEventQueue.add(new EntryUpdatedEvent(entry, true /* fromSystem */));
} }
} }
@@ -791,6 +797,51 @@ public class NotifCollection implements Dumpable {
private static final String TAG = "NotifCollection"; 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 = { @IntDef(prefix = { "REASON_" }, value = {
REASON_NOT_CANCELED, REASON_NOT_CANCELED,
REASON_UNKNOWN, REASON_UNKNOWN,

View File

@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection; package com.android.systemui.statusbar.notification.collection;
import android.os.Handler;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton; 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.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; 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.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.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -222,6 +225,17 @@ public class NotifPipeline implements CommonNotifCollection {
mShadeListBuilder.addPreRenderInvalidator(invalidator); 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 * 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 * are currently present in the shade. If this method is called during pipeline execution it

View File

@@ -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);
}

View File

@@ -53,6 +53,17 @@ public interface NotifCollectionListener {
default void onEntryAdded(@NonNull NotificationEntry entry) { 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 * 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. * the time this listener is called, the entry's SBN and Ranking will already have been updated.

View File

@@ -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) { fun logNoNotificationToRemoveWithKey(key: String) {
buffer.log(TAG, ERROR, { buffer.log(TAG, ERROR, {
str1 = key str1 = key

View File

@@ -64,10 +64,11 @@ data class EntryAddedEvent(
} }
data class EntryUpdatedEvent( data class EntryUpdatedEvent(
val entry: NotificationEntry val entry: NotificationEntry,
val fromSystem: Boolean
) : NotifEvent() { ) : NotifEvent() {
override fun dispatchToListener(listener: NotifCollectionListener) { override fun dispatchToListener(listener: NotifCollectionListener) {
listener.onEntryUpdated(entry) listener.onEntryUpdated(entry, fromSystem)
} }
} }

View File

@@ -86,7 +86,7 @@ class OngoingCallController @Inject constructor(
// //
// TODO(b/183229367): Remove this function override when b/178406514 is fixed. // TODO(b/183229367): Remove this function override when b/178406514 is fixed.
override fun onEntryAdded(entry: NotificationEntry) { override fun onEntryAdded(entry: NotificationEntry) {
onEntryUpdated(entry) onEntryUpdated(entry, true)
} }
override fun onEntryUpdated(entry: NotificationEntry) { override fun onEntryUpdated(entry: NotificationEntry) {

View File

@@ -35,6 +35,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
@@ -50,6 +51,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Notification; import android.app.Notification;
import android.os.Handler;
import android.os.RemoteException; import android.os.RemoteException;
import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap; 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.coalescer.GroupCoalescer.BatchableNotificationHandler;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; 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.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.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
@@ -107,6 +110,7 @@ public class NotifCollectionTest extends SysuiTestCase {
@Mock private FeatureFlags mFeatureFlags; @Mock private FeatureFlags mFeatureFlags;
@Mock private NotifCollectionLogger mLogger; @Mock private NotifCollectionLogger mLogger;
@Mock private LogBufferEulogizer mEulogizer; @Mock private LogBufferEulogizer mEulogizer;
@Mock private Handler mMainHandler;
@Mock private GroupCoalescer mGroupCoalescer; @Mock private GroupCoalescer mGroupCoalescer;
@Spy private RecordingCollectionListener mCollectionListener; @Spy private RecordingCollectionListener mCollectionListener;
@@ -152,6 +156,7 @@ public class NotifCollectionTest extends SysuiTestCase {
mClock, mClock,
mFeatureFlags, mFeatureFlags,
mLogger, mLogger,
mMainHandler,
mEulogizer, mEulogizer,
mock(DumpManager.class)); mock(DumpManager.class));
mCollection.attach(mGroupCoalescer); mCollection.attach(mGroupCoalescer);
@@ -1322,6 +1327,78 @@ public class NotifCollectionTest extends SysuiTestCase {
verify(mCollectionListener, never()).onEntryRemoved(any(NotificationEntry.class), anyInt()); 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) { private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
return new NotificationEntryBuilder() return new NotificationEntryBuilder()
.setPkg(pkg) .setPkg(pkg)
@@ -1371,6 +1448,11 @@ public class NotifCollectionTest extends SysuiTestCase {
mLastSeenEntries.put(entry.getKey(), entry); mLastSeenEntries.put(entry.getKey(), entry);
} }
@Override
public void onEntryUpdated(NotificationEntry entry, boolean fromSystem) {
onEntryUpdated(entry);
}
@Override @Override
public void onEntryRemoved(NotificationEntry entry, int reason) { public void onEntryRemoved(NotificationEntry entry, int reason) {
} }