diff options
26 files changed, 490 insertions, 618 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java index 129fa5a7cc17..0c341cc92f83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification; import android.content.Intent; -import android.service.notification.StatusBarNotification; import android.view.View; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -29,7 +28,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow */ public interface NotificationActivityStarter { /** Called when the user clicks on the surface of a notification. */ - void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row); + void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row); /** Called when the user clicks on a button in the notification guts which fires an intent. */ void startNotificationGutsIntent(Intent intent, int appUid, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index 392145ad306a..c3ce593b54fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -104,7 +104,7 @@ public final class NotificationClicker implements View.OnClickListener { mBubblesOptional.get().collapseStack(); } - mNotificationActivityStarter.onNotificationClicked(entry.getSbn(), row); + mNotificationActivityStarter.onNotificationClicked(entry, row); } private boolean isMenuVisible(ExpandableNotificationRow row) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt index fbf033bd2291..ad3dfedcdb96 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt @@ -27,7 +27,7 @@ class NotificationClickerLogger @Inject constructor( ) { fun logOnClick(entry: NotificationEntry) { buffer.log(TAG, LogLevel.DEBUG, { - str1 = entry.key + str1 = entry.logKey str2 = entry.ranking.channel.id }, { "CLICK $str1 (channel=$str2)" @@ -36,7 +36,7 @@ class NotificationClickerLogger @Inject constructor( fun logMenuVisible(entry: NotificationEntry) { buffer.log(TAG, LogLevel.DEBUG, { - str1 = entry.key + str1 = entry.logKey }, { "Ignoring click on $str1; menu is visible" }) @@ -44,7 +44,7 @@ class NotificationClickerLogger @Inject constructor( fun logParentMenuVisible(entry: NotificationEntry) { buffer.log(TAG, LogLevel.DEBUG, { - str1 = entry.key + str1 = entry.logKey }, { "Ignoring click on $str1; parent menu is visible" }) @@ -52,7 +52,7 @@ class NotificationClickerLogger @Inject constructor( fun logChildrenExpanded(entry: NotificationEntry) { buffer.log(TAG, LogLevel.DEBUG, { - str1 = entry.key + str1 = entry.logKey }, { "Ignoring click on $str1; children are expanded" }) @@ -60,7 +60,7 @@ class NotificationClickerLogger @Inject constructor( fun logGutsExposed(entry: NotificationEntry) { buffer.log(TAG, LogLevel.DEBUG, { - str1 = entry.key + str1 = entry.logKey }, { "Ignoring click on $str1; guts are exposed" }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java index c3cc97bc14a3..7cfb1571ccf0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java @@ -24,7 +24,8 @@ import android.widget.ImageView; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.util.Compile; /** * A util class for various reusable functions @@ -74,12 +75,18 @@ public class NotificationUtils { return (int) (dimensionPixelSize * factor); } + private static final boolean INCLUDE_HASH_CODE_IN_LIST_ENTRY_LOG_KEY = false; + /** Get the notification key, reformatted for logging, for the (optional) entry */ - public static String logKey(NotificationEntry entry) { + public static String logKey(ListEntry entry) { if (entry == null) { return "null"; } - return logKey(entry.getKey()); + if (Compile.IS_DEBUG && INCLUDE_HASH_CODE_IN_LIST_ENTRY_LOG_KEY) { + return logKey(entry.getKey()) + "@" + Integer.toHexString(entry.hashCode()); + } else { + return logKey(entry.getKey()); + } } /** Removes newlines from the notification key to prettify apps that have these in the tag */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt new file mode 100644 index 000000000000..432bac49fa9b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt @@ -0,0 +1,31 @@ +/* + * 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.statusbar.notification + +import android.service.notification.StatusBarNotification +import com.android.systemui.statusbar.notification.collection.ListEntry + +/** Get the notification key, reformatted for logging, for the (optional) entry */ +val ListEntry?.logKey: String? + get() = this?.let { NotificationUtils.logKey(it) } + +/** Get the notification key, reformatted for logging, for the (optional) sbn */ +val StatusBarNotification?.logKey: String? + get() = this?.key?.let { NotificationUtils.logKey(it) } + +/** Removes newlines from the notification key to prettify apps that have these in the tag */ +fun logKey(key: String?): String? = NotificationUtils.logKey(key)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index bcd8e594ffbd..6085096ee124 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -18,9 +18,12 @@ package com.android.systemui.statusbar.notification.collection; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; +import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; +import static android.service.notification.NotificationListenerService.REASON_CHANNEL_REMOVED; +import static android.service.notification.NotificationListenerService.REASON_CLEAR_DATA; import static android.service.notification.NotificationListenerService.REASON_CLICK; import static android.service.notification.NotificationListenerService.REASON_ERROR; import static android.service.notification.NotificationListenerService.REASON_GROUP_OPTIMIZATION; @@ -36,9 +39,11 @@ import static android.service.notification.NotificationListenerService.REASON_TI import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED; import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; +import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; +import static com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLoggerKt.cancellationReasonDebugString; import static java.util.Objects.requireNonNull; @@ -99,6 +104,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -143,6 +149,7 @@ public class NotifCollection implements Dumpable { private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>(); private final Collection<NotificationEntry> mReadOnlyNotificationSet = Collections.unmodifiableCollection(mNotificationSet.values()); + private final HashMap<String, FutureDismissal> mFutureDismissals = new HashMap<>(); @Nullable private CollectionReadyForBuildListener mBuildListener; private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>(); @@ -511,6 +518,7 @@ public class NotifCollection implements Dumpable { cancelDismissInterception(entry); mEventQueue.add(new EntryRemovedEvent(entry, entry.mCancellationReason)); mEventQueue.add(new CleanUpEntryEvent(entry)); + handleFutureDismissal(entry); return true; } else { return false; @@ -519,31 +527,32 @@ public class NotifCollection implements Dumpable { /** * Get the group summary entry - * @param group + * @param groupKey * @return */ @Nullable - public NotificationEntry getGroupSummary(String group) { + public NotificationEntry getGroupSummary(String groupKey) { return mNotificationSet .values() .stream() - .filter(it -> Objects.equals(it.getSbn().getGroup(), group)) + .filter(it -> Objects.equals(it.getSbn().getGroupKey(), groupKey)) .filter(it -> it.getSbn().getNotification().isGroupSummary()) .findFirst().orElse(null); } /** - * Checks if the entry is the only child in the logical group - * @param entry - * @return + * Checks if the entry is the only child in the logical group; + * it need not have a summary to qualify + * + * @param entry the entry to check */ public boolean isOnlyChildInGroup(NotificationEntry entry) { - String group = entry.getSbn().getGroup(); + String groupKey = entry.getSbn().getGroupKey(); return mNotificationSet.get(entry.getKey()) == entry && mNotificationSet .values() .stream() - .filter(it -> Objects.equals(it.getSbn().getGroup(), group)) + .filter(it -> Objects.equals(it.getSbn().getGroupKey(), groupKey)) .filter(it -> !it.getSbn().getNotification().isGroupSummary()) .count() == 1; } @@ -916,10 +925,139 @@ public class NotifCollection implements Dumpable { dispatchEventsAndRebuildList(); } + /** + * A method to alert the collection that an async operation is happening, at the end of which a + * dismissal request will be made. This method has the additional guarantee that if a parent + * notification exists for a single child, then that notification will also be dismissed. + * + * The runnable returned must be run at the end of the async operation to enact the cancellation + * + * @param entry the notification we want to dismiss + * @param cancellationReason the reason for the cancellation + * @param statsCreator the callback for generating the stats for an entry + * @return the runnable to be run when the dismissal is ready to happen + */ + public Runnable registerFutureDismissal(NotificationEntry entry, int cancellationReason, + DismissedByUserStatsCreator statsCreator) { + FutureDismissal dismissal = mFutureDismissals.get(entry.getKey()); + if (dismissal != null) { + mLogger.logFutureDismissalReused(dismissal); + return dismissal; + } + dismissal = new FutureDismissal(entry, cancellationReason, statsCreator); + mFutureDismissals.put(entry.getKey(), dismissal); + mLogger.logFutureDismissalRegistered(dismissal); + return dismissal; + } + + private void handleFutureDismissal(NotificationEntry entry) { + final FutureDismissal futureDismissal = mFutureDismissals.remove(entry.getKey()); + if (futureDismissal != null) { + futureDismissal.onSystemServerCancel(entry.mCancellationReason); + } + } + + /** A single method interface that callers can pass in when registering future dismissals */ + public interface DismissedByUserStatsCreator { + DismissedByUserStats createDismissedByUserStats(NotificationEntry entry); + } + + /** A class which tracks the double dismissal events coming in from both the system server and + * the ui */ + public class FutureDismissal implements Runnable { + private final NotificationEntry mEntry; + private final DismissedByUserStatsCreator mStatsCreator; + @Nullable + private final NotificationEntry mSummaryToDismiss; + private final String mLabel; + + private boolean mDidRun; + private boolean mDidSystemServerCancel; + + private FutureDismissal(NotificationEntry entry, @CancellationReason int cancellationReason, + DismissedByUserStatsCreator statsCreator) { + mEntry = entry; + mStatsCreator = statsCreator; + mSummaryToDismiss = fetchSummaryToDismiss(entry); + mLabel = "<FutureDismissal@" + Integer.toHexString(hashCode()) + + " entry=" + logKey(mEntry) + + " reason=" + cancellationReasonDebugString(cancellationReason) + + " summary=" + logKey(mSummaryToDismiss) + + ">"; + } + + @Nullable + private NotificationEntry fetchSummaryToDismiss(NotificationEntry entry) { + if (isOnlyChildInGroup(entry)) { + String group = entry.getSbn().getGroupKey(); + NotificationEntry summary = getGroupSummary(group); + if (summary != null && summary.isDismissable()) return summary; + } + return null; + } + + /** called when the entry has been removed from the collection */ + public void onSystemServerCancel(@CancellationReason int cancellationReason) { + Assert.isMainThread(); + if (mDidSystemServerCancel) { + mLogger.logFutureDismissalDoubleCancelledByServer(this); + return; + } + mLogger.logFutureDismissalGotSystemServerCancel(this, cancellationReason); + mDidSystemServerCancel = true; + // TODO: Internally dismiss the summary now instead of waiting for onUiCancel + } + + private void onUiCancel() { + mFutureDismissals.remove(mEntry.getKey()); + final NotificationEntry currentEntry = getEntry(mEntry.getKey()); + // generate stats for the entry before dismissing summary, which could affect state + final DismissedByUserStats stats = mStatsCreator.createDismissedByUserStats(mEntry); + // dismiss the summary (if it exists) + if (mSummaryToDismiss != null) { + final NotificationEntry currentSummary = getEntry(mSummaryToDismiss.getKey()); + if (currentSummary == mSummaryToDismiss) { + mLogger.logFutureDismissalDismissing(this, "summary"); + dismissNotification(mSummaryToDismiss, + mStatsCreator.createDismissedByUserStats(mSummaryToDismiss)); + } else { + mLogger.logFutureDismissalMismatchedEntry(this, "summary", currentSummary); + } + } + // dismiss this entry (if it is still around) + if (mDidSystemServerCancel) { + mLogger.logFutureDismissalAlreadyCancelledByServer(this); + } else if (currentEntry == mEntry) { + mLogger.logFutureDismissalDismissing(this, "entry"); + dismissNotification(mEntry, stats); + } else { + mLogger.logFutureDismissalMismatchedEntry(this, "entry", currentEntry); + } + } + + /** called when the dismissal should be completed */ + @Override + public void run() { + Assert.isMainThread(); + if (mDidRun) { + mLogger.logFutureDismissalDoubleRun(this); + return; + } + mDidRun = true; + onUiCancel(); + } + + /** provides a debug label for this instance */ + public String getLabel() { + return mLabel; + } + } + @IntDef(prefix = { "REASON_" }, value = { REASON_NOT_CANCELED, REASON_UNKNOWN, REASON_CLICK, + REASON_CANCEL, REASON_CANCEL_ALL, REASON_ERROR, REASON_PACKAGE_CHANGED, @@ -937,6 +1075,9 @@ public class NotifCollection implements Dumpable { REASON_CHANNEL_BANNED, REASON_SNOOZED, REASON_TIMEOUT, + REASON_CHANNEL_REMOVED, + REASON_CLEAR_DATA, + REASON_ASSISTANT_CANCEL, }) @Retention(RetentionPolicy.SOURCE) public @interface CancellationReason {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ActivityLaunchAnimCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ActivityLaunchAnimCoordinator.kt deleted file mode 100644 index b54163d29e80..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ActivityLaunchAnimCoordinator.kt +++ /dev/null @@ -1,95 +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.statusbar.notification.collection.coordinator - -import com.android.systemui.statusbar.notification.collection.NotifPipeline -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback -import com.android.systemui.statusbar.phone.NotifActivityLaunchEvents -import dagger.Binds -import dagger.Module -import javax.inject.Inject - -/** Extends the lifetime of notifications while their activity launch animation is playing. */ -interface ActivityLaunchAnimCoordinator : Coordinator - -/** Provides an [ActivityLaunchAnimCoordinator] to [CoordinatorScope]. */ -@Module(includes = [PrivateActivityStarterCoordinatorModule::class]) -object ActivityLaunchAnimCoordinatorModule - -@Module -private interface PrivateActivityStarterCoordinatorModule { - @Binds - fun bindCoordinator(impl: ActivityLaunchAnimCoordinatorImpl): ActivityLaunchAnimCoordinator -} - -/** - * Listens for [NotifActivityLaunchEvents], and then extends the lifetimes of any notifs while their - * launch animation is playing. - */ -@CoordinatorScope -private class ActivityLaunchAnimCoordinatorImpl @Inject constructor( - private val activityLaunchEvents: NotifActivityLaunchEvents -) : ActivityLaunchAnimCoordinator { - // Tracks notification launches, and whether or not their lifetimes are extended. - private val notifsLaunchingActivities = mutableMapOf<String, Boolean>() - - private var onEndLifetimeExtensionCallback: OnEndLifetimeExtensionCallback? = null - - override fun attach(pipeline: NotifPipeline) { - activityLaunchEvents.registerListener(activityStartEventListener) - pipeline.addNotificationLifetimeExtender(extender) - } - - private val activityStartEventListener = object : NotifActivityLaunchEvents.Listener { - override fun onStartLaunchNotifActivity(entry: NotificationEntry) { - notifsLaunchingActivities[entry.key] = false - } - - override fun onFinishLaunchNotifActivity(entry: NotificationEntry) { - if (notifsLaunchingActivities.remove(entry.key) == true) { - // If we were extending the lifetime of this notification, stop. - onEndLifetimeExtensionCallback?.onEndLifetimeExtension(extender, entry) - } - } - } - - private val extender = object : NotifLifetimeExtender { - override fun getName(): String = "ActivityStarterCoordinator" - - override fun setCallback(callback: OnEndLifetimeExtensionCallback) { - onEndLifetimeExtensionCallback = callback - } - - override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { - if (entry.key in notifsLaunchingActivities) { - // Track that we're now extending this notif - notifsLaunchingActivities[entry.key] = true - return true - } - return false - } - - override fun cancelLifetimeExtension(entry: NotificationEntry) { - if (entry.key in notifsLaunchingActivities) { - notifsLaunchingActivities[entry.key] = false - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index b24d2922adfb..0b3a0dc9dd58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -56,7 +56,6 @@ class NotifCoordinatorsImpl @Inject constructor( smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, viewConfigCoordinator: ViewConfigCoordinator, visualStabilityCoordinator: VisualStabilityCoordinator, - activityLaunchAnimCoordinator: ActivityLaunchAnimCoordinator ) : NotifCoordinators { private val mCoordinators: MutableList<Coordinator> = ArrayList() @@ -93,7 +92,6 @@ class NotifCoordinatorsImpl @Inject constructor( mCoordinators.add(shadeEventCoordinator) mCoordinators.add(viewConfigCoordinator) mCoordinators.add(visualStabilityCoordinator) - mCoordinators.add(activityLaunchAnimCoordinator) if (notifPipelineFlags.isSmartspaceDedupingEnabled()) { mCoordinators.add(smartspaceDedupingCoordinator) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt index 8ecffcb7670a..839cf0d7ef92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator.dagger import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.collection.coordinator.ActivityLaunchAnimCoordinatorModule import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinatorsImpl import dagger.Binds @@ -48,7 +47,6 @@ interface CoordinatorsSubcomponent { } @Module(includes = [ - ActivityLaunchAnimCoordinatorModule::class, ]) private abstract class InternalCoordinatorsModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java index 3bd91b5c8480..7dd3672a6e30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java @@ -18,17 +18,17 @@ package com.android.systemui.statusbar.notification.collection.inflation; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; -import android.annotation.Nullable; import android.os.SystemClock; -import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; +import androidx.annotation.NonNull; + import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; -import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -43,54 +43,33 @@ public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback private final HeadsUpManager mHeadsUpManager; private final StatusBarStateController mStatusBarStateController; private final VisualStabilityCoordinator mVisualStabilityCoordinator; - private final GroupMembershipManager mGroupMembershipManager; public OnUserInteractionCallbackImpl( NotificationVisibilityProvider visibilityProvider, NotifCollection notifCollection, HeadsUpManager headsUpManager, StatusBarStateController statusBarStateController, - VisualStabilityCoordinator visualStabilityCoordinator, - GroupMembershipManager groupMembershipManager + VisualStabilityCoordinator visualStabilityCoordinator ) { mVisibilityProvider = visibilityProvider; mNotifCollection = notifCollection; mHeadsUpManager = headsUpManager; mStatusBarStateController = statusBarStateController; mVisualStabilityCoordinator = visualStabilityCoordinator; - mGroupMembershipManager = groupMembershipManager; } - /** - * Callback triggered when a user: - * 1. Manually dismisses a notification {@see ExpandableNotificationRow}. - * 2. Clicks on a notification with flag {@link android.app.Notification#FLAG_AUTO_CANCEL}. - * {@see StatusBarNotificationActivityStarter} - */ - @Override - public void onDismiss( - NotificationEntry entry, - @NotificationListenerService.NotificationCancelReason int cancellationReason, - @Nullable NotificationEntry groupSummaryToDismiss - ) { + @NonNull + private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) { int dismissalSurface = NotificationStats.DISMISSAL_SHADE; if (mHeadsUpManager.isAlerting(entry.getKey())) { dismissalSurface = NotificationStats.DISMISSAL_PEEK; } else if (mStatusBarStateController.isDozing()) { dismissalSurface = NotificationStats.DISMISSAL_AOD; } - - if (groupSummaryToDismiss != null) { - onDismiss(groupSummaryToDismiss, cancellationReason, null); - } - - mNotifCollection.dismissNotification( - entry, - new DismissedByUserStats( - dismissalSurface, - DISMISS_SENTIMENT_NEUTRAL, - mVisibilityProvider.obtain(entry, true)) - ); + return new DismissedByUserStats( + dismissalSurface, + DISMISS_SENTIMENT_NEUTRAL, + mVisibilityProvider.obtain(entry, true)); } @Override @@ -100,19 +79,11 @@ public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback SystemClock.uptimeMillis()); } - /** - * @param entry that is being dismissed - * @return the group summary to dismiss along with this entry if this is the last entry in - * the group. Else, returns null. - */ + @NonNull @Override - @Nullable - public NotificationEntry getGroupSummaryToDismiss(NotificationEntry entry) { - String group = entry.getSbn().getGroup(); - if (mNotifCollection.isOnlyChildInGroup(entry)) { - NotificationEntry summary = mNotifCollection.getGroupSummary(group); - if (summary != null && summary.isDismissable()) return summary; - } - return null; + public Runnable registerFutureDismissal(@NonNull NotificationEntry entry, + @CancellationReason int cancellationReason) { + return mNotifCollection.registerFutureDismissal( + entry, cancellationReason, this::getDismissedByUserStats); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java index 8daf8be0cc8f..103b14b09e9c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java @@ -18,12 +18,15 @@ package com.android.systemui.statusbar.notification.collection.legacy; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; -import android.annotation.Nullable; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -68,8 +71,7 @@ public class OnUserInteractionCallbackImplLegacy implements OnUserInteractionCal * along with this dismissal. If null, does not additionally * dismiss any notifications. */ - @Override - public void onDismiss( + private void onDismiss( NotificationEntry entry, @NotificationListenerService.NotificationCancelReason int cancellationReason, @Nullable NotificationEntry groupSummaryToDismiss @@ -106,14 +108,21 @@ public class OnUserInteractionCallbackImplLegacy implements OnUserInteractionCal * @return the group summary to dismiss along with this entry if this is the last entry in * the group. Else, returns null. */ - @Override @Nullable - public NotificationEntry getGroupSummaryToDismiss(NotificationEntry entry) { + private NotificationEntry getGroupSummaryToDismiss(NotificationEntry entry) { if (mGroupMembershipManager.isOnlyChildInGroup(entry)) { NotificationEntry groupSummary = mGroupMembershipManager.getLogicalGroupSummary(entry); return groupSummary.isDismissable() ? groupSummary : null; } return null; } + + @Override + @NonNull + public Runnable registerFutureDismissal(@NonNull NotificationEntry entry, + @CancellationReason int cancellationReason) { + NotificationEntry groupSummaryToDismiss = getGroupSummaryToDismiss(entry); + return () -> onDismiss(entry, cancellationReason, groupSummaryToDismiss); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index 7302de57e955..7e7936717b84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -28,7 +28,9 @@ import com.android.systemui.log.LogLevel.WTF import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason +import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject fun cancellationReasonDebugString(@CancellationReason reason: Int) = @@ -36,6 +38,7 @@ fun cancellationReasonDebugString(@CancellationReason reason: Int) = -1 -> "REASON_NOT_CANCELED" // NotifCollection.REASON_NOT_CANCELED NotifCollection.REASON_UNKNOWN -> "REASON_UNKNOWN" NotificationListenerService.REASON_CLICK -> "REASON_CLICK" + NotificationListenerService.REASON_CANCEL -> "REASON_CANCEL" NotificationListenerService.REASON_CANCEL_ALL -> "REASON_CANCEL_ALL" NotificationListenerService.REASON_ERROR -> "REASON_ERROR" NotificationListenerService.REASON_PACKAGE_CHANGED -> "REASON_PACKAGE_CHANGED" @@ -53,6 +56,9 @@ fun cancellationReasonDebugString(@CancellationReason reason: Int) = NotificationListenerService.REASON_CHANNEL_BANNED -> "REASON_CHANNEL_BANNED" NotificationListenerService.REASON_SNOOZED -> "REASON_SNOOZED" NotificationListenerService.REASON_TIMEOUT -> "REASON_TIMEOUT" + NotificationListenerService.REASON_CHANNEL_REMOVED -> "REASON_CHANNEL_REMOVED" + NotificationListenerService.REASON_CLEAR_DATA -> "REASON_CLEAR_DATA" + NotificationListenerService.REASON_ASSISTANT_CANCEL -> "REASON_ASSISTANT_CANCEL" else -> "unknown" } @@ -241,6 +247,81 @@ class NotifCollectionLogger @Inject constructor( "ERROR suppressed due to initialization forgiveness: $str1" }) } + + fun logFutureDismissalReused(dismissal: FutureDismissal) { + buffer.log(TAG, INFO, { + str1 = dismissal.label + }, { + "Reusing existing registration: $str1" + }) + } + + fun logFutureDismissalRegistered(dismissal: FutureDismissal) { + buffer.log(TAG, DEBUG, { + str1 = dismissal.label + }, { + "Registered: $str1" + }) + } + + fun logFutureDismissalDoubleCancelledByServer(dismissal: FutureDismissal) { + buffer.log(TAG, WARNING, { + str1 = dismissal.label + }, { + "System server double cancelled: $str1" + }) + } + + fun logFutureDismissalDoubleRun(dismissal: FutureDismissal) { + buffer.log(TAG, WARNING, { + str1 = dismissal.label + }, { + "Double run: $str1" + }) + } + + fun logFutureDismissalAlreadyCancelledByServer(dismissal: FutureDismissal) { + buffer.log(TAG, DEBUG, { + str1 = dismissal.label + }, { + "Ignoring: entry already cancelled by server: $str1" + }) + } + + fun logFutureDismissalGotSystemServerCancel( + dismissal: FutureDismissal, + @CancellationReason cancellationReason: Int + ) { + buffer.log(TAG, DEBUG, { + str1 = dismissal.label + int1 = cancellationReason + }, { + "SystemServer cancelled: $str1 reason=${cancellationReasonDebugString(int1)}" + }) + } + + fun logFutureDismissalDismissing(dismissal: FutureDismissal, type: String) { + buffer.log(TAG, DEBUG, { + str1 = dismissal.label + str2 = type + }, { + "Dismissing $str2 for: $str1" + }) + } + + fun logFutureDismissalMismatchedEntry( + dismissal: FutureDismissal, + type: String, + latestEntry: NotificationEntry? + ) { + buffer.log(TAG, WARNING, { + str1 = dismissal.label + str2 = type + str3 = latestEntry.logKey + }, { + "Mismatch: current $str2 is $str3 for: $str1" + }) + } } private const val TAG = "NotifCollection" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index d96590a82547..c9c7fe9e0ab6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -88,7 +88,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationSectionsMan import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotifActivityLaunchEventsModule; import com.android.systemui.statusbar.phone.NotifPanelEventsModule; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -111,7 +110,6 @@ import dagger.Provides; @Module(includes = { CoordinatorsModule.class, KeyguardNotificationVisibilityProviderModule.class, - NotifActivityLaunchEventsModule.class, NotifPanelEventsModule.class, NotifPipelineChoreographerModule.class, NotificationSectionHeadersModule.class, @@ -350,8 +348,7 @@ public interface NotificationsModule { notifCollection.get(), headsUpManager, statusBarStateController, - visualStabilityCoordinator.get(), - groupMembershipManagerLazy.get()) + visualStabilityCoordinator.get()) : new OnUserInteractionCallbackImplLegacy( entryManager, visibilityProvider.get(), 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 f975799b4b85..d5088acaa61c 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 @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.row; import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY; -import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; @@ -34,8 +33,6 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.role.RoleManager; import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; @@ -51,7 +48,6 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Trace; -import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.AttributeSet; @@ -113,7 +109,6 @@ import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.SwipeableView; -import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.InflatedSmartReplyState; @@ -1428,8 +1423,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView dismiss(fromAccessibility); if (mEntry.isDismissable()) { if (mOnUserInteractionCallback != null) { - mOnUserInteractionCallback.onDismiss(mEntry, REASON_CANCEL, - mOnUserInteractionCallback.getGroupSummaryToDismiss(mEntry)); + mOnUserInteractionCallback.registerFutureDismissal(mEntry, REASON_CANCEL).run(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallback.java index 94c5507cfbf4..98d43539841d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallback.java @@ -16,8 +16,9 @@ package com.android.systemui.statusbar.notification.row; -import android.service.notification.NotificationListenerService; +import androidx.annotation.NonNull; +import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** @@ -26,29 +27,23 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; public interface OnUserInteractionCallback { /** - * Handle a user interaction that triggers a notification dismissal. Called when a user clicks - * on an auto-cancelled notification or manually swipes to dismiss the notification. - * - * @param entry notification being dismissed - * @param cancellationReason reason for the cancellation - * @param groupSummaryToDismiss group summary to dismiss with `entry`. - */ - void onDismiss( - NotificationEntry entry, - @NotificationListenerService.NotificationCancelReason int cancellationReason, - NotificationEntry groupSummaryToDismiss); - - /** * Triggered after a user has changed the importance of the notification via its * {@link NotificationGuts}. */ void onImportanceChanged(NotificationEntry entry); - /** - * @param entry being dismissed by the user - * @return group summary that should be dismissed along with `entry`. Can be null if no - * relevant group summary exists or the group summary should not be dismissed with `entry`. + * Called once it is known that a dismissal will take place for the given reason. + * This returns a Runnable which MUST be invoked when the dismissal is ready to be completed. + * + * Registering for future dismissal is typically done before notifying the NMS that a + * notification was clicked or dismissed, but the local dismissal may happen later. + * + * @param entry the entry being cancelled + * @param cancellationReason the reason for the cancellation + * @return the runnable to call when the dismissal can happen */ - NotificationEntry getGroupSummaryToDismiss(NotificationEntry entry); + @NonNull + Runnable registerFutureDismissal(@NonNull NotificationEntry entry, + @CancellationReason int cancellationReason); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifActivityLaunchEvents.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifActivityLaunchEvents.kt deleted file mode 100644 index f46d07338206..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifActivityLaunchEvents.kt +++ /dev/null @@ -1,39 +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.statusbar.phone - -import com.android.systemui.statusbar.notification.collection.NotificationEntry - -/** Provides events about [android.app.Activity] launches from Notifications. */ -interface NotifActivityLaunchEvents { - - /** Registers a [Listener] to be invoked when notification activity launch events occur. */ - fun registerListener(listener: Listener) - - /** Unregisters a [Listener] previously registered via [registerListener] */ - fun unregisterListener(listener: Listener) - - /** Listener for events about [android.app.Activity] launches from Notifications. */ - interface Listener { - - /** Invoked when an activity has started launching from a notification. */ - fun onStartLaunchNotifActivity(entry: NotificationEntry) - - /** Invoked when an activity has finished launching. */ - fun onFinishLaunchNotifActivity(entry: NotificationEntry) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifActivityLaunchEventsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifActivityLaunchEventsModule.java deleted file mode 100644 index 84ff538677b0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifActivityLaunchEventsModule.java +++ /dev/null @@ -1,30 +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.statusbar.phone; - -import com.android.systemui.dagger.SysUISingleton; - -import dagger.Binds; -import dagger.Module; - -/** Provides a {@link NotifActivityLaunchEvents} in {@link SysUISingleton} scope. */ -@Module -public abstract class NotifActivityLaunchEventsModule { - @Binds - abstract NotifActivityLaunchEvents bindLaunchEvents( - StatusBarNotificationActivityStarter.LaunchEventsEmitter impl); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index f15ea627af47..314ab2221b19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3377,9 +3377,9 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void setIsLaunchAnimationRunning(boolean running) { - boolean wasRunning = isLaunchTransitionRunning(); + boolean wasRunning = mIsLaunchAnimationRunning; super.setIsLaunchAnimationRunning(running); - if (wasRunning != isLaunchTransitionRunning()) { + if (wasRunning != mIsLaunchAnimationRunning) { mPanelEventsEmitter.notifyLaunchingActivityChanged(running); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt index 56b6dfc42ee3..c0922163903f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -20,7 +20,9 @@ class StatusBarLaunchAnimatorController( override fun onIntentStarted(willAnimate: Boolean) { delegate.onIntentStarted(willAnimate) - if (!willAnimate) { + if (willAnimate) { + centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(true) + } else { centralSurfaces.collapsePanelOnMainThread() } } @@ -51,6 +53,7 @@ class StatusBarLaunchAnimatorController( override fun onLaunchAnimationCancelled() { delegate.onLaunchAnimationCancelled() + centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(false) centralSurfaces.onLaunchAnimationCancelled(isLaunchForActivity) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 87ca942edff2..cf776e3b60d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -41,7 +41,6 @@ import android.text.TextUtils; import android.util.EventLog; import android.view.View; -import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; @@ -52,7 +51,6 @@ import com.android.systemui.ActivityIntentHelper; import com.android.systemui.EventLogTags; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; @@ -77,7 +75,6 @@ import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.ListenerSet; import com.android.systemui.wmshell.BubblesManager; import java.util.Optional; @@ -131,7 +128,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte private final ActivityLaunchAnimator mActivityLaunchAnimator; private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider; private final OnUserInteractionCallback mOnUserInteractionCallback; - private final LaunchEventsEmitter mLaunchEventsEmitter; private boolean mIsCollapsingToShowActivityOverLockscreen; @@ -170,8 +166,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte NotificationPresenter presenter, NotificationPanelViewController panel, ActivityLaunchAnimator activityLaunchAnimator, - NotificationLaunchAnimatorControllerProvider notificationAnimationProvider, - LaunchEventsEmitter launchEventsEmitter) { + NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) { mContext = context; mCommandQueue = commandQueue; mMainThreadHandler = mainThreadHandler; @@ -207,7 +202,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte mNotificationPanel = panel; mActivityLaunchAnimator = activityLaunchAnimator; mNotificationAnimationProvider = notificationAnimationProvider; - mLaunchEventsEmitter = launchEventsEmitter; if (!mNotifPipelineFlags.isNewPipelineEnabled()) { mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @@ -229,14 +223,13 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte /** * Called when a notification is clicked. * - * @param sbn notification that was clicked + * @param entry notification that was clicked * @param row row for that notification */ @Override - public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) { - mLogger.logStartingActivityFromClick(sbn.getKey()); + public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) { + mLogger.logStartingActivityFromClick(entry); - final NotificationEntry entry = row.getEntry(); if (mRemoteInputManager.isRemoteInputActive(entry) && !TextUtils.isEmpty(row.getActiveRemoteInputText())) { // We have an active remote input typed and the user clicked on the notification. @@ -244,7 +237,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte mRemoteInputManager.closeRemoteInputs(); return; } - Notification notification = sbn.getNotification(); + Notification notification = entry.getSbn().getNotification(); final PendingIntent intent = notification.contentIntent != null ? notification.contentIntent : notification.fullScreenIntent; @@ -254,12 +247,10 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte // The only valid case is Bubble notifications. Guard against other cases // entering here. if (intent == null && !isBubble) { - mLogger.logNonClickableNotification(sbn.getKey()); + mLogger.logNonClickableNotification(entry); return; } - mLaunchEventsEmitter.notifyStartLaunchNotifActivity(entry); - boolean isActivityIntent = intent != null && intent.isActivity() && !isBubble; final boolean willLaunchResolverActivity = isActivityIntent && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(), @@ -287,7 +278,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte } else { mActivityStarter.dismissKeyguardThenExecute( postKeyguardAction, - () -> mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry), + null, willLaunchResolverActivity); } } @@ -299,7 +290,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte boolean isActivityIntent, boolean animate, boolean showOverLockscreen) { - mLogger.logHandleClickAfterKeyguardDismissed(entry.getKey()); + mLogger.logHandleClickAfterKeyguardDismissed(entry); final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed( entry, row, intent, isActivityIntent, animate); @@ -326,7 +317,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte boolean isActivityIntent, boolean animate) { String notificationKey = entry.getKey(); - mLogger.logHandleClickAfterPanelCollapsed(notificationKey); + mLogger.logHandleClickAfterPanelCollapsed(entry); try { // The intent we are sending is for the application, which @@ -367,11 +358,9 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte } final boolean canBubble = entry.canBubble(); if (canBubble) { - mLogger.logExpandingBubble(notificationKey); + mLogger.logExpandingBubble(entry); removeHunAfterClick(row); expandBubbleStackOnMainThread(entry); - mMainThreadHandler.post( - () -> mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry)); } else { startNotificationIntent(intent, fillInIntent, entry, row, animate, isActivityIntent); } @@ -381,30 +370,13 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true); - // retrieve the group summary to remove with this entry before we tell NMS the - // notification was clicked to avoid a race condition - final boolean shouldAutoCancel = shouldAutoCancel(entry.getSbn()); - final NotificationEntry summaryToRemove = shouldAutoCancel - ? mOnUserInteractionCallback.getGroupSummaryToDismiss(entry) : null; - - // inform NMS that the notification was clicked - mClickNotifier.onNotificationClick(notificationKey, nv); - - if (!canBubble && (shouldAutoCancel + if (!canBubble && (shouldAutoCancel(entry.getSbn()) || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(notificationKey))) { + final Runnable removeNotification = + mOnUserInteractionCallback.registerFutureDismissal(entry, REASON_CLICK); // Immediately remove notification from visually showing. // We have to post the removal to the UI thread for synchronization. mMainThreadHandler.post(() -> { - final Runnable removeNotification = () -> { - mOnUserInteractionCallback.onDismiss(entry, REASON_CLICK, summaryToRemove); - if (!animate) { - // If we're animating, this would be invoked after the activity launch - // animation completes. Since we're not animating, the launch already - // happened synchronously, so we notify the launch is complete here after - // onDismiss. - mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry); - } - }; if (mPresenter.isCollapsing()) { // To avoid lags we're only performing the remove after the shade is collapsed mShadeController.addPostCollapseAction(removeNotification); @@ -412,12 +384,11 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte removeNotification.run(); } }); - } else if (!canBubble && !animate) { - // Not animating, this is the end of the launch flow (see above comment for more info). - mMainThreadHandler.post( - () -> mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry)); } + // inform NMS that the notification was clicked + mClickNotifier.onNotificationClick(notificationKey, nv); + mIsCollapsingToShowActivityOverLockscreen = false; } @@ -434,24 +405,14 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte // will focus follow operation only after drag-and-drop that notification. final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true); - // retrieve the group summary to remove with this entry before we tell NMS the - // notification was clicked to avoid a race condition - final boolean shouldAutoCancel = shouldAutoCancel(entry.getSbn()); - final NotificationEntry summaryToRemove = shouldAutoCancel - ? mOnUserInteractionCallback.getGroupSummaryToDismiss(entry) : null; - String notificationKey = entry.getKey(); - // inform NMS that the notification was clicked - mClickNotifier.onNotificationClick(notificationKey, nv); - - if (shouldAutoCancel || mRemoteInputManager.isNotificationKeptForRemoteInputHistory( - notificationKey)) { + if (shouldAutoCancel(entry.getSbn()) + || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(notificationKey)) { + final Runnable removeNotification = + mOnUserInteractionCallback.registerFutureDismissal(entry, REASON_CLICK); // Immediately remove notification from visually showing. // We have to post the removal to the UI thread for synchronization. mMainThreadHandler.post(() -> { - final Runnable removeNotification = () -> - mOnUserInteractionCallback.onDismiss( - entry, REASON_CLICK, summaryToRemove); if (mPresenter.isCollapsing()) { // To avoid lags we're only performing the remove // after the shade is collapsed @@ -462,6 +423,9 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte }); } + // inform NMS that the notification was clicked + mClickNotifier.onNotificationClick(notificationKey, nv); + mIsCollapsingToShowActivityOverLockscreen = false; } @@ -489,15 +453,11 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte ExpandableNotificationRow row, boolean animate, boolean isActivityIntent) { - mLogger.logStartNotificationIntent(entry.getKey()); + mLogger.logStartNotificationIntent(entry); try { - Runnable onFinishAnimationCallback = animate - ? () -> mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry) - : null; ActivityLaunchAnimator.Controller animationController = new StatusBarLaunchAnimatorController( - mNotificationAnimationProvider - .getAnimatorController(row, onFinishAnimationCallback), + mNotificationAnimationProvider.getAnimatorController(row, null), mCentralSurfaces, isActivityIntent); mActivityLaunchAnimator.startPendingIntentWithAnimation( @@ -515,7 +475,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte : getActivityOptions(mCentralSurfaces.getDisplayId(), adapter); int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null, null, null, options); - mLogger.logSendPendingIntent(entry.getKey(), intent, result); + mLogger.logSendPendingIntent(entry, intent, result); return result; }); } catch (PendingIntent.CanceledException e) { @@ -622,9 +582,9 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte void handleFullScreenIntent(NotificationEntry entry) { if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) { if (shouldSuppressFullScreenIntent(entry)) { - mLogger.logFullScreenIntentSuppressedByDnD(entry.getKey()); + mLogger.logFullScreenIntentSuppressedByDnD(entry); } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) { - mLogger.logFullScreenIntentNotImportantEnough(entry.getKey()); + mLogger.logFullScreenIntentNotImportantEnough(entry); } else { // Stop screensaver if the notification has a fullscreen intent. // (like an incoming phone call) @@ -639,7 +599,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte // not immersive & a fullscreen alert should be shown final PendingIntent fullscreenIntent = entry.getSbn().getNotification().fullScreenIntent; - mLogger.logSendingFullScreenIntent(entry.getKey(), fullscreenIntent); + mLogger.logSendingFullScreenIntent(entry, fullscreenIntent); try { EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, entry.getKey()); @@ -685,35 +645,4 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte return entry.shouldSuppressFullScreenIntent(); } - - @SysUISingleton - static class LaunchEventsEmitter implements NotifActivityLaunchEvents { - - private final ListenerSet<Listener> mListeners = new ListenerSet<>(); - - @Inject - LaunchEventsEmitter() {} - - @Override - public void registerListener(@NonNull Listener listener) { - mListeners.addIfAbsent(listener); - } - - @Override - public void unregisterListener(@NonNull Listener listener) { - mListeners.remove(listener); - } - - private void notifyStartLaunchNotifActivity(NotificationEntry entry) { - for (Listener listener : mListeners) { - listener.onStartLaunchNotifActivity(entry); - } - } - - private void notifyFinishLaunchNotifActivity(NotificationEntry entry) { - for (Listener listener : mListeners) { - listener.onFinishLaunchNotifActivity(entry); - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt index 2fbe520a4b61..b9a1413ff791 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt @@ -23,46 +23,48 @@ import com.android.systemui.log.LogLevel.ERROR import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.dagger.NotifInteractionLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject class StatusBarNotificationActivityStarterLogger @Inject constructor( @NotifInteractionLog private val buffer: LogBuffer ) { - fun logStartingActivityFromClick(key: String) { + fun logStartingActivityFromClick(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = key + str1 = entry.logKey }, { "(1/5) onNotificationClicked: $str1" }) } - fun logHandleClickAfterKeyguardDismissed(key: String) { + fun logHandleClickAfterKeyguardDismissed(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = key + str1 = entry.logKey }, { "(2/5) handleNotificationClickAfterKeyguardDismissed: $str1" }) } - fun logHandleClickAfterPanelCollapsed(key: String) { + fun logHandleClickAfterPanelCollapsed(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = key + str1 = entry.logKey }, { "(3/5) handleNotificationClickAfterPanelCollapsed: $str1" }) } - fun logStartNotificationIntent(key: String) { + fun logStartNotificationIntent(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "(4/5) startNotificationIntent: $str1" }) } - fun logSendPendingIntent(key: String, pendingIntent: PendingIntent, result: Int) { + fun logSendPendingIntent(entry: NotificationEntry, pendingIntent: PendingIntent, result: Int) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey str2 = pendingIntent.intent.toString() int1 = result }, { @@ -70,9 +72,9 @@ class StatusBarNotificationActivityStarterLogger @Inject constructor( }) } - fun logExpandingBubble(key: String) { + fun logExpandingBubble(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = key + str1 = entry.logKey }, { "Expanding bubble for $str1 (rather than firing intent)" }) @@ -86,33 +88,33 @@ class StatusBarNotificationActivityStarterLogger @Inject constructor( }) } - fun logNonClickableNotification(key: String) { + fun logNonClickableNotification(entry: NotificationEntry) { buffer.log(TAG, ERROR, { - str1 = key + str1 = entry.logKey }, { "onNotificationClicked called for non-clickable notification! $str1" }) } - fun logFullScreenIntentSuppressedByDnD(key: String) { + fun logFullScreenIntentSuppressedByDnD(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = key + str1 = entry.logKey }, { "No Fullscreen intent: suppressed by DND: $str1" }) } - fun logFullScreenIntentNotImportantEnough(key: String) { + fun logFullScreenIntentNotImportantEnough(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = key + str1 = entry.logKey }, { "No Fullscreen intent: not important enough: $str1" }) } - fun logSendingFullScreenIntent(key: String, pendingIntent: PendingIntent) { + fun logSendingFullScreenIntent(entry: NotificationEntry, pendingIntent: PendingIntent) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey str2 = pendingIntent.intent.toString() }, { "Notification $str1 has fullScreenIntent; sending fullScreenIntent $str2" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 706800940fd1..958d54230f1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -32,6 +32,8 @@ import static com.android.systemui.statusbar.notification.collection.Notificatio import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; +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.assertNotEquals; @@ -47,6 +49,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static java.util.Collections.singletonList; @@ -180,13 +183,14 @@ public class NotifCollectionTest extends SysuiTestCase { @Test public void testGetGroupSummary() { - assertEquals(null, mCollection.getGroupSummary("group")); - NotifEvent summary = mNoMan.postNotif( - buildNotif(TEST_PACKAGE, 0) - .setGroup(mContext, "group") - .setGroupSummary(mContext, true)); + final NotificationEntryBuilder entryBuilder = buildNotif(TEST_PACKAGE, 0) + .setGroup(mContext, "group") + .setGroupSummary(mContext, true); + final String groupKey = entryBuilder.build().getSbn().getGroupKey(); + assertEquals(null, mCollection.getGroupSummary(groupKey)); + NotifEvent summary = mNoMan.postNotif(entryBuilder); - final NotificationEntry entry = mCollection.getGroupSummary("group"); + final NotificationEntry entry = mCollection.getGroupSummary(groupKey); assertEquals(summary.key, entry.getKey()); assertEquals(summary.sbn, entry.getSbn()); assertEquals(summary.ranking, entry.getRanking()); @@ -194,9 +198,9 @@ public class NotifCollectionTest extends SysuiTestCase { @Test public void testIsOnlyChildInGroup() { - NotifEvent notif1 = mNoMan.postNotif( - buildNotif(TEST_PACKAGE, 1) - .setGroup(mContext, "group")); + final NotificationEntryBuilder entryBuilder = buildNotif(TEST_PACKAGE, 1) + .setGroup(mContext, "group"); + NotifEvent notif1 = mNoMan.postNotif(entryBuilder); final NotificationEntry entry = mCollection.getEntry(notif1.key); assertTrue(mCollection.isOnlyChildInGroup(entry)); @@ -1488,6 +1492,55 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testRegisterFutureDismissal() throws RemoteException { + // GIVEN a pipeline with one notification + NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = requireNonNull(mCollection.getEntry(notifEvent.key)); + clearInvocations(mCollectionListener); + + // WHEN registering a future dismissal, nothing happens right away + final Runnable onDismiss = mCollection.registerFutureDismissal(entry, REASON_CLICK, + NotifCollectionTest::defaultStats); + verifyNoMoreInteractions(mCollectionListener); + + // WHEN finally dismissing + onDismiss.run(); + verify(mStatusBarService).onNotificationClear(any(), anyInt(), eq(notifEvent.key), + anyInt(), anyInt(), any()); + verifyNoMoreInteractions(mStatusBarService); + verifyNoMoreInteractions(mCollectionListener); + } + + @Test + public void testRegisterFutureDismissalWithRetractionAndRepost() { + // GIVEN a pipeline with one notification + NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + NotificationEntry entry = requireNonNull(mCollection.getEntry(notifEvent.key)); + clearInvocations(mCollectionListener); + + // WHEN registering a future dismissal, nothing happens right away + final Runnable onDismiss = mCollection.registerFutureDismissal(entry, REASON_CLICK, + NotifCollectionTest::defaultStats); + verifyNoMoreInteractions(mCollectionListener); + + // WHEN retracting the notification, and then reposting + mNoMan.retractNotif(notifEvent.sbn, REASON_CLICK); + mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + clearInvocations(mCollectionListener); + + // KNOWING that the entry in the collection is different now + assertThat(mCollection.getEntry(notifEvent.key)).isNotSameInstanceAs(entry); + + // WHEN finally dismissing + onDismiss.run(); + + // VERIFY that nothing happens; the notification should not be removed + verifyNoMoreInteractions(mCollectionListener); + assertThat(mCollection.getEntry(notifEvent.key)).isNotNull(); + verifyNoMoreInteractions(mStatusBarService); + } + + @Test public void testCannotDismissOngoingNotificationChildren() { // GIVEN an ongoing notification final NotificationEntry container = new NotificationEntryBuilder() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ActivityLaunchAnimCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ActivityLaunchAnimCoordinatorTest.kt deleted file mode 100644 index c6c043aafb20..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ActivityLaunchAnimCoordinatorTest.kt +++ /dev/null @@ -1,138 +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.statusbar.notification.collection.coordinator - -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.notification.collection.NotifPipeline -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender -import com.android.systemui.statusbar.phone.NotifActivityLaunchEvents -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor -import dagger.BindsInstance -import dagger.Component -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever - -@SmallTest -class ActivityLaunchAnimCoordinatorTest : SysuiTestCase() { - - val activityLaunchEvents: NotifActivityLaunchEvents = mock() - val pipeline: NotifPipeline = mock() - - val coordinator: ActivityLaunchAnimCoordinator = - DaggerTestActivityStarterCoordinatorComponent - .factory() - .create(activityLaunchEvents) - .coordinator - - @Test - fun testNoLifetimeExtensionIfNoAssociatedActivityLaunch() { - coordinator.attach(pipeline) - val lifetimeExtender = withArgCaptor<NotifLifetimeExtender> { - verify(pipeline).addNotificationLifetimeExtender(capture()) - } - val fakeEntry = mock<NotificationEntry>().also { - whenever(it.key).thenReturn("0") - } - assertFalse(lifetimeExtender.maybeExtendLifetime(fakeEntry, 0)) - } - - @Test - fun testNoLifetimeExtensionIfAssociatedActivityLaunchAlreadyEnded() { - coordinator.attach(pipeline) - val lifetimeExtender = withArgCaptor<NotifLifetimeExtender> { - verify(pipeline).addNotificationLifetimeExtender(capture()) - } - val eventListener = withArgCaptor<NotifActivityLaunchEvents.Listener> { - verify(activityLaunchEvents).registerListener(capture()) - } - val fakeEntry = mock<NotificationEntry>().also { - whenever(it.key).thenReturn("0") - } - eventListener.onStartLaunchNotifActivity(fakeEntry) - eventListener.onFinishLaunchNotifActivity(fakeEntry) - assertFalse(lifetimeExtender.maybeExtendLifetime(fakeEntry, 0)) - } - - @Test - fun testLifetimeExtensionWhileActivityLaunchInProgress() { - coordinator.attach(pipeline) - val lifetimeExtender = withArgCaptor<NotifLifetimeExtender> { - verify(pipeline).addNotificationLifetimeExtender(capture()) - } - val eventListener = withArgCaptor<NotifActivityLaunchEvents.Listener> { - verify(activityLaunchEvents).registerListener(capture()) - } - val onEndLifetimeExtensionCallback = - mock<NotifLifetimeExtender.OnEndLifetimeExtensionCallback>() - lifetimeExtender.setCallback(onEndLifetimeExtensionCallback) - - val fakeEntry = mock<NotificationEntry>().also { - whenever(it.key).thenReturn("0") - } - eventListener.onStartLaunchNotifActivity(fakeEntry) - assertTrue(lifetimeExtender.maybeExtendLifetime(fakeEntry, 0)) - - eventListener.onFinishLaunchNotifActivity(fakeEntry) - verify(onEndLifetimeExtensionCallback).onEndLifetimeExtension(lifetimeExtender, fakeEntry) - } - - @Test - fun testCancelLifetimeExtensionDoesNotInvokeCallback() { - coordinator.attach(pipeline) - val lifetimeExtender = withArgCaptor<NotifLifetimeExtender> { - verify(pipeline).addNotificationLifetimeExtender(capture()) - } - val eventListener = withArgCaptor<NotifActivityLaunchEvents.Listener> { - verify(activityLaunchEvents).registerListener(capture()) - } - val onEndLifetimeExtensionCallback = - mock<NotifLifetimeExtender.OnEndLifetimeExtensionCallback>() - lifetimeExtender.setCallback(onEndLifetimeExtensionCallback) - - val fakeEntry = mock<NotificationEntry>().also { - whenever(it.key).thenReturn("0") - } - eventListener.onStartLaunchNotifActivity(fakeEntry) - assertTrue(lifetimeExtender.maybeExtendLifetime(fakeEntry, 0)) - - lifetimeExtender.cancelLifetimeExtension(fakeEntry) - eventListener.onFinishLaunchNotifActivity(fakeEntry) - verify(onEndLifetimeExtensionCallback, never()) - .onEndLifetimeExtension(lifetimeExtender, fakeEntry) - } -} - -@CoordinatorScope -@Component(modules = [ActivityLaunchAnimCoordinatorModule::class]) -interface TestActivityStarterCoordinatorComponent { - val coordinator: ActivityLaunchAnimCoordinator - - @Component.Factory - interface Factory { - fun create( - @BindsInstance activityLaunchEvents: NotifActivityLaunchEvents - ): TestActivityStarterCoordinatorComponent - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 1f9af81d6508..f5a0e2d48859 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -346,7 +346,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { .build(); row.performDismiss(false); verify(mNotificationTestHelper.mOnUserInteractionCallback) - .onDismiss(any(), anyInt(), any()); + .registerFutureDismissal(any(), anyInt()); } @Test @@ -358,6 +358,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { .build(); row.performDismiss(false); verify(mNotificationTestHelper.mOnUserInteractionCallback, never()) - .onDismiss(any(), anyInt(), any()); + .registerFutureDismissal(any(), anyInt()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index d5ed37a570d2..7a8b329bec64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -23,8 +23,11 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.app.ActivityManager; @@ -121,6 +124,7 @@ public class NotificationTestHelper { private StatusBarStateController mStatusBarStateController; private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; public final OnUserInteractionCallback mOnUserInteractionCallback; + public final Runnable mFutureDismissalRunnable; public NotificationTestHelper( Context context, @@ -182,6 +186,9 @@ public class NotificationTestHelper { mBindPipelineEntryListener = collectionListenerCaptor.getValue(); mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class); mOnUserInteractionCallback = mock(OnUserInteractionCallback.class); + mFutureDismissalRunnable = mock(Runnable.class); + when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt())) + .thenReturn(mFutureDismissalRunnable); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index fa867e2796f7..ecea14c6a522 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -85,7 +85,6 @@ import com.android.systemui.wmshell.BubblesManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; @@ -93,6 +92,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; import java.util.ArrayList; +import java.util.List; import java.util.Optional; @SmallTest @@ -141,12 +141,13 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private OnUserInteractionCallback mOnUserInteractionCallback; @Mock + private Runnable mFutureDismissalRunnable; + @Mock private StatusBarNotificationActivityStarter mNotificationActivityStarter; @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; @Mock private InteractionJankMonitor mJankMonitor; - private StatusBarNotificationActivityStarter.LaunchEventsEmitter mLaunchEventsEmitter; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private NotificationTestHelper mNotificationTestHelper; private ExpandableNotificationRow mNotificationRow; @@ -187,8 +188,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false); - when(mOnUserInteractionCallback.getGroupSummaryToDismiss(mNotificationRow.getEntry())) - .thenReturn(null); + when(mOnUserInteractionCallback.registerFutureDismissal(eq(mNotificationRow.getEntry()), + anyInt())).thenReturn(mFutureDismissalRunnable); when(mVisibilityProvider.obtain(anyString(), anyBoolean())) .thenAnswer(invocation -> NotificationVisibility.obtain( invocation.getArgument(0), 0, 1, false)); @@ -203,7 +204,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { NotificationListContainer.class), headsUpManager, mJankMonitor); - mLaunchEventsEmitter = new StatusBarNotificationActivityStarter.LaunchEventsEmitter(); mNotificationActivityStarter = new StatusBarNotificationActivityStarter( getContext(), @@ -239,8 +239,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(NotificationPresenter.class), mock(NotificationPanelViewController.class), mActivityLaunchAnimator, - notificationAnimationProvider, - mLaunchEventsEmitter + notificationAnimationProvider ); // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg @@ -264,16 +263,23 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Test public void testOnNotificationClicked_keyGuardShowing() throws PendingIntent.CanceledException, RemoteException { + // To get the order right, collect posted runnables and run them later + List<Runnable> runnables = new ArrayList<>(); + doAnswer(answerVoid(r -> runnables.add((Runnable) r))) + .when(mHandler).post(any(Runnable.class)); // Given - StatusBarNotification sbn = mNotificationRow.getEntry().getSbn(); - sbn.getNotification().contentIntent = mContentIntent; - sbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL; + NotificationEntry entry = mNotificationRow.getEntry(); + Notification notification = entry.getSbn().getNotification(); + notification.contentIntent = mContentIntent; + notification.flags |= Notification.FLAG_AUTO_CANCEL; when(mKeyguardStateController.isShowing()).thenReturn(true); when(mCentralSurfaces.isOccluded()).thenReturn(true); // When - mNotificationActivityStarter.onNotificationClicked(sbn, mNotificationRow); + mNotificationActivityStarter.onNotificationClicked(entry, mNotificationRow); + // Run the collected runnables in fifo order, the way post() really does. + while (!runnables.isEmpty()) runnables.remove(0).run(); // Then verify(mShadeController, atLeastOnce()).collapsePanel(); @@ -283,24 +289,27 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { verify(mAssistManager).hideAssist(); - InOrder orderVerifier = Mockito.inOrder(mClickNotifier, mOnUserInteractionCallback); - orderVerifier.verify(mClickNotifier).onNotificationClick( - eq(sbn.getKey()), any(NotificationVisibility.class)); + InOrder orderVerifier = Mockito.inOrder(mClickNotifier, mOnUserInteractionCallback, + mFutureDismissalRunnable); // Notification calls dismiss callback to remove notification due to FLAG_AUTO_CANCEL - orderVerifier.verify(mOnUserInteractionCallback).onDismiss(mNotificationRow.getEntry(), - REASON_CLICK, null); + orderVerifier.verify(mOnUserInteractionCallback) + .registerFutureDismissal(eq(entry), eq(REASON_CLICK)); + orderVerifier.verify(mClickNotifier).onNotificationClick( + eq(entry.getKey()), any(NotificationVisibility.class)); + orderVerifier.verify(mFutureDismissalRunnable).run(); } @Test public void testOnNotificationClicked_bubble_noContentIntent_noKeyGuard() throws RemoteException { - StatusBarNotification sbn = mBubbleNotificationRow.getEntry().getSbn(); + NotificationEntry entry = mBubbleNotificationRow.getEntry(); + StatusBarNotification sbn = entry.getSbn(); // Given sbn.getNotification().contentIntent = null; // When - mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow); + mNotificationActivityStarter.onNotificationClicked(entry, mBubbleNotificationRow); // Then verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry())); @@ -311,20 +320,22 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { verify(mAssistManager).hideAssist(); verify(mClickNotifier).onNotificationClick( - eq(sbn.getKey()), any(NotificationVisibility.class)); + eq(entry.getKey()), any(NotificationVisibility.class)); // The content intent should NOT be sent on click. verifyZeroInteractions(mContentIntent); // Notification should not be cancelled. - verify(mOnUserInteractionCallback, never()).onDismiss(eq(mNotificationRow.getEntry()), - anyInt(), eq(null)); + verify(mOnUserInteractionCallback, never()) + .registerFutureDismissal(eq(mNotificationRow.getEntry()), anyInt()); + verify(mFutureDismissalRunnable, never()).run(); } @Test public void testOnNotificationClicked_bubble_noContentIntent_keyGuardShowing() throws RemoteException { - StatusBarNotification sbn = mBubbleNotificationRow.getEntry().getSbn(); + NotificationEntry entry = mBubbleNotificationRow.getEntry(); + StatusBarNotification sbn = entry.getSbn(); // Given sbn.getNotification().contentIntent = null; @@ -332,7 +343,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { when(mCentralSurfaces.isOccluded()).thenReturn(true); // When - mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow); + mNotificationActivityStarter.onNotificationClicked(entry, mBubbleNotificationRow); // Then verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry())); @@ -342,7 +353,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { verify(mAssistManager).hideAssist(); verify(mClickNotifier).onNotificationClick( - eq(sbn.getKey()), any(NotificationVisibility.class)); + eq(entry.getKey()), any(NotificationVisibility.class)); // The content intent should NOT be sent on click. verifyZeroInteractions(mContentIntent); @@ -354,7 +365,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Test public void testOnNotificationClicked_bubble_withContentIntent_keyGuardShowing() throws RemoteException { - StatusBarNotification sbn = mBubbleNotificationRow.getEntry().getSbn(); + NotificationEntry entry = mBubbleNotificationRow.getEntry(); + StatusBarNotification sbn = entry.getSbn(); // Given sbn.getNotification().contentIntent = mContentIntent; @@ -362,7 +374,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { when(mCentralSurfaces.isOccluded()).thenReturn(true); // When - mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow); + mNotificationActivityStarter.onNotificationClicked(entry, mBubbleNotificationRow); // Then verify(mBubblesManager).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry()); @@ -372,7 +384,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { verify(mAssistManager).hideAssist(); verify(mClickNotifier).onNotificationClick( - eq(sbn.getKey()), any(NotificationVisibility.class)); + eq(entry.getKey()), any(NotificationVisibility.class)); // The content intent should NOT be sent on click. verify(mContentIntent).getIntent(); @@ -405,57 +417,4 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // THEN display should try wake up for the full screen intent verify(mCentralSurfaces).wakeUpForFullScreenIntent(); } - - @Test - public void testNotifActivityStarterEventSourceStartEvent_onNotificationClicked() { - NotifActivityLaunchEvents.Listener listener = - mock(NotifActivityLaunchEvents.Listener.class); - mLaunchEventsEmitter.registerListener(listener); - mNotificationActivityStarter - .onNotificationClicked(mNotificationRow.getEntry().getSbn(), mNotificationRow); - verify(listener).onStartLaunchNotifActivity(mNotificationRow.getEntry()); - } - - @Test - public void testNotifActivityStarterEventSourceFinishEvent_dismissKeyguardCancelled() { - NotifActivityLaunchEvents.Listener listener = - mock(NotifActivityLaunchEvents.Listener.class); - mLaunchEventsEmitter.registerListener(listener); - // set up dismissKeyguardThenExecute to synchronously invoke the cancel runnable arg - doAnswer(answerVoid( - (OnDismissAction dismissAction, Runnable cancel, Boolean afterKeyguardGone) -> - cancel.run())) - .when(mActivityStarter) - .dismissKeyguardThenExecute(any(OnDismissAction.class), any(), anyBoolean()); - mNotificationActivityStarter - .onNotificationClicked(mNotificationRow.getEntry().getSbn(), mNotificationRow); - verify(listener).onFinishLaunchNotifActivity(mNotificationRow.getEntry()); - } - - @Test - public void testNotifActivityStarterEventSourceFinishEvent_postPanelCollapse() - throws Exception { - NotifActivityLaunchEvents.Listener listener = - mock(NotifActivityLaunchEvents.Listener.class); - mLaunchEventsEmitter.registerListener(listener); - mNotificationActivityStarter - .onNotificationClicked(mNotificationRow.getEntry().getSbn(), mNotificationRow); - ArgumentCaptor<ActivityLaunchAnimator.Controller> controllerCaptor = - ArgumentCaptor.forClass(ActivityLaunchAnimator.Controller.class); - verify(mActivityLaunchAnimator).startPendingIntentWithAnimation( - controllerCaptor.capture(), anyBoolean(), any(), any()); - controllerCaptor.getValue().onIntentStarted(false); - verify(listener).onFinishLaunchNotifActivity(mNotificationRow.getEntry()); - } - - @Test - public void testNotifActivityStarterEventSourceFinishEvent_postPanelCollapse_noAnimate() { - NotifActivityLaunchEvents.Listener listener = - mock(NotifActivityLaunchEvents.Listener.class); - mLaunchEventsEmitter.registerListener(listener); - when(mCentralSurfaces.shouldAnimateLaunch(anyBoolean())).thenReturn(false); - mNotificationActivityStarter - .onNotificationClicked(mNotificationRow.getEntry().getSbn(), mNotificationRow); - verify(listener).onFinishLaunchNotifActivity(mNotificationRow.getEntry()); - } } |