Merge "Ensure the notification is removed when bubble is removed & fix cancel all" into qt-dev
This commit is contained in:
@@ -16,6 +16,10 @@
|
||||
|
||||
package com.android.systemui.bubbles;
|
||||
|
||||
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_CANCEL;
|
||||
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.Display.INVALID_DISPLAY;
|
||||
import static android.view.View.INVISIBLE;
|
||||
@@ -53,13 +57,13 @@ import androidx.annotation.MainThread;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.internal.statusbar.NotificationVisibility;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.shared.system.WindowManagerWrapper;
|
||||
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
@@ -210,6 +214,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
|
||||
mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
|
||||
mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
|
||||
mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
|
||||
|
||||
mStatusBarWindowController = statusBarWindowController;
|
||||
mStatusBarStateListener = new StatusBarStateListener();
|
||||
@@ -388,6 +393,46 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final NotificationRemoveInterceptor mRemoveInterceptor =
|
||||
new NotificationRemoveInterceptor() {
|
||||
@Override
|
||||
public boolean onNotificationRemoveRequested(String key, int reason) {
|
||||
if (!mBubbleData.hasBubbleWithKey(key)) {
|
||||
return false;
|
||||
}
|
||||
NotificationEntry entry = mBubbleData.getBubbleWithKey(key).entry;
|
||||
|
||||
final boolean isClearAll = reason == REASON_CANCEL_ALL;
|
||||
final boolean isUserDimiss = reason == REASON_CANCEL;
|
||||
final boolean isAppCancel = reason == REASON_APP_CANCEL
|
||||
|| reason == REASON_APP_CANCEL_ALL;
|
||||
|
||||
// Need to check for !appCancel here because the notification may have
|
||||
// previously been dismissed & entry.isRowDismissed would still be true
|
||||
boolean userRemovedNotif = (entry.isRowDismissed() && !isAppCancel)
|
||||
|| isClearAll || isUserDimiss;
|
||||
|
||||
// The bubble notification sticks around in the data as long as the bubble is
|
||||
// not dismissed and the app hasn't cancelled the notification.
|
||||
boolean bubbleExtended = entry.isBubble() && !entry.isBubbleDismissed()
|
||||
&& userRemovedNotif;
|
||||
if (bubbleExtended) {
|
||||
entry.setShowInShadeWhenBubble(false);
|
||||
if (mStackView != null) {
|
||||
mStackView.updateDotVisibility(entry.key);
|
||||
}
|
||||
mNotificationEntryManager.updateNotifications();
|
||||
return true;
|
||||
} else if (!userRemovedNotif && !entry.isBubbleDismissed()) {
|
||||
// This wasn't a user removal so we should remove the bubble as well
|
||||
mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
|
||||
@Override
|
||||
@@ -396,7 +441,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
return;
|
||||
}
|
||||
if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) {
|
||||
// TODO: handle group summaries?
|
||||
updateShowInShadeForSuppressNotification(entry);
|
||||
}
|
||||
}
|
||||
@@ -426,23 +470,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
updateBubble(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryRemoved(NotificationEntry entry,
|
||||
@Nullable NotificationVisibility visibility,
|
||||
boolean removedByUser) {
|
||||
if (!areBubblesEnabled(mContext)) {
|
||||
return;
|
||||
}
|
||||
entry.setShowInShadeWhenBubble(false);
|
||||
if (mStackView != null) {
|
||||
mStackView.updateDotVisibility(entry.key);
|
||||
}
|
||||
if (!removedByUser) {
|
||||
// This was a cancel so we should remove the bubble
|
||||
removeBubble(entry.key, DISMISS_NOTIF_CANCEL);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
@@ -455,13 +482,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBubbleRemoved(Bubble bubble, int reason) {
|
||||
public void onBubbleRemoved(Bubble bubble, @DismissReason int reason) {
|
||||
if (mStackView != null) {
|
||||
mStackView.removeBubble(bubble);
|
||||
}
|
||||
if (!bubble.entry.showInShadeWhenBubble()) {
|
||||
// The notification is gone & bubble is gone, time to actually remove it
|
||||
mNotificationEntryManager.performRemoveNotification(bubble.entry.notification);
|
||||
if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
|
||||
&& !bubble.entry.showInShadeWhenBubble()) {
|
||||
// The bubble is gone & the notification is gone, time to actually remove it
|
||||
mNotificationEntryManager.performRemoveNotification(bubble.entry.notification,
|
||||
0 /* reason */);
|
||||
} else {
|
||||
// The notification is still in the shade but we've removed the bubble so
|
||||
// lets make sure NoMan knows it's not a bubble anymore
|
||||
|
||||
@@ -104,7 +104,7 @@ public class NotificationListener extends NotificationListenerWithPlugins {
|
||||
|
||||
// Remove existing notification to avoid stale data.
|
||||
if (isUpdate) {
|
||||
mEntryManager.removeNotification(key, rankingMap);
|
||||
mEntryManager.removeNotification(key, rankingMap, 0 /* reason */);
|
||||
} else {
|
||||
mEntryManager.getNotificationData()
|
||||
.updateRanking(rankingMap);
|
||||
@@ -121,17 +121,22 @@ public class NotificationListener extends NotificationListenerWithPlugins {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification sbn,
|
||||
final RankingMap rankingMap) {
|
||||
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
|
||||
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
|
||||
int reason) {
|
||||
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn + " reason: " + reason);
|
||||
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
|
||||
final String key = sbn.getKey();
|
||||
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
|
||||
mEntryManager.removeNotification(key, rankingMap);
|
||||
mEntryManager.removeNotification(key, rankingMap, reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
|
||||
onNotificationRemoved(sbn, rankingMap, 0 /* reason */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
|
||||
if (DEBUG) Log.d(TAG, "onRankingUpdate");
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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;
|
||||
|
||||
import android.service.notification.NotificationListenerService;
|
||||
|
||||
/**
|
||||
* Interface for anything that may need to prevent notifications from being removed. This is
|
||||
* similar to a {@link NotificationLifetimeExtender} in the sense that it extends the life of
|
||||
* a notification by preventing the removal, however, unlike the extender, the remove interceptor
|
||||
* gets first pick at intercepting any type of removal -- the life time extender is unable to
|
||||
* extend the life of a user dismissed or force removed notification.
|
||||
*/
|
||||
public interface NotificationRemoveInterceptor {
|
||||
|
||||
/**
|
||||
* Called when a notification has been removed.
|
||||
*
|
||||
* @param key the entry key of the notification being removed.
|
||||
* @param removeReason why the notification is being removed, e.g.
|
||||
* {@link NotificationListenerService#REASON_CANCEL} or 0 if unknown.
|
||||
*
|
||||
* @return true if the removal should be ignored, false otherwise.
|
||||
*/
|
||||
boolean onNotificationRemoveRequested(String key, int removeReason);
|
||||
}
|
||||
@@ -37,8 +37,10 @@ public interface NotificationUpdateHandler {
|
||||
*
|
||||
* @param key Key identifying the notification to remove
|
||||
* @param ranking RankingMap to update with
|
||||
* @param reason why the notification is being removed, e.g.
|
||||
* {@link NotificationListenerService#REASON_CANCEL}.
|
||||
*/
|
||||
void removeNotification(String key, NotificationListenerService.RankingMap ranking);
|
||||
void removeNotification(String key, NotificationListenerService.RankingMap ranking, int reason);
|
||||
|
||||
/**
|
||||
* Update a given notification and the current notification ranking map.
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package com.android.systemui.statusbar.notification;
|
||||
|
||||
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
|
||||
import static android.service.notification.NotificationListenerService.REASON_ERROR;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
@@ -30,6 +33,7 @@ import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.statusbar.NotificationLifetimeExtender;
|
||||
import com.android.systemui.statusbar.NotificationPresenter;
|
||||
import com.android.systemui.statusbar.NotificationRemoteInputManager;
|
||||
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
|
||||
import com.android.systemui.statusbar.NotificationUiAdjustment;
|
||||
import com.android.systemui.statusbar.NotificationUpdateHandler;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationData;
|
||||
@@ -82,6 +86,7 @@ public class NotificationEntryManager implements
|
||||
final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
|
||||
= new ArrayList<>();
|
||||
private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>();
|
||||
private NotificationRemoveInterceptor mRemoveInterceptor;
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
@@ -115,6 +120,11 @@ public class NotificationEntryManager implements
|
||||
mNotificationEntryListeners.add(listener);
|
||||
}
|
||||
|
||||
/** Sets the {@link NotificationRemoveInterceptor}. */
|
||||
public void setNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
|
||||
mRemoveInterceptor = interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our dependencies can have cyclic references, so some need to be lazy
|
||||
*/
|
||||
@@ -146,7 +156,7 @@ public class NotificationEntryManager implements
|
||||
/** Adds a {@link NotificationLifetimeExtender}. */
|
||||
public void addNotificationLifetimeExtender(NotificationLifetimeExtender extender) {
|
||||
mNotificationLifetimeExtenders.add(extender);
|
||||
extender.setCallback(key -> removeNotification(key, mLatestRankingMap));
|
||||
extender.setCallback(key -> removeNotification(key, mLatestRankingMap, 0));
|
||||
}
|
||||
|
||||
public NotificationData getNotificationData() {
|
||||
@@ -158,10 +168,18 @@ public class NotificationEntryManager implements
|
||||
updateNotifications();
|
||||
}
|
||||
|
||||
public void performRemoveNotification(StatusBarNotification n) {
|
||||
/**
|
||||
* Requests a notification to be removed.
|
||||
*
|
||||
* @param n the notification to remove.
|
||||
* @param reason why it is being removed e.g. {@link NotificationListenerService#REASON_CANCEL},
|
||||
* or 0 if unknown.
|
||||
*/
|
||||
public void performRemoveNotification(StatusBarNotification n, int reason) {
|
||||
final NotificationVisibility nv = obtainVisibility(n.getKey());
|
||||
removeNotificationInternal(
|
||||
n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */);
|
||||
n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */,
|
||||
reason);
|
||||
}
|
||||
|
||||
private NotificationVisibility obtainVisibility(String key) {
|
||||
@@ -193,7 +211,8 @@ public class NotificationEntryManager implements
|
||||
@Override
|
||||
public void handleInflationException(StatusBarNotification n, Exception e) {
|
||||
removeNotificationInternal(
|
||||
n.getKey(), null, null, true /* forceRemove */, false /* removedByUser */);
|
||||
n.getKey(), null, null, true /* forceRemove */, false /* removedByUser */,
|
||||
REASON_ERROR);
|
||||
for (NotificationEntryListener listener : mNotificationEntryListeners) {
|
||||
listener.onInflationError(n, e);
|
||||
}
|
||||
@@ -228,9 +247,10 @@ public class NotificationEntryManager implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotification(String key, NotificationListenerService.RankingMap ranking) {
|
||||
public void removeNotification(String key, NotificationListenerService.RankingMap ranking,
|
||||
int reason) {
|
||||
removeNotificationInternal(key, ranking, obtainVisibility(key), false /* forceRemove */,
|
||||
false /* removedByUser */);
|
||||
false /* removedByUser */, reason);
|
||||
}
|
||||
|
||||
private void removeNotificationInternal(
|
||||
@@ -238,7 +258,15 @@ public class NotificationEntryManager implements
|
||||
@Nullable NotificationListenerService.RankingMap ranking,
|
||||
@Nullable NotificationVisibility visibility,
|
||||
boolean forceRemove,
|
||||
boolean removedByUser) {
|
||||
boolean removedByUser,
|
||||
int reason) {
|
||||
|
||||
if (mRemoveInterceptor != null
|
||||
&& mRemoveInterceptor.onNotificationRemoveRequested(key, reason)) {
|
||||
// Remove intercepted; skip
|
||||
return;
|
||||
}
|
||||
|
||||
final NotificationEntry entry = mNotificationData.get(key);
|
||||
|
||||
abortExistingInflation(key);
|
||||
@@ -342,7 +370,8 @@ public class NotificationEntryManager implements
|
||||
|
||||
Dependency.get(LeakDetector.class).trackInstance(entry);
|
||||
// Construct the expanded view.
|
||||
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification));
|
||||
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
|
||||
REASON_CANCEL));
|
||||
|
||||
abortExistingInflation(key);
|
||||
|
||||
@@ -383,7 +412,8 @@ public class NotificationEntryManager implements
|
||||
listener.onPreEntryUpdated(entry);
|
||||
}
|
||||
|
||||
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification));
|
||||
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
|
||||
REASON_CANCEL));
|
||||
updateNotifications();
|
||||
|
||||
if (DEBUG) {
|
||||
|
||||
@@ -247,7 +247,7 @@ public final class NotificationEntry {
|
||||
*/
|
||||
public boolean showInShadeWhenBubble() {
|
||||
// We always show it in the shade if non-clearable
|
||||
return !isClearable() || mShowInShadeWhenBubble;
|
||||
return !isRowDismissed() && (!isClearable() || mShowInShadeWhenBubble);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -270,6 +270,8 @@ public class NotificationLogger implements StateListener {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This method has side effects, it is NOT just logging that a notification
|
||||
// was cleared, it also actually removes the notification
|
||||
private void logNotificationClear(String key, StatusBarNotification notification,
|
||||
NotificationVisibility nv) {
|
||||
final String pkg = notification.getPackageName();
|
||||
|
||||
@@ -46,6 +46,7 @@ import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.ServiceManager;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
@@ -5586,7 +5587,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
|
||||
setDismissAllInProgress(false);
|
||||
for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
|
||||
if (canChildBeDismissed(rowToRemove)) {
|
||||
mEntryManager.removeNotification(rowToRemove.getEntry().key, null);
|
||||
mEntryManager.removeNotification(rowToRemove.getEntry().key, null /* ranking */,
|
||||
NotificationListenerService.REASON_CANCEL_ALL);
|
||||
} else {
|
||||
rowToRemove.resetTranslation();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.systemui.statusbar.phone;
|
||||
|
||||
import static android.service.notification.NotificationListenerService.REASON_CLICK;
|
||||
|
||||
import static com.android.systemui.statusbar.phone.StatusBar.getActivityOptions;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
@@ -483,7 +485,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
|
||||
// We have to post it to the UI thread for synchronization
|
||||
mMainThreadHandler.post(() -> {
|
||||
Runnable removeRunnable =
|
||||
() -> mEntryManager.performRemoveNotification(notification);
|
||||
() -> mEntryManager.performRemoveNotification(notification, REASON_CLICK);
|
||||
if (mPresenter.isCollapsing()) {
|
||||
// To avoid lags we're only performing the remove
|
||||
// after the shade was collapsed
|
||||
|
||||
@@ -18,13 +18,18 @@ package com.android.systemui.bubbles;
|
||||
|
||||
import static android.app.Notification.FLAG_BUBBLE;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
|
||||
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
|
||||
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
|
||||
|
||||
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.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -50,6 +55,7 @@ import androidx.test.filters.SmallTest;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.statusbar.NotificationPresenter;
|
||||
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
|
||||
import com.android.systemui.statusbar.NotificationTestHelper;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
@@ -95,10 +101,13 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
private FrameLayout mStatusBarView;
|
||||
@Captor
|
||||
private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
|
||||
@Captor
|
||||
private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
|
||||
|
||||
private TestableBubbleController mBubbleController;
|
||||
private StatusBarWindowController mStatusBarWindowController;
|
||||
private NotificationEntryListener mEntryListener;
|
||||
private NotificationRemoveInterceptor mRemoveInterceptor;
|
||||
|
||||
private NotificationTestHelper mNotificationTestHelper;
|
||||
private ExpandableNotificationRow mRow;
|
||||
@@ -167,6 +176,10 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
verify(mNotificationEntryManager, atLeastOnce())
|
||||
.addNotificationEntryListener(mEntryListenerCaptor.capture());
|
||||
mEntryListener = mEntryListenerCaptor.getValue();
|
||||
// And the remove interceptor
|
||||
verify(mNotificationEntryManager, atLeastOnce())
|
||||
.setNotificationRemoveInterceptor(mRemoveInterceptorCaptor.capture());
|
||||
mRemoveInterceptor = mRemoveInterceptorCaptor.getValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -198,6 +211,27 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveBubble_withDismissedNotif() {
|
||||
mEntryListener.onPendingEntryAdded(mRow.getEntry());
|
||||
mBubbleController.updateBubble(mRow.getEntry());
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertTrue(mRow.getEntry().showInShadeWhenBubble());
|
||||
|
||||
// Make it look like dismissed notif
|
||||
mRow.getEntry().setShowInShadeWhenBubble(false);
|
||||
|
||||
// Now remove the bubble
|
||||
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
|
||||
|
||||
// Since the notif is dismissed, once the bubble is removed, performRemoveNotification gets
|
||||
// called to really remove the notif
|
||||
verify(mNotificationEntryManager, times(1)).performRemoveNotification(
|
||||
mRow.getEntry().notification, 0);
|
||||
assertFalse(mBubbleController.hasBubbles());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDismissStack() {
|
||||
mBubbleController.updateBubble(mRow.getEntry());
|
||||
@@ -455,8 +489,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
mBubbleController.updateBubble(mRow.getEntry());
|
||||
|
||||
// Simulate notification cancellation.
|
||||
mEntryListener.onEntryRemoved(mRow.getEntry(), null /* notificationVisibility (unused) */,
|
||||
false /* removedbyUser */);
|
||||
mRemoveInterceptor.onNotificationRemoveRequested(mRow.getEntry().key, REASON_APP_CANCEL);
|
||||
|
||||
mBubbleController.expandStackAndSelectBubble(key);
|
||||
}
|
||||
@@ -511,6 +544,83 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
verify(mDeleteIntent, never()).send();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveBubble_succeeds_appCancel() {
|
||||
mEntryListener.onPendingEntryAdded(mRow.getEntry());
|
||||
mBubbleController.updateBubble(mRow.getEntry());
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
|
||||
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
|
||||
mRow.getEntry().key, REASON_APP_CANCEL);
|
||||
|
||||
// Cancels always remove so no need to intercept
|
||||
assertFalse(intercepted);
|
||||
assertFalse(mBubbleController.hasBubbles());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeBubble_fails_clearAll() {
|
||||
mEntryListener.onPendingEntryAdded(mRow.getEntry());
|
||||
mBubbleController.updateBubble(mRow.getEntry());
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertTrue(mRow.getEntry().showInShadeWhenBubble());
|
||||
|
||||
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
|
||||
mRow.getEntry().key, REASON_CANCEL_ALL);
|
||||
|
||||
// Intercept!
|
||||
assertTrue(intercepted);
|
||||
// Should update show in shade state
|
||||
assertFalse(mRow.getEntry().showInShadeWhenBubble());
|
||||
|
||||
verify(mNotificationEntryManager, never()).performRemoveNotification(
|
||||
any(), anyInt());
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeBubble_fails_userDismissNotif() {
|
||||
mEntryListener.onPendingEntryAdded(mRow.getEntry());
|
||||
mBubbleController.updateBubble(mRow.getEntry());
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertTrue(mRow.getEntry().showInShadeWhenBubble());
|
||||
|
||||
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
|
||||
mRow.getEntry().key, REASON_CANCEL);
|
||||
|
||||
// Intercept!
|
||||
assertTrue(intercepted);
|
||||
// Should update show in shade state
|
||||
assertFalse(mRow.getEntry().showInShadeWhenBubble());
|
||||
|
||||
verify(mNotificationEntryManager, never()).performRemoveNotification(
|
||||
any(), anyInt());
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeBubble_succeeds_userDismissBubble_userDimissNotif() {
|
||||
mEntryListener.onPendingEntryAdded(mRow.getEntry());
|
||||
mBubbleController.updateBubble(mRow.getEntry());
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertTrue(mRow.getEntry().showInShadeWhenBubble());
|
||||
|
||||
// Dismiss the bubble
|
||||
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
|
||||
assertFalse(mBubbleController.hasBubbles());
|
||||
|
||||
// Dismiss the notification
|
||||
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
|
||||
mRow.getEntry().key, REASON_CANCEL);
|
||||
|
||||
// It's no longer a bubble so we shouldn't intercept
|
||||
assertFalse(intercepted);
|
||||
}
|
||||
|
||||
static class TestableBubbleController extends BubbleController {
|
||||
// Let's assume surfaces can be synchronized immediately.
|
||||
TestableBubbleController(Context context,
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.systemui.statusbar;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -99,7 +101,7 @@ public class NotificationListenerTest extends SysuiTestCase {
|
||||
public void testNotificationRemovalCallsRemoveNotification() {
|
||||
mListener.onNotificationRemoved(mSbn, mRanking);
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
verify(mEntryManager).removeNotification(mSbn.getKey(), mRanking);
|
||||
verify(mEntryManager).removeNotification(eq(mSbn.getKey()), eq(mRanking), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,13 +16,18 @@
|
||||
|
||||
package com.android.systemui.statusbar.notification;
|
||||
|
||||
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
|
||||
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -51,6 +56,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.statusbar.NotificationVisibility;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.ForegroundServiceController;
|
||||
import com.android.systemui.InitController;
|
||||
@@ -61,6 +67,7 @@ import com.android.systemui.statusbar.NotificationListener;
|
||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
|
||||
import com.android.systemui.statusbar.NotificationPresenter;
|
||||
import com.android.systemui.statusbar.NotificationRemoteInputManager;
|
||||
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
|
||||
import com.android.systemui.statusbar.RemoteInputController;
|
||||
import com.android.systemui.statusbar.SmartReplyController;
|
||||
import com.android.systemui.statusbar.StatusBarIconView;
|
||||
@@ -105,6 +112,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
@Mock private ExpandableNotificationRow mRow;
|
||||
@Mock private NotificationListContainer mListContainer;
|
||||
@Mock private NotificationEntryListener mEntryListener;
|
||||
@Mock private NotificationRemoveInterceptor mRemoveInterceptor;
|
||||
@Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
|
||||
@Mock private HeadsUpManager mHeadsUpManager;
|
||||
@Mock private NotificationListenerService.RankingMap mRankingMap;
|
||||
@@ -234,6 +242,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
Dependency.get(InitController.class).executePostInitTasks();
|
||||
mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
|
||||
mEntryManager.addNotificationEntryListener(mEntryListener);
|
||||
mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
|
||||
|
||||
NotificationRowBinderImpl notificationRowBinder =
|
||||
new NotificationRowBinderImpl(mContext, true /* allowLongPress */);
|
||||
@@ -341,7 +350,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
mEntry.setRow(mRow);
|
||||
mEntryManager.getNotificationData().add(mEntry);
|
||||
|
||||
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap);
|
||||
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, 0 /* reason */);
|
||||
|
||||
verify(mEntryListener, never()).onInflationError(any(), any());
|
||||
|
||||
@@ -357,7 +366,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() {
|
||||
com.android.systemui.util.Assert.isNotMainThread();
|
||||
|
||||
mEntryManager.removeNotification("not_a_real_key", mRankingMap);
|
||||
mEntryManager.removeNotification("not_a_real_key", mRankingMap, 0 /* reason */);
|
||||
|
||||
verify(mEntryListener, never()).onEntryRemoved(
|
||||
eq(mEntry), any(), eq(false) /* removedByUser */);
|
||||
@@ -370,7 +379,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
mEntryManager.setRowBinder(mMockedRowBinder);
|
||||
|
||||
mEntryManager.addNotification(mSbn, mRankingMap);
|
||||
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap);
|
||||
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, 0 /* reason */);
|
||||
|
||||
verify(mEntryListener, never()).onEntryRemoved(
|
||||
eq(mEntry), any(), eq(false /* removedByUser */));
|
||||
@@ -449,7 +458,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
mEntryManager.addNotificationLifetimeExtender(extender);
|
||||
|
||||
// WHEN the notification is removed
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap);
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap, 0 /* reason */);
|
||||
|
||||
// THEN the extender is asked to manage the lifetime
|
||||
verify(extender).setShouldManageLifetime(mEntry, true);
|
||||
@@ -465,7 +474,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
mEntryManager.getNotificationData().add(mEntry);
|
||||
final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender();
|
||||
mEntryManager.addNotificationLifetimeExtender(extender);
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap);
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap, 0 /* reason */);
|
||||
assertTrue(extender.isManaging(mEntry.key));
|
||||
|
||||
// WHEN the extender finishes its extension
|
||||
@@ -485,7 +494,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
|
||||
when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
|
||||
mEntryManager.addNotificationLifetimeExtender(extender);
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap);
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap, 0 /* reason */);
|
||||
|
||||
// WHEN the notification is updated
|
||||
mEntryManager.updateNotification(mEntry.notification, mRankingMap);
|
||||
@@ -510,13 +519,13 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
mEntryManager.addNotificationLifetimeExtender(extender2);
|
||||
|
||||
// GIVEN a notification was lifetime-extended and extender2 is managing it
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap);
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap, 0 /* reason */);
|
||||
verify(extender1, never()).setShouldManageLifetime(mEntry, true);
|
||||
verify(extender2).setShouldManageLifetime(mEntry, true);
|
||||
|
||||
// WHEN the extender1 changes its mind and wants to extend the lifetime of the notif
|
||||
when(extender1.shouldExtendLifetime(mEntry)).thenReturn(true);
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap);
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap, 0 /* reason */);
|
||||
|
||||
// THEN extender2 stops managing the notif and extender1 starts managing it
|
||||
verify(extender1).setShouldManageLifetime(mEntry, true);
|
||||
@@ -530,7 +539,45 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
@Test
|
||||
public void testPerformRemoveNotification_removedEntry() {
|
||||
mEntryManager.getNotificationData().remove(mSbn.getKey(), null /* ranking */);
|
||||
mEntryManager.performRemoveNotification(mSbn);
|
||||
mEntryManager.performRemoveNotification(mSbn, REASON_CANCEL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveInterceptor_interceptsDontGetRemoved() {
|
||||
// GIVEN an entry manager with a notification
|
||||
mEntryManager.setRowBinder(mMockedRowBinder);
|
||||
mEntryManager.getNotificationData().add(mEntry);
|
||||
|
||||
// GIVEN interceptor that intercepts that entry
|
||||
when(mRemoveInterceptor.onNotificationRemoveRequested(eq(mEntry.key), anyInt()))
|
||||
.thenReturn(true);
|
||||
|
||||
// WHEN the notification is removed
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap, 0 /* reason */);
|
||||
|
||||
// THEN the interceptor intercepts & the entry is not removed & no listeners are called
|
||||
assertNotNull(mEntryManager.getNotificationData().get(mEntry.key));
|
||||
verify(mEntryListener, never()).onEntryRemoved(eq(mEntry),
|
||||
any(NotificationVisibility.class), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveInterceptor_notInterceptedGetsRemoved() {
|
||||
// GIVEN an entry manager with a notification
|
||||
mEntryManager.setRowBinder(mMockedRowBinder);
|
||||
mEntryManager.getNotificationData().add(mEntry);
|
||||
|
||||
// GIVEN interceptor that doesn't intercept
|
||||
when(mRemoveInterceptor.onNotificationRemoveRequested(eq(mEntry.key), anyInt()))
|
||||
.thenReturn(false);
|
||||
|
||||
// WHEN the notification is removed
|
||||
mEntryManager.removeNotification(mEntry.key, mRankingMap, 0 /* reason */);
|
||||
|
||||
// THEN the interceptor intercepts & the entry is not removed & no listeners are called
|
||||
assertNull(mEntryManager.getNotificationData().get(mEntry.key));
|
||||
verify(mEntryListener, atLeastOnce()).onEntryRemoved(eq(mEntry),
|
||||
any(NotificationVisibility.class), anyBoolean());
|
||||
}
|
||||
|
||||
private Notification.Action createAction() {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.systemui.statusbar.phone;
|
||||
|
||||
import static android.service.notification.NotificationListenerService.REASON_CLICK;
|
||||
|
||||
import static org.mockito.AdditionalAnswers.answerVoid;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
@@ -224,7 +226,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
|
||||
eq(sbn.getKey()), any(NotificationVisibility.class));
|
||||
|
||||
// Notification is removed due to FLAG_AUTO_CANCEL
|
||||
verify(mEntryManager).performRemoveNotification(eq(sbn));
|
||||
verify(mEntryManager).performRemoveNotification(eq(sbn), eq(REASON_CLICK));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -253,7 +255,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
|
||||
verifyZeroInteractions(mContentIntent);
|
||||
|
||||
// Notification should not be cancelled.
|
||||
verify(mEntryManager, never()).performRemoveNotification(eq(sbn));
|
||||
verify(mEntryManager, never()).performRemoveNotification(eq(sbn), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -283,7 +285,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
|
||||
verifyZeroInteractions(mContentIntent);
|
||||
|
||||
// Notification should not be cancelled.
|
||||
verify(mEntryManager, never()).performRemoveNotification(eq(sbn));
|
||||
verify(mEntryManager, never()).performRemoveNotification(eq(sbn), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -315,6 +317,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
|
||||
verifyNoMoreInteractions(mContentIntent);
|
||||
|
||||
// Notification should not be cancelled.
|
||||
verify(mEntryManager, never()).performRemoveNotification(eq(sbn));
|
||||
verify(mEntryManager, never()).performRemoveNotification(eq(sbn), anyInt());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user