Merge changes from topic "register_future_dismissal" into tm-dev
* changes: Remove the ActivityLaunchAnimCoordinator. Fix NotificationEntry mismatch crashes when tapping notification. Fix events so that VisualStabilityCoordinator blocks shade recalculation during activity launch animation Add logging required to diagnose the multi-entry issues
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
})
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -25,30 +26,24 @@ 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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -1487,6 +1491,55 @@ public class NotifCollectionTest extends SysuiTestCase {
|
||||
verify(mCollectionListener, never()).onEntryUpdated(any(), anyBoolean());
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user