Merge changes from topics "nem-combine-callbacks", "nem-fullscreen-intent", "nem-interruption-state"
* changes: Combines NotificationEntryManager listener interfaces (part 1). Moves fullscreen intent logic to NotificationActivityStarter. Extracts heads-up/pulsing logic from NotificationEntryManager.
This commit is contained in:
committed by
Android (Google) Code Review
commit
82da8288e3
@@ -21,9 +21,11 @@ import android.content.Context;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.keyguard.ViewMediatorCallback;
|
||||
import com.android.systemui.car.CarNotificationEntryManager;
|
||||
import com.android.systemui.car.CarNotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.car.CarFacetButtonController;
|
||||
import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
|
||||
import com.android.systemui.volume.CarVolumeDialogComponent;
|
||||
import com.android.systemui.volume.VolumeDialogComponent;
|
||||
@@ -67,6 +69,12 @@ public class CarSystemUIFactory extends SystemUIFactory {
|
||||
return new CarNotificationEntryManager(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotificationInterruptionStateProvider provideNotificationInterruptionStateProvider(
|
||||
Context context) {
|
||||
return new CarNotificationInterruptionStateProvider(context);
|
||||
}
|
||||
|
||||
@Module
|
||||
protected static class ContextHolder {
|
||||
private Context mContext;
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.android.systemui.car;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.systemui.statusbar.notification.NotificationData;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
|
||||
@@ -39,16 +38,4 @@ public class CarNotificationEntryManager extends NotificationEntryManager {
|
||||
// long click listener.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldHeadsUp(NotificationData.Entry entry) {
|
||||
// Because space is usually constrained in the auto use-case, there should not be a
|
||||
// pinned notification when the shade has been expanded. Ensure this by not pinning any
|
||||
// notification if the shade is already opened.
|
||||
if (!getPresenter().isPresenterFullyCollapsed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.shouldHeadsUp(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.car;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.systemui.statusbar.notification.NotificationData;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
|
||||
/** Auto-specific implementation of {@link NotificationInterruptionStateProvider}. */
|
||||
public class CarNotificationInterruptionStateProvider extends
|
||||
NotificationInterruptionStateProvider {
|
||||
public CarNotificationInterruptionStateProvider(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldHeadsUp(NotificationData.Entry entry) {
|
||||
// Because space is usually constrained in the auto use-case, there should not be a
|
||||
// pinned notification when the shade has been expanded. Ensure this by not pinning any
|
||||
// notification if the shade is already opened.
|
||||
if (!getPresenter().isPresenterFullyCollapsed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.shouldHeadsUp(entry);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,8 @@ import com.android.systemui.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.VibratorHelper;
|
||||
import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationFilter;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.notification.NotificationRowBinder;
|
||||
import com.android.systemui.statusbar.notification.VisualStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
@@ -256,6 +258,8 @@ public class Dependency extends SystemUI {
|
||||
@Inject Lazy<NotificationLogger> mNotificationLogger;
|
||||
@Inject Lazy<NotificationViewHierarchyManager> mNotificationViewHierarchyManager;
|
||||
@Inject Lazy<NotificationRowBinder> mNotificationRowBinder;
|
||||
@Inject Lazy<NotificationFilter> mNotificationFilter;
|
||||
@Inject Lazy<NotificationInterruptionStateProvider> mNotificationInterruptionStateProvider;
|
||||
@Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
|
||||
@Inject Lazy<SmartReplyController> mSmartReplyController;
|
||||
@Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
|
||||
@@ -425,6 +429,9 @@ public class Dependency extends SystemUI {
|
||||
mProviders.put(NotificationViewHierarchyManager.class,
|
||||
mNotificationViewHierarchyManager::get);
|
||||
mProviders.put(NotificationRowBinder.class, mNotificationRowBinder::get);
|
||||
mProviders.put(NotificationFilter.class, mNotificationFilter::get);
|
||||
mProviders.put(NotificationInterruptionStateProvider.class,
|
||||
mNotificationInterruptionStateProvider::get);
|
||||
mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
|
||||
mProviders.put(SmartReplyController.class, mSmartReplyController::get);
|
||||
mProviders.put(RemoteInputQuickSettingsDisabler.class,
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
|
||||
import com.android.systemui.statusbar.ScrimView;
|
||||
import com.android.systemui.statusbar.notification.NotificationData;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.phone.DozeParameters;
|
||||
import com.android.systemui.statusbar.phone.KeyguardBouncer;
|
||||
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
|
||||
@@ -198,6 +199,13 @@ public class SystemUIFactory {
|
||||
return new NotificationListener(context);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
public NotificationInterruptionStateProvider provideNotificationInterruptionStateProvider(
|
||||
Context context) {
|
||||
return new NotificationInterruptionStateProvider(context);
|
||||
}
|
||||
|
||||
@Module
|
||||
protected static class ContextHolder {
|
||||
private Context mContext;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.android.systemui.statusbar;
|
||||
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationRowBinder;
|
||||
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
@@ -29,7 +28,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
|
||||
*/
|
||||
public interface NotificationPresenter extends ExpandableNotificationRow.OnExpandClickListener,
|
||||
ActivatableNotificationView.OnActivatedListener,
|
||||
NotificationEntryManager.Callback,
|
||||
NotificationRowBinder.BindRowCallback {
|
||||
/**
|
||||
* Returns true if the presenter is not visible. For example, it may not be necessary to do
|
||||
|
||||
@@ -133,7 +133,6 @@ public class NotificationViewHierarchyManager {
|
||||
mAlwaysExpandNonGroupedNotification =
|
||||
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
|
||||
mStatusBarStateListener = new StatusBarStateListener(mBubbleController);
|
||||
mEntryManager.setStatusBarStateListener(mStatusBarStateListener);
|
||||
Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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;
|
||||
|
||||
/**
|
||||
* Listener interface for when NotificationEntryManager needs to tell
|
||||
* NotificationGroupAlertTransferHelper things. Will eventually grow to be a general-purpose
|
||||
* listening interface for the NotificationEntryManager.
|
||||
*/
|
||||
public interface AlertTransferListener {
|
||||
/**
|
||||
* Called when a new notification is posted. At this point, the notification is "pending": its
|
||||
* views haven't been inflated yet and most of the system pretends like it doesn't exist yet.
|
||||
*/
|
||||
void onPendingEntryAdded(NotificationData.Entry entry);
|
||||
|
||||
/**
|
||||
* Called when an existing notification's views are reinflated (usually due to an update being
|
||||
* posted to that notification).
|
||||
*/
|
||||
void onEntryReinflated(NotificationData.Entry entry);
|
||||
|
||||
/**
|
||||
* Called when a notification has been removed (either because the user swiped it away or
|
||||
* because the developer retracted it).
|
||||
*/
|
||||
void onEntryRemoved(NotificationData.Entry entry);
|
||||
}
|
||||
@@ -27,20 +27,15 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT
|
||||
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
|
||||
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Person;
|
||||
import android.content.Context;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.service.notification.NotificationListenerService.Ranking;
|
||||
import android.service.notification.NotificationListenerService.RankingMap;
|
||||
@@ -58,17 +53,13 @@ import com.android.internal.statusbar.StatusBarIcon;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.ContrastColorUtil;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.ForegroundServiceController;
|
||||
import com.android.systemui.statusbar.InflationTask;
|
||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
|
||||
import com.android.systemui.statusbar.NotificationMediaManager;
|
||||
import com.android.systemui.statusbar.StatusBarIconView;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGuts;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
|
||||
import com.android.systemui.statusbar.phone.NotificationGroupManager;
|
||||
import com.android.systemui.statusbar.phone.ShadeController;
|
||||
import com.android.systemui.statusbar.phone.StatusBar;
|
||||
import com.android.systemui.statusbar.policy.HeadsUpManager;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -83,14 +74,13 @@ import java.util.Objects;
|
||||
*/
|
||||
public class NotificationData {
|
||||
|
||||
private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
|
||||
|
||||
/**
|
||||
* These dependencies are late init-ed
|
||||
*/
|
||||
private KeyguardEnvironment mEnvironment;
|
||||
private ShadeController mShadeController;
|
||||
private NotificationMediaManager mMediaManager;
|
||||
private ForegroundServiceController mFsc;
|
||||
private NotificationLockscreenUserManager mUserManager;
|
||||
|
||||
private HeadsUpManager mHeadsUpManager;
|
||||
|
||||
@@ -120,6 +110,9 @@ public class NotificationData {
|
||||
@NonNull
|
||||
public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
|
||||
public CharSequence[] smartReplies = new CharSequence[0];
|
||||
@VisibleForTesting
|
||||
public int suppressedVisualEffects;
|
||||
public boolean suspended;
|
||||
|
||||
private Entry parent; // our parent (if we're in a group)
|
||||
private ArrayList<Entry> children = new ArrayList<Entry>();
|
||||
@@ -183,6 +176,8 @@ public class NotificationData {
|
||||
smartReplies = ranking.getSmartReplies() == null
|
||||
? new CharSequence[0]
|
||||
: ranking.getSmartReplies().toArray(new CharSequence[0]);
|
||||
suppressedVisualEffects = ranking.getSuppressedVisualEffects();
|
||||
suspended = ranking.isSuspended();
|
||||
}
|
||||
|
||||
public void setInterruption() {
|
||||
@@ -625,6 +620,71 @@ public class NotificationData {
|
||||
if (row == null) return true;
|
||||
return row.canViewBeDismissed();
|
||||
}
|
||||
|
||||
boolean isExemptFromDndVisualSuppression() {
|
||||
if (isNotificationBlockedByPolicy(notification.getNotification())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((notification.getNotification().flags
|
||||
& Notification.FLAG_FOREGROUND_SERVICE) != 0) {
|
||||
return true;
|
||||
}
|
||||
if (notification.getNotification().isMediaNotification()) {
|
||||
return true;
|
||||
}
|
||||
if (mIsSystemNotification != null && mIsSystemNotification) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldSuppressVisualEffect(int effect) {
|
||||
if (isExemptFromDndVisualSuppression()) {
|
||||
return false;
|
||||
}
|
||||
return (suppressedVisualEffects & effect) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT}
|
||||
* is set for this entry.
|
||||
*/
|
||||
public boolean shouldSuppressFullScreenIntent() {
|
||||
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}
|
||||
* is set for this entry.
|
||||
*/
|
||||
public boolean shouldSuppressPeek() {
|
||||
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_STATUS_BAR}
|
||||
* is set for this entry.
|
||||
*/
|
||||
public boolean shouldSuppressStatusBar() {
|
||||
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}
|
||||
* is set for this entry.
|
||||
*/
|
||||
public boolean shouldSuppressAmbient() {
|
||||
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
|
||||
* is set for this entry.
|
||||
*/
|
||||
public boolean shouldSuppressNotificationList() {
|
||||
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
|
||||
@@ -706,13 +766,6 @@ public class NotificationData {
|
||||
return mEnvironment;
|
||||
}
|
||||
|
||||
private ShadeController getShadeController() {
|
||||
if (mShadeController == null) {
|
||||
mShadeController = Dependency.get(ShadeController.class);
|
||||
}
|
||||
return mShadeController;
|
||||
}
|
||||
|
||||
private NotificationMediaManager getMediaManager() {
|
||||
if (mMediaManager == null) {
|
||||
mMediaManager = Dependency.get(NotificationMediaManager.class);
|
||||
@@ -720,20 +773,6 @@ public class NotificationData {
|
||||
return mMediaManager;
|
||||
}
|
||||
|
||||
private ForegroundServiceController getFsc() {
|
||||
if (mFsc == null) {
|
||||
mFsc = Dependency.get(ForegroundServiceController.class);
|
||||
}
|
||||
return mFsc;
|
||||
}
|
||||
|
||||
private NotificationLockscreenUserManager getUserManager() {
|
||||
if (mUserManager == null) {
|
||||
mUserManager = Dependency.get(NotificationLockscreenUserManager.class);
|
||||
}
|
||||
return mUserManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment}
|
||||
*
|
||||
@@ -778,7 +817,7 @@ public class NotificationData {
|
||||
}
|
||||
|
||||
public Entry remove(String key, RankingMap ranking) {
|
||||
Entry removed = null;
|
||||
Entry removed;
|
||||
synchronized (mEntries) {
|
||||
removed = mEntries.remove(key);
|
||||
}
|
||||
@@ -849,62 +888,12 @@ public class NotificationData {
|
||||
return Ranking.VISIBILITY_NO_OVERRIDE;
|
||||
}
|
||||
|
||||
public boolean shouldSuppressFullScreenIntent(Entry entry) {
|
||||
return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
|
||||
}
|
||||
|
||||
public boolean shouldSuppressPeek(Entry entry) {
|
||||
return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_PEEK);
|
||||
}
|
||||
|
||||
public boolean shouldSuppressStatusBar(Entry entry) {
|
||||
return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_STATUS_BAR);
|
||||
}
|
||||
|
||||
public boolean shouldSuppressAmbient(Entry entry) {
|
||||
return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_AMBIENT);
|
||||
}
|
||||
|
||||
public boolean shouldSuppressNotificationList(Entry entry) {
|
||||
return shouldSuppressVisualEffect(entry, SUPPRESSED_EFFECT_NOTIFICATION_LIST);
|
||||
}
|
||||
|
||||
private boolean shouldSuppressVisualEffect(Entry entry, int effect) {
|
||||
if (isExemptFromDndVisualSuppression(entry)) {
|
||||
return false;
|
||||
}
|
||||
String key = entry.key;
|
||||
if (mRankingMap != null) {
|
||||
getRanking(key, mTmpRanking);
|
||||
return (mTmpRanking.getSuppressedVisualEffects() & effect) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isExemptFromDndVisualSuppression(Entry entry) {
|
||||
if (isNotificationBlockedByPolicy(entry.notification.getNotification())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((entry.notification.getNotification().flags
|
||||
& Notification.FLAG_FOREGROUND_SERVICE) != 0) {
|
||||
return true;
|
||||
}
|
||||
if (entry.notification.getNotification().isMediaNotification()) {
|
||||
return true;
|
||||
}
|
||||
if (entry.mIsSystemNotification != null && entry.mIsSystemNotification) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Categories that are explicitly called out on DND settings screens are always blocked, if
|
||||
* DND has flagged them, even if they are foreground or system notifications that might
|
||||
* otherwise visually bypass DND.
|
||||
*/
|
||||
protected boolean isNotificationBlockedByPolicy(Notification n) {
|
||||
private static boolean isNotificationBlockedByPolicy(Notification n) {
|
||||
if (isCategory(CATEGORY_CALL, n)
|
||||
|| isCategory(CATEGORY_MESSAGE, n)
|
||||
|| isCategory(CATEGORY_ALARM, n)
|
||||
@@ -915,7 +904,7 @@ public class NotificationData {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isCategory(String category, Notification n) {
|
||||
private static boolean isCategory(String category, Notification n) {
|
||||
return Objects.equals(n.category, category);
|
||||
}
|
||||
|
||||
@@ -1013,7 +1002,7 @@ public class NotificationData {
|
||||
for (int i = 0; i < N; i++) {
|
||||
Entry entry = mEntries.valueAt(i);
|
||||
|
||||
if (shouldFilterOut(entry)) {
|
||||
if (mNotificationFilter.shouldFilterOut(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1024,87 +1013,6 @@ public class NotificationData {
|
||||
Collections.sort(mSortedAndFiltered, mRankingComparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this notification should NOT be shown right now
|
||||
*/
|
||||
public boolean shouldFilterOut(Entry entry) {
|
||||
final StatusBarNotification sbn = entry.notification;
|
||||
if (!(getEnvironment().isDeviceProvisioned() ||
|
||||
showNotificationEvenIfUnprovisioned(sbn))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getUserManager().isLockscreenPublicMode(sbn.getUserId()) &&
|
||||
(sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
|
||||
|| getUserManager().shouldHideNotifications(sbn.getUserId())
|
||||
|| getUserManager().shouldHideNotifications(sbn.getKey()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getShadeController().isDozing() && shouldSuppressAmbient(entry)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!getShadeController().isDozing() && shouldSuppressNotificationList(entry)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shouldHide(sbn.getKey())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
|
||||
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getFsc().isDungeonNotification(sbn)
|
||||
&& !getFsc().isDungeonNeededForUser(sbn.getUserId())) {
|
||||
// this is a foreground-service disclosure for a user that does not need to show one
|
||||
return true;
|
||||
}
|
||||
if (getFsc().isSystemAlertNotification(sbn)) {
|
||||
final String[] apps = sbn.getNotification().extras.getStringArray(
|
||||
Notification.EXTRA_FOREGROUND_APPS);
|
||||
if (apps != null && apps.length >= 1) {
|
||||
if (!getFsc().isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Q: What kinds of notifications should show during setup?
|
||||
// A: Almost none! Only things coming from packages with permission
|
||||
// android.permission.NOTIFICATION_DURING_SETUP that also have special "kind" tags marking them
|
||||
// as relevant for setup (see below).
|
||||
public static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
|
||||
return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean showNotificationEvenIfUnprovisioned(IPackageManager packageManager,
|
||||
StatusBarNotification sbn) {
|
||||
return checkUidPermission(packageManager, Manifest.permission.NOTIFICATION_DURING_SETUP,
|
||||
sbn.getUid()) == PackageManager.PERMISSION_GRANTED
|
||||
&& sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
|
||||
}
|
||||
|
||||
private static int checkUidPermission(IPackageManager packageManager, String permission,
|
||||
int uid) {
|
||||
try {
|
||||
return packageManager.checkUidPermission(permission, uid);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw, String indent) {
|
||||
int N = mSortedAndFiltered.size();
|
||||
pw.print(indent);
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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;
|
||||
|
||||
/**
|
||||
* Listener interface for changes sent by NotificationEntryManager.
|
||||
*/
|
||||
public interface NotificationEntryListener {
|
||||
/**
|
||||
* Called when a new notification is posted. At this point, the notification is "pending": its
|
||||
* views haven't been inflated yet and most of the system pretends like it doesn't exist yet.
|
||||
*/
|
||||
default void onPendingEntryAdded(NotificationData.Entry entry) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new entry is created.
|
||||
*/
|
||||
default void onNotificationAdded(NotificationData.Entry entry) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a notification was updated.
|
||||
*/
|
||||
default void onNotificationUpdated(StatusBarNotification notification) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an existing notification's views are reinflated (usually due to an update being
|
||||
* posted to that notification).
|
||||
*/
|
||||
default void onEntryReinflated(NotificationData.Entry entry) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a notification has been removed (either because the user swiped it away or
|
||||
* because the developer retracted it).
|
||||
*
|
||||
* TODO: combine this with onNotificationRemoved().
|
||||
*/
|
||||
default void onEntryRemoved(NotificationData.Entry entry) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a notification was removed.
|
||||
*
|
||||
* @param key key of notification that was removed
|
||||
* @param old StatusBarNotification of the notification before it was removed
|
||||
*/
|
||||
default void onNotificationRemoved(String key, StatusBarNotification old) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a notification immediately.
|
||||
*
|
||||
* TODO: combine this with onNotificationRemoved().
|
||||
*/
|
||||
default void onPerformRemoveNotification(StatusBarNotification statusBarNotification) {
|
||||
}
|
||||
}
|
||||
@@ -17,43 +17,30 @@ package com.android.systemui.statusbar.notification;
|
||||
|
||||
import static com.android.systemui.bubbles.BubbleController.DEBUG_DEMOTE_TO_NOTIF;
|
||||
import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
|
||||
import static com.android.systemui.statusbar.StatusBarState.SHADE;
|
||||
import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT;
|
||||
import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.service.dreams.DreamService;
|
||||
import android.service.dreams.IDreamManager;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.NotificationStats;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.EventLog;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.internal.statusbar.NotificationVisibility;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.EventLogTags;
|
||||
import com.android.systemui.ForegroundServiceController;
|
||||
import com.android.systemui.UiOffloadThread;
|
||||
import com.android.systemui.bubbles.BubbleController;
|
||||
import com.android.systemui.statusbar.AlertingNotificationManager;
|
||||
import com.android.systemui.statusbar.AmbientPulseManager;
|
||||
@@ -64,7 +51,6 @@ import com.android.systemui.statusbar.NotificationPresenter;
|
||||
import com.android.systemui.statusbar.NotificationRemoteInputManager;
|
||||
import com.android.systemui.statusbar.NotificationUiAdjustment;
|
||||
import com.android.systemui.statusbar.NotificationUpdateHandler;
|
||||
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
|
||||
@@ -97,8 +83,6 @@ public class NotificationEntryManager implements
|
||||
BubbleController.BubbleDismissListener {
|
||||
private static final String TAG = "NotificationEntryMgr";
|
||||
protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
private static final boolean ENABLE_HEADS_UP = true;
|
||||
private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
|
||||
|
||||
public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
|
||||
|
||||
@@ -109,7 +93,6 @@ public class NotificationEntryManager implements
|
||||
Dependency.get(NotificationGroupManager.class);
|
||||
private final NotificationGutsManager mGutsManager =
|
||||
Dependency.get(NotificationGutsManager.class);
|
||||
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
|
||||
private final DeviceProvisionedController mDeviceProvisionedController =
|
||||
Dependency.get(DeviceProvisionedController.class);
|
||||
private final VisualStabilityManager mVisualStabilityManager =
|
||||
@@ -119,6 +102,8 @@ public class NotificationEntryManager implements
|
||||
private final AmbientPulseManager mAmbientPulseManager =
|
||||
Dependency.get(AmbientPulseManager.class);
|
||||
private final BubbleController mBubbleController = Dependency.get(BubbleController.class);
|
||||
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
|
||||
Dependency.get(NotificationInterruptionStateProvider.class);
|
||||
|
||||
// Lazily retrieved dependencies
|
||||
private NotificationRemoteInputManager mRemoteInputManager;
|
||||
@@ -130,23 +115,18 @@ public class NotificationEntryManager implements
|
||||
private final Handler mDeferredNotificationViewUpdateHandler;
|
||||
private Runnable mUpdateNotificationViewsCallback;
|
||||
|
||||
protected IDreamManager mDreamManager;
|
||||
protected IStatusBarService mBarService;
|
||||
private NotificationPresenter mPresenter;
|
||||
private Callback mCallback;
|
||||
private NotificationEntryListener mCallback;
|
||||
protected PowerManager mPowerManager;
|
||||
private NotificationListenerService.RankingMap mLatestRankingMap;
|
||||
protected HeadsUpManager mHeadsUpManager;
|
||||
protected NotificationData mNotificationData;
|
||||
private ContentObserver mHeadsUpObserver;
|
||||
protected boolean mUseHeadsUp = false;
|
||||
private boolean mDisableNotificationAlerts;
|
||||
protected NotificationListContainer mListContainer;
|
||||
@VisibleForTesting
|
||||
final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
|
||||
= new ArrayList<>();
|
||||
private NotificationViewHierarchyManager.StatusBarStateListener mStatusBarStateListener;
|
||||
@Nullable private AlertTransferListener mAlertTransferListener;
|
||||
private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>();
|
||||
|
||||
private final DeviceProvisionedController.DeviceProvisionedListener
|
||||
mDeviceProvisionedListener =
|
||||
@@ -157,11 +137,6 @@ public class NotificationEntryManager implements
|
||||
}
|
||||
};
|
||||
|
||||
public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
|
||||
mDisableNotificationAlerts = disableNotificationAlerts;
|
||||
mHeadsUpObserver.onChange(true);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
|
||||
}
|
||||
@@ -177,8 +152,6 @@ public class NotificationEntryManager implements
|
||||
pw.println(entry.notification);
|
||||
}
|
||||
}
|
||||
pw.print(" mUseHeadsUp=");
|
||||
pw.println(mUseHeadsUp);
|
||||
}
|
||||
|
||||
public NotificationEntryManager(Context context) {
|
||||
@@ -186,15 +159,14 @@ public class NotificationEntryManager implements
|
||||
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
|
||||
mBarService = IStatusBarService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
|
||||
mDreamManager = IDreamManager.Stub.asInterface(
|
||||
ServiceManager.checkService(DreamService.DREAM_SERVICE));
|
||||
mBubbleController.setDismissListener(this /* bubbleEventListener */);
|
||||
mNotificationData = new NotificationData();
|
||||
mDeferredNotificationViewUpdateHandler = new Handler();
|
||||
}
|
||||
|
||||
public void setAlertTransferListener(AlertTransferListener listener) {
|
||||
mAlertTransferListener = listener;
|
||||
/** Adds a {@link NotificationEntryListener}. */
|
||||
public void addNotificationEntryListener(NotificationEntryListener listener) {
|
||||
mNotificationEntryListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,7 +208,7 @@ public class NotificationEntryManager implements
|
||||
}
|
||||
|
||||
public void setUpWithPresenter(NotificationPresenter presenter,
|
||||
NotificationListContainer listContainer, Callback callback,
|
||||
NotificationListContainer listContainer, NotificationEntryListener callback,
|
||||
HeadsUpManager headsUpManager) {
|
||||
mPresenter = presenter;
|
||||
mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews;
|
||||
@@ -245,36 +217,6 @@ public class NotificationEntryManager implements
|
||||
mNotificationData.setHeadsUpManager(mHeadsUpManager);
|
||||
mListContainer = listContainer;
|
||||
|
||||
mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
boolean wasUsing = mUseHeadsUp;
|
||||
mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
|
||||
&& Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
|
||||
mContext.getContentResolver(),
|
||||
Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
|
||||
Settings.Global.HEADS_UP_OFF);
|
||||
Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
|
||||
if (wasUsing != mUseHeadsUp) {
|
||||
if (!mUseHeadsUp) {
|
||||
Log.d(TAG,
|
||||
"dismissing any existing heads up notification on disable event");
|
||||
mHeadsUpManager.releaseAllImmediately();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (ENABLE_HEADS_UP) {
|
||||
mContext.getContentResolver().registerContentObserver(
|
||||
Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
|
||||
true,
|
||||
mHeadsUpObserver);
|
||||
mContext.getContentResolver().registerContentObserver(
|
||||
Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
|
||||
mHeadsUpObserver);
|
||||
}
|
||||
|
||||
mNotificationLifetimeExtenders.add(mHeadsUpManager);
|
||||
mNotificationLifetimeExtenders.add(mAmbientPulseManager);
|
||||
mNotificationLifetimeExtenders.add(mGutsManager);
|
||||
@@ -285,20 +227,6 @@ public class NotificationEntryManager implements
|
||||
}
|
||||
|
||||
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
|
||||
|
||||
mHeadsUpObserver.onChange(true); // set up
|
||||
|
||||
getRowBinder().setInterruptionStateProvider(new InterruptionStateProvider() {
|
||||
@Override
|
||||
public boolean shouldHeadsUp(NotificationData.Entry entry) {
|
||||
return NotificationEntryManager.this.shouldHeadsUp(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPulse(NotificationData.Entry entry) {
|
||||
return NotificationEntryManager.this.shouldPulse(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public NotificationData getNotificationData() {
|
||||
@@ -322,14 +250,6 @@ public class NotificationEntryManager implements
|
||||
updateNotifications();
|
||||
}
|
||||
|
||||
private boolean shouldSuppressFullScreenIntent(NotificationData.Entry entry) {
|
||||
if (mPresenter.isDeviceInVrMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return mNotificationData.shouldSuppressFullScreenIntent(entry);
|
||||
}
|
||||
|
||||
public void performRemoveNotification(StatusBarNotification n) {
|
||||
final int rank = mNotificationData.getRank(n.getKey());
|
||||
final int count = mNotificationData.getActiveNotifications().size();
|
||||
@@ -441,7 +361,7 @@ public class NotificationEntryManager implements
|
||||
if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
|
||||
// Possible for shouldHeadsUp to change between the inflation starting and ending.
|
||||
// If it does and we no longer need to heads up, we should free the view.
|
||||
if (shouldHeadsUp(entry)) {
|
||||
if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
|
||||
mHeadsUpManager.showNotification(entry);
|
||||
// Mark as seen immediately
|
||||
setNotificationShown(entry.notification);
|
||||
@@ -450,7 +370,7 @@ public class NotificationEntryManager implements
|
||||
}
|
||||
}
|
||||
if ((inflatedFlags & FLAG_CONTENT_VIEW_AMBIENT) != 0) {
|
||||
if (shouldPulse(entry)) {
|
||||
if (mNotificationInterruptionStateProvider.shouldPulse(entry)) {
|
||||
mAmbientPulseManager.showNotification(entry);
|
||||
} else {
|
||||
entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_AMBIENT);
|
||||
@@ -474,8 +394,8 @@ public class NotificationEntryManager implements
|
||||
mVisualStabilityManager.onLowPriorityUpdated(entry);
|
||||
mPresenter.updateNotificationViews();
|
||||
}
|
||||
if (mAlertTransferListener != null) {
|
||||
mAlertTransferListener.onEntryReinflated(entry);
|
||||
for (NotificationEntryListener listener : mNotificationEntryListeners) {
|
||||
listener.onEntryReinflated(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -492,8 +412,10 @@ public class NotificationEntryManager implements
|
||||
final NotificationData.Entry entry = mNotificationData.get(key);
|
||||
|
||||
abortExistingInflation(key);
|
||||
if (mAlertTransferListener != null && entry != null) {
|
||||
mAlertTransferListener.onEntryRemoved(entry);
|
||||
if (entry != null) {
|
||||
for (NotificationEntryListener listener : mNotificationEntryListeners) {
|
||||
listener.onEntryRemoved(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to remove notifications from their alert managers (heads up, ambient pulse).
|
||||
@@ -650,51 +572,15 @@ public class NotificationEntryManager implements
|
||||
mNotificationData.updateRanking(rankingMap);
|
||||
NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
|
||||
rankingMap.getRanking(key, ranking);
|
||||
NotificationData.Entry shadeEntry = createNotificationEntry(notification, ranking);
|
||||
boolean isHeadsUped = shouldHeadsUp(shadeEntry);
|
||||
if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
|
||||
if (shouldSuppressFullScreenIntent(shadeEntry)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
|
||||
}
|
||||
} else if (mNotificationData.getImportance(key)
|
||||
< NotificationManager.IMPORTANCE_HIGH) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No Fullscreen intent: not important enough: "
|
||||
+ key);
|
||||
}
|
||||
} else {
|
||||
// Stop screensaver if the notification has a fullscreen intent.
|
||||
// (like an incoming phone call)
|
||||
Dependency.get(UiOffloadThread.class).submit(() -> {
|
||||
try {
|
||||
mDreamManager.awaken();
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
// not immersive & a fullscreen alert should be shown
|
||||
if (DEBUG)
|
||||
Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
|
||||
try {
|
||||
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
|
||||
key);
|
||||
notification.getNotification().fullScreenIntent.send();
|
||||
shadeEntry.notifyFullScreenIntentLaunched();
|
||||
mMetricsLogger.count("note_fullscreen", 1);
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
NotificationData.Entry entry = createNotificationEntry(notification, ranking);
|
||||
abortExistingInflation(key);
|
||||
|
||||
mForegroundServiceController.addNotification(notification,
|
||||
mNotificationData.getImportance(key));
|
||||
|
||||
mPendingNotifications.put(key, shadeEntry);
|
||||
if (mAlertTransferListener != null) {
|
||||
mAlertTransferListener.onPendingEntryAdded(shadeEntry);
|
||||
mPendingNotifications.put(key, entry);
|
||||
for (NotificationEntryListener listener : mNotificationEntryListeners) {
|
||||
listener.onPendingEntryAdded(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,9 +653,11 @@ public class NotificationEntryManager implements
|
||||
|
||||
boolean alertAgain = alertAgain(entry, entry.notification.getNotification());
|
||||
if (getShadeController().isDozing()) {
|
||||
updateAlertState(entry, shouldPulse(entry), alertAgain, mAmbientPulseManager);
|
||||
updateAlertState(entry, mNotificationInterruptionStateProvider.shouldPulse(entry),
|
||||
alertAgain, mAmbientPulseManager);
|
||||
} else {
|
||||
updateAlertState(entry, shouldHeadsUp(entry), alertAgain, mHeadsUpManager);
|
||||
updateAlertState(entry, mNotificationInterruptionStateProvider.shouldHeadsUp(entry),
|
||||
alertAgain, mHeadsUpManager);
|
||||
}
|
||||
updateNotifications();
|
||||
|
||||
@@ -828,7 +716,7 @@ public class NotificationEntryManager implements
|
||||
|
||||
// By comparing the old and new UI adjustments, reinflate the view accordingly.
|
||||
for (NotificationData.Entry entry : entries) {
|
||||
mNotificationRowBinder.onNotificationRankingUpdated(
|
||||
getRowBinder().onNotificationRankingUpdated(
|
||||
entry,
|
||||
oldImportances.get(entry.key),
|
||||
oldAdjustments.get(entry.key),
|
||||
@@ -851,182 +739,6 @@ public class NotificationEntryManager implements
|
||||
}
|
||||
}
|
||||
|
||||
public void setStatusBarStateListener(
|
||||
NotificationViewHierarchyManager.StatusBarStateListener listener) {
|
||||
mStatusBarStateListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the notification should peek in from the top and alert the user.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if the entry should heads up, false otherwise
|
||||
*/
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
|
||||
public boolean shouldHeadsUp(NotificationData.Entry entry) {
|
||||
StatusBarNotification sbn = entry.notification;
|
||||
|
||||
if (getShadeController().isDozing()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: device is dozing: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: need to changes this, e.g. should still heads up in expanded shade, might want
|
||||
// message bubble from the bubble to go through heads up path
|
||||
boolean inShade = mStatusBarStateListener != null
|
||||
&& mStatusBarStateListener.getCurrentState() == SHADE;
|
||||
if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canAlertCommon(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: no huns or vr mode");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isDreaming = false;
|
||||
try {
|
||||
isDreaming = mDreamManager.isDreaming();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to query dream manager.", e);
|
||||
}
|
||||
boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
|
||||
|
||||
if (!inUse) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: not in use: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mNotificationData.shouldSuppressPeek(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isSnoozedPackage(sbn)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.hasJustLaunchedFullScreenIntent()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mNotificationData.getImportance(sbn.getKey()) < NotificationManager.IMPORTANCE_HIGH) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mCallback.canHeadsUp(entry, sbn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the notification should "pulse" on the user's display when the phone is
|
||||
* dozing. This displays the ambient view of the notification.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if the entry should ambient pulse, false otherwise
|
||||
*/
|
||||
private boolean shouldPulse(NotificationData.Entry entry) {
|
||||
StatusBarNotification sbn = entry.notification;
|
||||
|
||||
if (!getShadeController().isDozing()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No pulsing: not dozing: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canAlertCommon(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mNotificationData.shouldSuppressAmbient(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No pulsing: ambient effect suppressed: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mNotificationData.getImportance(sbn.getKey())
|
||||
< NotificationManager.IMPORTANCE_DEFAULT) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bundle extras = sbn.getNotification().extras;
|
||||
CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE);
|
||||
CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT);
|
||||
if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No pulsing: title and text are empty: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common checks between heads up alerting and ambient pulse alerting. See
|
||||
* {@link NotificationEntryManager#shouldHeadsUp(NotificationData.Entry)} and
|
||||
* {@link NotificationEntryManager#shouldPulse(NotificationData.Entry)}. Notifications that
|
||||
* fail any of these checks should not alert at all.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if these checks pass, false if the notification should not alert
|
||||
*/
|
||||
protected boolean canAlertCommon(NotificationData.Entry entry) {
|
||||
StatusBarNotification sbn = entry.notification;
|
||||
|
||||
if (mNotificationData.shouldFilterOut(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No alerting: filtered notification: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't alert notifications that are suppressed due to group alert behavior
|
||||
if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No alerting: suppressed due to group alert behavior");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setNotificationShown(StatusBarNotification n) {
|
||||
setNotificationsShown(new String[]{n.getKey()});
|
||||
}
|
||||
@@ -1039,10 +751,6 @@ public class NotificationEntryManager implements
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSnoozedPackage(StatusBarNotification sbn) {
|
||||
return mHeadsUpManager.isSnoozed(sbn.getPackageName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the entry's alert state and call the appropriate {@link AlertingNotificationManager}
|
||||
* method.
|
||||
@@ -1076,64 +784,4 @@ public class NotificationEntryManager implements
|
||||
public Iterable<NotificationData.Entry> getPendingNotificationsIterator() {
|
||||
return mPendingNotifications.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for retrieving heads-up and pulsing state for an entry.
|
||||
*/
|
||||
public interface InterruptionStateProvider {
|
||||
/**
|
||||
* Whether the provided entry should be marked as heads-up when inflated.
|
||||
*/
|
||||
boolean shouldHeadsUp(NotificationData.Entry entry);
|
||||
|
||||
/**
|
||||
* Whether the provided entry should be marked as pulsing (displayed in ambient) when
|
||||
* inflated.
|
||||
*/
|
||||
boolean shouldPulse(NotificationData.Entry entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for NotificationEntryManager.
|
||||
*/
|
||||
public interface Callback {
|
||||
|
||||
/**
|
||||
* Called when a new entry is created.
|
||||
*
|
||||
* @param shadeEntry entry that was created
|
||||
*/
|
||||
void onNotificationAdded(NotificationData.Entry shadeEntry);
|
||||
|
||||
/**
|
||||
* Called when a notification was updated.
|
||||
*
|
||||
* @param notification notification that was updated
|
||||
*/
|
||||
void onNotificationUpdated(StatusBarNotification notification);
|
||||
|
||||
/**
|
||||
* Called when a notification was removed.
|
||||
*
|
||||
* @param key key of notification that was removed
|
||||
* @param old StatusBarNotification of the notification before it was removed
|
||||
*/
|
||||
void onNotificationRemoved(String key, StatusBarNotification old);
|
||||
|
||||
/**
|
||||
* Removes a notification immediately.
|
||||
*
|
||||
* @param statusBarNotification notification that is being removed
|
||||
*/
|
||||
void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
|
||||
|
||||
/**
|
||||
* Returns true if NotificationEntryManager can heads up this notification.
|
||||
*
|
||||
* @param entry entry of the notification that might be heads upped
|
||||
* @param sbn notification that might be heads upped
|
||||
* @return true if the notification can be heads upped
|
||||
*/
|
||||
boolean canHeadsUp(NotificationData.Entry entry, StatusBarNotification sbn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.Manifest;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.Notification;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.RemoteException;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.ForegroundServiceController;
|
||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
|
||||
import com.android.systemui.statusbar.phone.NotificationGroupManager;
|
||||
import com.android.systemui.statusbar.phone.ShadeController;
|
||||
import com.android.systemui.statusbar.phone.StatusBar;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** Component which manages the various reasons a notification might be filtered out. */
|
||||
@Singleton
|
||||
public class NotificationFilter {
|
||||
|
||||
private final NotificationGroupManager mGroupManager = Dependency.get(
|
||||
NotificationGroupManager.class);
|
||||
|
||||
private NotificationData.KeyguardEnvironment mEnvironment;
|
||||
private ShadeController mShadeController;
|
||||
private ForegroundServiceController mFsc;
|
||||
private NotificationLockscreenUserManager mUserManager;
|
||||
|
||||
@Inject
|
||||
public NotificationFilter() {}
|
||||
|
||||
private NotificationData.KeyguardEnvironment getEnvironment() {
|
||||
if (mEnvironment == null) {
|
||||
mEnvironment = Dependency.get(NotificationData.KeyguardEnvironment.class);
|
||||
}
|
||||
return mEnvironment;
|
||||
}
|
||||
|
||||
private ShadeController getShadeController() {
|
||||
if (mShadeController == null) {
|
||||
mShadeController = Dependency.get(ShadeController.class);
|
||||
}
|
||||
return mShadeController;
|
||||
}
|
||||
|
||||
private ForegroundServiceController getFsc() {
|
||||
if (mFsc == null) {
|
||||
mFsc = Dependency.get(ForegroundServiceController.class);
|
||||
}
|
||||
return mFsc;
|
||||
}
|
||||
|
||||
private NotificationLockscreenUserManager getUserManager() {
|
||||
if (mUserManager == null) {
|
||||
mUserManager = Dependency.get(NotificationLockscreenUserManager.class);
|
||||
}
|
||||
return mUserManager;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the provided notification should NOT be shown right now.
|
||||
*/
|
||||
public boolean shouldFilterOut(NotificationData.Entry entry) {
|
||||
final StatusBarNotification sbn = entry.notification;
|
||||
if (!(getEnvironment().isDeviceProvisioned()
|
||||
|| showNotificationEvenIfUnprovisioned(sbn))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getUserManager().isLockscreenPublicMode(sbn.getUserId())
|
||||
&& (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
|
||||
|| getUserManager().shouldHideNotifications(sbn.getUserId())
|
||||
|| getUserManager().shouldHideNotifications(sbn.getKey()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getShadeController().isDozing() && entry.shouldSuppressAmbient()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!getShadeController().isDozing() && entry.shouldSuppressNotificationList()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entry.suspended) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
|
||||
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getFsc().isDungeonNotification(sbn)
|
||||
&& !getFsc().isDungeonNeededForUser(sbn.getUserId())) {
|
||||
// this is a foreground-service disclosure for a user that does not need to show one
|
||||
return true;
|
||||
}
|
||||
if (getFsc().isSystemAlertNotification(sbn)) {
|
||||
final String[] apps = sbn.getNotification().extras.getStringArray(
|
||||
Notification.EXTRA_FOREGROUND_APPS);
|
||||
if (apps != null && apps.length >= 1) {
|
||||
if (!getFsc().isSystemAlertWarningNeeded(sbn.getUserId(), apps[0])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Q: What kinds of notifications should show during setup?
|
||||
// A: Almost none! Only things coming from packages with permission
|
||||
// android.permission.NOTIFICATION_DURING_SETUP that also have special "kind" tags marking them
|
||||
// as relevant for setup (see below).
|
||||
private static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
|
||||
return showNotificationEvenIfUnprovisioned(AppGlobals.getPackageManager(), sbn);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean showNotificationEvenIfUnprovisioned(IPackageManager packageManager,
|
||||
StatusBarNotification sbn) {
|
||||
return checkUidPermission(packageManager, Manifest.permission.NOTIFICATION_DURING_SETUP,
|
||||
sbn.getUid()) == PackageManager.PERMISSION_GRANTED
|
||||
&& sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
|
||||
}
|
||||
|
||||
private static int checkUidPermission(IPackageManager packageManager, String permission,
|
||||
int uid) {
|
||||
try {
|
||||
return packageManager.checkUidPermission(permission, uid);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 static com.android.systemui.statusbar.StatusBarState.SHADE;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.provider.Settings;
|
||||
import android.service.dreams.DreamService;
|
||||
import android.service.dreams.IDreamManager;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.statusbar.NotificationPresenter;
|
||||
import com.android.systemui.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.phone.ShadeController;
|
||||
import com.android.systemui.statusbar.policy.HeadsUpManager;
|
||||
|
||||
/**
|
||||
* Provides heads-up and pulsing state for notification entries.
|
||||
*/
|
||||
public class NotificationInterruptionStateProvider {
|
||||
|
||||
private static final String TAG = "InterruptionStateProvider";
|
||||
private static final boolean DEBUG = false;
|
||||
private static final boolean ENABLE_HEADS_UP = true;
|
||||
private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
|
||||
|
||||
private final StatusBarStateController mStatusBarStateController =
|
||||
Dependency.get(StatusBarStateController.class);
|
||||
private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
|
||||
|
||||
private final Context mContext;
|
||||
private final PowerManager mPowerManager;
|
||||
private final IDreamManager mDreamManager;
|
||||
|
||||
private NotificationPresenter mPresenter;
|
||||
private ShadeController mShadeController;
|
||||
private HeadsUpManager mHeadsUpManager;
|
||||
private HeadsUpSuppressor mHeadsUpSuppressor;
|
||||
|
||||
private ContentObserver mHeadsUpObserver;
|
||||
@VisibleForTesting
|
||||
protected boolean mUseHeadsUp = false;
|
||||
private boolean mDisableNotificationAlerts;
|
||||
|
||||
public NotificationInterruptionStateProvider(Context context) {
|
||||
this(context,
|
||||
(PowerManager) context.getSystemService(Context.POWER_SERVICE),
|
||||
IDreamManager.Stub.asInterface(
|
||||
ServiceManager.checkService(DreamService.DREAM_SERVICE)));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected NotificationInterruptionStateProvider(
|
||||
Context context,
|
||||
PowerManager powerManager,
|
||||
IDreamManager dreamManager) {
|
||||
mContext = context;
|
||||
mPowerManager = powerManager;
|
||||
mDreamManager = dreamManager;
|
||||
}
|
||||
|
||||
/** Sets up late-binding dependencies for this component. */
|
||||
public void setUpWithPresenter(
|
||||
NotificationPresenter notificationPresenter,
|
||||
HeadsUpManager headsUpManager,
|
||||
HeadsUpSuppressor headsUpSuppressor) {
|
||||
mPresenter = notificationPresenter;
|
||||
mHeadsUpManager = headsUpManager;
|
||||
mHeadsUpSuppressor = headsUpSuppressor;
|
||||
|
||||
mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
boolean wasUsing = mUseHeadsUp;
|
||||
mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
|
||||
&& Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
|
||||
mContext.getContentResolver(),
|
||||
Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
|
||||
Settings.Global.HEADS_UP_OFF);
|
||||
Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
|
||||
if (wasUsing != mUseHeadsUp) {
|
||||
if (!mUseHeadsUp) {
|
||||
Log.d(TAG,
|
||||
"dismissing any existing heads up notification on disable event");
|
||||
mHeadsUpManager.releaseAllImmediately();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (ENABLE_HEADS_UP) {
|
||||
mContext.getContentResolver().registerContentObserver(
|
||||
Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
|
||||
true,
|
||||
mHeadsUpObserver);
|
||||
mContext.getContentResolver().registerContentObserver(
|
||||
Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
|
||||
mHeadsUpObserver);
|
||||
}
|
||||
mHeadsUpObserver.onChange(true); // set up
|
||||
}
|
||||
|
||||
private ShadeController getShadeController() {
|
||||
if (mShadeController == null) {
|
||||
mShadeController = Dependency.get(ShadeController.class);
|
||||
}
|
||||
return mShadeController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the notification should peek in from the top and alert the user.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if the entry should heads up, false otherwise
|
||||
*/
|
||||
public boolean shouldHeadsUp(NotificationData.Entry entry) {
|
||||
StatusBarNotification sbn = entry.notification;
|
||||
|
||||
if (getShadeController().isDozing()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: device is dozing: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: need to changes this, e.g. should still heads up in expanded shade, might want
|
||||
// message bubble from the bubble to go through heads up path
|
||||
boolean inShade = mStatusBarStateController.getState() == SHADE;
|
||||
if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canAlertCommon(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: no huns or vr mode");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isDreaming = false;
|
||||
try {
|
||||
isDreaming = mDreamManager.isDreaming();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to query dream manager.", e);
|
||||
}
|
||||
boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
|
||||
|
||||
if (!inUse) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: not in use: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.shouldSuppressPeek()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isSnoozedPackage(sbn)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.hasJustLaunchedFullScreenIntent()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the notification should "pulse" on the user's display when the phone is
|
||||
* dozing. This displays the ambient view of the notification.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if the entry should ambient pulse, false otherwise
|
||||
*/
|
||||
public boolean shouldPulse(NotificationData.Entry entry) {
|
||||
StatusBarNotification sbn = entry.notification;
|
||||
|
||||
if (!getShadeController().isDozing()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No pulsing: not dozing: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canAlertCommon(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.shouldSuppressAmbient()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No pulsing: ambient effect suppressed: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.importance < NotificationManager.IMPORTANCE_DEFAULT) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bundle extras = sbn.getNotification().extras;
|
||||
CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE);
|
||||
CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT);
|
||||
if (TextUtils.isEmpty(title) && TextUtils.isEmpty(text)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No pulsing: title and text are empty: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common checks between heads up alerting and ambient pulse alerting. See
|
||||
* {@link #shouldHeadsUp(NotificationData.Entry)} and
|
||||
* {@link #shouldPulse(NotificationData.Entry)}. Notifications that fail any of these checks
|
||||
* should not alert at all.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if these checks pass, false if the notification should not alert
|
||||
*/
|
||||
protected boolean canAlertCommon(NotificationData.Entry entry) {
|
||||
StatusBarNotification sbn = entry.notification;
|
||||
|
||||
if (mNotificationFilter.shouldFilterOut(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No alerting: filtered notification: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't alert notifications that are suppressed due to group alert behavior
|
||||
if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No alerting: suppressed due to group alert behavior");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isSnoozedPackage(StatusBarNotification sbn) {
|
||||
return mHeadsUpManager.isSnoozed(sbn.getPackageName());
|
||||
}
|
||||
|
||||
/** Sets whether to disable all alerts. */
|
||||
public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
|
||||
mDisableNotificationAlerts = disableNotificationAlerts;
|
||||
mHeadsUpObserver.onChange(true);
|
||||
}
|
||||
|
||||
protected NotificationPresenter getPresenter() {
|
||||
return mPresenter;
|
||||
}
|
||||
|
||||
/** A component which can suppress heads-up notifications due to the overall state of the UI. */
|
||||
public interface HeadsUpSuppressor {
|
||||
/**
|
||||
* Returns false if the provided notification is ineligible for heads-up according to this
|
||||
* component.
|
||||
*
|
||||
* @param entry entry of the notification that might be heads upped
|
||||
* @param sbn notification that might be heads upped
|
||||
* @return false if the notification can not be heads upped
|
||||
*/
|
||||
boolean canHeadsUp(NotificationData.Entry entry, StatusBarNotification sbn);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,6 +64,8 @@ public class NotificationRowBinder {
|
||||
private final NotificationGutsManager mGutsManager =
|
||||
Dependency.get(NotificationGutsManager.class);
|
||||
private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
|
||||
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
|
||||
Dependency.get(NotificationInterruptionStateProvider.class);
|
||||
|
||||
private final Context mContext;
|
||||
private final IStatusBarService mBarService;
|
||||
@@ -79,7 +81,6 @@ public class NotificationRowBinder {
|
||||
private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
|
||||
private BindRowCallback mBindRowCallback;
|
||||
private NotificationClicker mNotificationClicker;
|
||||
private NotificationEntryManager.InterruptionStateProvider mInterruptionStateProvider;
|
||||
|
||||
@Inject
|
||||
public NotificationRowBinder(Context context) {
|
||||
@@ -116,11 +117,6 @@ public class NotificationRowBinder {
|
||||
mNotificationClicker = clicker;
|
||||
}
|
||||
|
||||
public void setInterruptionStateProvider(
|
||||
NotificationEntryManager.InterruptionStateProvider interruptionStateProvider) {
|
||||
mInterruptionStateProvider = interruptionStateProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the views for the given entry (possibly asynchronously).
|
||||
*/
|
||||
@@ -253,10 +249,10 @@ public class NotificationRowBinder {
|
||||
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
|
||||
row.setEntry(entry);
|
||||
|
||||
if (mInterruptionStateProvider.shouldHeadsUp(entry)) {
|
||||
if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
|
||||
row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */);
|
||||
}
|
||||
if (mInterruptionStateProvider.shouldPulse(entry)) {
|
||||
if (mNotificationInterruptionStateProvider.shouldPulse(entry)) {
|
||||
row.updateInflationFlag(FLAG_CONTENT_VIEW_AMBIENT, true /* shouldInflate */);
|
||||
}
|
||||
row.setNeedsRedaction(
|
||||
|
||||
@@ -29,8 +29,8 @@ import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListen
|
||||
import com.android.systemui.statusbar.InflationTask;
|
||||
import com.android.systemui.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
|
||||
import com.android.systemui.statusbar.notification.AlertTransferListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationData.Entry;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationInflater.AsyncInflationTask;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
|
||||
@@ -95,7 +95,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
|
||||
// not being up to date.
|
||||
mEntryManager = entryManager;
|
||||
|
||||
mEntryManager.setAlertTransferListener(mAlertTransferListener);
|
||||
mEntryManager.addNotificationEntryListener(mNotificationEntryListener);
|
||||
groupManager.addOnGroupChangeListener(mOnGroupChangeListener);
|
||||
}
|
||||
|
||||
@@ -186,7 +186,8 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
|
||||
}
|
||||
}
|
||||
|
||||
private final AlertTransferListener mAlertTransferListener = new AlertTransferListener() {
|
||||
private final NotificationEntryListener mNotificationEntryListener =
|
||||
new NotificationEntryListener() {
|
||||
// Called when a new notification has been posted but is not inflated yet. We use this to
|
||||
// see as early as we can if we need to abort a transfer.
|
||||
@Override
|
||||
|
||||
@@ -207,7 +207,7 @@ public class NotificationIconAreaController implements DarkReceiver {
|
||||
}
|
||||
|
||||
// showAmbient == show in shade but not shelf
|
||||
if (!showAmbient && mEntryManager.getNotificationData().shouldSuppressStatusBar(entry)) {
|
||||
if (!showAmbient && entry.shouldSuppressStatusBar()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -191,6 +191,7 @@ import com.android.systemui.statusbar.notification.NotificationClicker;
|
||||
import com.android.systemui.statusbar.notification.NotificationData;
|
||||
import com.android.systemui.statusbar.notification.NotificationData.Entry;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.notification.NotificationRowBinder;
|
||||
import com.android.systemui.statusbar.notification.VisualStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
@@ -377,6 +378,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
private NotificationGutsManager mGutsManager;
|
||||
protected NotificationLogger mNotificationLogger;
|
||||
protected NotificationEntryManager mEntryManager;
|
||||
private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
|
||||
private NotificationRowBinder mNotificationRowBinder;
|
||||
protected NotificationViewHierarchyManager mViewHierarchyManager;
|
||||
protected ForegroundServiceController mForegroundServiceController;
|
||||
@@ -622,6 +624,8 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
mGutsManager = Dependency.get(NotificationGutsManager.class);
|
||||
mMediaManager = Dependency.get(NotificationMediaManager.class);
|
||||
mEntryManager = Dependency.get(NotificationEntryManager.class);
|
||||
mNotificationInterruptionStateProvider =
|
||||
Dependency.get(NotificationInterruptionStateProvider.class);
|
||||
mNotificationRowBinder = Dependency.get(NotificationRowBinder.class);
|
||||
mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
|
||||
mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
|
||||
@@ -1413,7 +1417,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
}
|
||||
|
||||
if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
|
||||
mEntryManager.setDisableNotificationAlerts(
|
||||
mNotificationInterruptionStateProvider.setDisableNotificationAlerts(
|
||||
(state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.app.ActivityManager;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.TaskStackBuilder;
|
||||
import android.content.Context;
|
||||
@@ -33,15 +34,21 @@ import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.service.dreams.DreamService;
|
||||
import android.service.dreams.IDreamManager;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.text.TextUtils;
|
||||
import android.util.EventLog;
|
||||
import android.util.Log;
|
||||
import android.view.RemoteAnimationAdapter;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.internal.statusbar.NotificationVisibility;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.EventLogTags;
|
||||
import com.android.systemui.UiOffloadThread;
|
||||
import com.android.systemui.assist.AssistManager;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.statusbar.CommandQueue;
|
||||
@@ -54,7 +61,9 @@ import com.android.systemui.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
|
||||
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
|
||||
import com.android.systemui.statusbar.notification.NotificationData;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.policy.HeadsUpUtil;
|
||||
import com.android.systemui.statusbar.policy.KeyguardMonitor;
|
||||
@@ -66,6 +75,7 @@ import com.android.systemui.statusbar.policy.PreviewInflater;
|
||||
public class StatusBarNotificationActivityStarter implements NotificationActivityStarter {
|
||||
|
||||
private static final String TAG = "NotificationClickHandler";
|
||||
protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final AssistManager mAssistManager = Dependency.get(AssistManager.class);
|
||||
private final NotificationGroupManager mGroupManager =
|
||||
@@ -84,6 +94,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
|
||||
Dependency.get(NotificationEntryManager.class);
|
||||
private final StatusBarStateController mStatusBarStateController =
|
||||
Dependency.get(StatusBarStateController.class);
|
||||
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
|
||||
Dependency.get(NotificationInterruptionStateProvider.class);
|
||||
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
|
||||
|
||||
private final Context mContext;
|
||||
private final NotificationPanelView mNotificationPanel;
|
||||
@@ -94,6 +107,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
|
||||
private final ActivityLaunchAnimator mActivityLaunchAnimator;
|
||||
private final IStatusBarService mBarService;
|
||||
private final CommandQueue mCommandQueue;
|
||||
private final IDreamManager mDreamManager;
|
||||
|
||||
private boolean mIsCollapsingToShowActivityOverLockscreen;
|
||||
|
||||
@@ -112,6 +126,15 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
|
||||
mBarService = IStatusBarService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
|
||||
mCommandQueue = getComponent(context, CommandQueue.class);
|
||||
mDreamManager = IDreamManager.Stub.asInterface(
|
||||
ServiceManager.checkService(DreamService.DREAM_SERVICE));
|
||||
|
||||
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
|
||||
@Override
|
||||
public void onPendingEntryAdded(NotificationData.Entry entry) {
|
||||
handleFullScreenIntent(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,6 +345,45 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
|
||||
}, null, false /* afterKeyguardGone */);
|
||||
}
|
||||
|
||||
private void handleFullScreenIntent(NotificationData.Entry entry) {
|
||||
boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(entry);
|
||||
if (!isHeadsUped && entry.notification.getNotification().fullScreenIntent != null) {
|
||||
if (shouldSuppressFullScreenIntent(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.key);
|
||||
}
|
||||
} else if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No Fullscreen intent: not important enough: " + entry.key);
|
||||
}
|
||||
} else {
|
||||
// Stop screensaver if the notification has a fullscreen intent.
|
||||
// (like an incoming phone call)
|
||||
Dependency.get(UiOffloadThread.class).submit(() -> {
|
||||
try {
|
||||
mDreamManager.awaken();
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
// not immersive & a fullscreen alert should be shown
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
|
||||
}
|
||||
try {
|
||||
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
|
||||
entry.key);
|
||||
entry.notification.getNotification().fullScreenIntent.send();
|
||||
entry.notifyFullScreenIntentLaunched();
|
||||
mMetricsLogger.count("note_fullscreen", 1);
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollapsingToShowActivityOverLockscreen() {
|
||||
return mIsCollapsingToShowActivityOverLockscreen;
|
||||
@@ -351,6 +413,14 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
|
||||
|| !mActivityLaunchAnimator.isAnimationPending();
|
||||
}
|
||||
|
||||
private boolean shouldSuppressFullScreenIntent(NotificationData.Entry entry) {
|
||||
if (mPresenter.isDeviceInVrMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return entry.shouldSuppressFullScreenIntent();
|
||||
}
|
||||
|
||||
private void removeNotification(StatusBarNotification notification) {
|
||||
// We have to post it to the UI thread for synchronization
|
||||
Dependency.get(MAIN_HANDLER).post(() -> {
|
||||
|
||||
@@ -57,7 +57,9 @@ import com.android.systemui.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.notification.AboveShelfObserver;
|
||||
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
|
||||
import com.android.systemui.statusbar.notification.NotificationData.Entry;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.notification.NotificationRowBinder;
|
||||
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
@@ -91,6 +93,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
|
||||
Dependency.get(NotificationEntryManager.class);
|
||||
private final NotificationRowBinder mNotificationRowBinder =
|
||||
Dependency.get(NotificationRowBinder.class);
|
||||
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
|
||||
Dependency.get(NotificationInterruptionStateProvider.class);
|
||||
private final NotificationMediaManager mMediaManager =
|
||||
Dependency.get(NotificationMediaManager.class);
|
||||
protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
|
||||
@@ -169,10 +173,37 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
|
||||
|
||||
NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller;
|
||||
Dependency.get(InitController.class).addPostInitTask(() -> {
|
||||
NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
|
||||
@Override
|
||||
public void onNotificationAdded(Entry entry) {
|
||||
// Recalculate the position of the sliding windows and the titles.
|
||||
mShadeController.updateAreThereNotifications();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationUpdated(StatusBarNotification notification) {
|
||||
mShadeController.updateAreThereNotifications();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRemoved(String key, StatusBarNotification old) {
|
||||
StatusBarNotificationPresenter.this.onNotificationRemoved(key, old);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformRemoveNotification(
|
||||
StatusBarNotification statusBarNotification) {
|
||||
StatusBarNotificationPresenter.this.onPerformRemoveNotification();
|
||||
}
|
||||
};
|
||||
|
||||
mViewHierarchyManager.setUpWithPresenter(this, notifListContainer);
|
||||
mEntryManager.setUpWithPresenter(this, notifListContainer, this, mHeadsUpManager);
|
||||
mEntryManager.setUpWithPresenter(
|
||||
this, notifListContainer, notificationEntryListener, mHeadsUpManager);
|
||||
mNotificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager,
|
||||
mEntryManager, this);
|
||||
mNotificationInterruptionStateProvider.setUpWithPresenter(
|
||||
this, mHeadsUpManager, this::canHeadsUp);
|
||||
mLockscreenUserManager.setUpWithPresenter(this);
|
||||
mMediaManager.setUpWithPresenter(this);
|
||||
Dependency.get(NotificationGutsManager.class).setUpWithPresenter(this,
|
||||
@@ -222,10 +253,9 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
|
||||
|| mActivityLaunchAnimator.isAnimationRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformRemoveNotification(StatusBarNotification n) {
|
||||
private void onPerformRemoveNotification() {
|
||||
if (mNotificationPanel.hasPulsingNotifications() &&
|
||||
!mAmbientPulseManager.hasNotifications()) {
|
||||
!mAmbientPulseManager.hasNotifications()) {
|
||||
// We were showing a pulse for a notification, but no notifications are pulsing anymore.
|
||||
// Finish the pulse.
|
||||
mDozeScrimController.pulseOutNow();
|
||||
@@ -249,18 +279,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
|
||||
mNotificationPanel.updateNotificationViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationAdded(Entry shadeEntry) {
|
||||
// Recalculate the position of the sliding windows and the titles.
|
||||
mShadeController.updateAreThereNotifications();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationUpdated(StatusBarNotification notification) {
|
||||
mShadeController.updateAreThereNotifications();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRemoved(String key, StatusBarNotification old) {
|
||||
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
|
||||
|
||||
@@ -282,7 +300,6 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
|
||||
return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHeadsUp(Entry entry, StatusBarNotification sbn) {
|
||||
if (mShadeController.isDozing()) {
|
||||
return false;
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.testing.TestableLooper;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.InitController;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
|
||||
@@ -55,7 +56,8 @@ import org.mockito.MockitoAnnotations;
|
||||
public class NonPhoneDependencyTest extends SysuiTestCase {
|
||||
@Mock private NotificationPresenter mPresenter;
|
||||
@Mock private NotificationListContainer mListContainer;
|
||||
@Mock private NotificationEntryManager.Callback mEntryManagerCallback;
|
||||
@Mock
|
||||
private NotificationEntryListener mEntryManagerCallback;
|
||||
@Mock private HeadsUpManager mHeadsUpManager;
|
||||
@Mock private RemoteInputController.Delegate mDelegate;
|
||||
@Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
|
||||
|
||||
@@ -29,8 +29,6 @@ import static junit.framework.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.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -50,7 +48,6 @@ import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.NotificationListenerService.Ranking;
|
||||
import android.service.notification.SnoozeCriterion;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.test.annotation.UiThreadTest;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
@@ -105,6 +102,7 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL);
|
||||
when(mMockStatusBarNotification.cloneLight()).thenReturn(mMockStatusBarNotification);
|
||||
|
||||
when(mMockPackageManager.checkUidPermission(
|
||||
eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
|
||||
@@ -128,41 +126,6 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
Dependency.get(InitController.class).executePostInitTasks();
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
public void testShowNotificationEvenIfUnprovisioned_FalseIfNoExtra() {
|
||||
initStatusBarNotification(false);
|
||||
when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP);
|
||||
|
||||
assertFalse(
|
||||
NotificationData.showNotificationEvenIfUnprovisioned(
|
||||
mMockPackageManager,
|
||||
mMockStatusBarNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
public void testShowNotificationEvenIfUnprovisioned_FalseIfNoPermission() {
|
||||
initStatusBarNotification(true);
|
||||
|
||||
assertFalse(
|
||||
NotificationData.showNotificationEvenIfUnprovisioned(
|
||||
mMockPackageManager,
|
||||
mMockStatusBarNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
public void testShowNotificationEvenIfUnprovisioned_TrueIfHasPermissionAndExtra() {
|
||||
initStatusBarNotification(true);
|
||||
when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP);
|
||||
|
||||
assertTrue(
|
||||
NotificationData.showNotificationEvenIfUnprovisioned(
|
||||
mMockPackageManager,
|
||||
mMockStatusBarNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChannelSetWhenAdded() {
|
||||
mNotificationData.add(mRow.getEntry());
|
||||
@@ -229,76 +192,6 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
.mActiveAppOps.contains(OP_ACCEPT_HANDOVER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuppressSystemAlertNotification() {
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
|
||||
when(mFsc.isSystemAlertNotification(any())).thenReturn(true);
|
||||
StatusBarNotification sbn = mRow.getEntry().notification;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {"something"});
|
||||
sbn.getNotification().extras = bundle;
|
||||
|
||||
assertTrue(mNotificationData.shouldFilterOut(mRow.getEntry()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotSuppressSystemAlertNotification() {
|
||||
StatusBarNotification sbn = mRow.getEntry().notification;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {"something"});
|
||||
sbn.getNotification().extras = bundle;
|
||||
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
|
||||
when(mFsc.isSystemAlertNotification(any())).thenReturn(true);
|
||||
|
||||
assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry()));
|
||||
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
|
||||
when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
|
||||
|
||||
assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry()));
|
||||
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
|
||||
when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
|
||||
|
||||
assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotSuppressMalformedSystemAlertNotification() {
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
|
||||
|
||||
// missing extra
|
||||
assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry()));
|
||||
|
||||
StatusBarNotification sbn = mRow.getEntry().notification;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {});
|
||||
sbn.getNotification().extras = bundle;
|
||||
|
||||
// extra missing values
|
||||
assertFalse(mNotificationData.shouldFilterOut(mRow.getEntry()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldFilterHiddenNotifications() {
|
||||
initStatusBarNotification(false);
|
||||
// setup
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
|
||||
when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
|
||||
|
||||
// test should filter out hidden notifications:
|
||||
// hidden
|
||||
when(mMockStatusBarNotification.getKey()).thenReturn(TEST_HIDDEN_NOTIFICATION_KEY);
|
||||
NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
|
||||
assertTrue(mNotificationData.shouldFilterOut(entry));
|
||||
|
||||
// not hidden
|
||||
when(mMockStatusBarNotification.getKey()).thenReturn("not hidden");
|
||||
entry = new NotificationData.Entry(mMockStatusBarNotification);
|
||||
assertFalse(mNotificationData.shouldFilterOut(entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications()
|
||||
throws Exception {
|
||||
@@ -325,9 +218,10 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
Notification n = mMockStatusBarNotification.getNotification();
|
||||
n.flags = Notification.FLAG_FOREGROUND_SERVICE;
|
||||
NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
|
||||
mNotificationData.add(entry);
|
||||
|
||||
assertTrue(mNotificationData.isExemptFromDndVisualSuppression(entry));
|
||||
assertFalse(mNotificationData.shouldSuppressAmbient(entry));
|
||||
assertTrue(entry.isExemptFromDndVisualSuppression());
|
||||
assertFalse(entry.shouldSuppressAmbient());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -341,9 +235,10 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
n = nb.build();
|
||||
when(mMockStatusBarNotification.getNotification()).thenReturn(n);
|
||||
NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
|
||||
mNotificationData.add(entry);
|
||||
|
||||
assertTrue(mNotificationData.isExemptFromDndVisualSuppression(entry));
|
||||
assertFalse(mNotificationData.shouldSuppressAmbient(entry));
|
||||
assertTrue(entry.isExemptFromDndVisualSuppression());
|
||||
assertFalse(entry.shouldSuppressAmbient());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -353,9 +248,10 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY);
|
||||
NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
|
||||
entry.mIsSystemNotification = true;
|
||||
mNotificationData.add(entry);
|
||||
|
||||
assertTrue(mNotificationData.isExemptFromDndVisualSuppression(entry));
|
||||
assertFalse(mNotificationData.shouldSuppressAmbient(entry));
|
||||
assertTrue(entry.isExemptFromDndVisualSuppression());
|
||||
assertFalse(entry.shouldSuppressAmbient());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -365,31 +261,33 @@ public class NotificationDataTest extends SysuiTestCase {
|
||||
TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY);
|
||||
NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
|
||||
entry.mIsSystemNotification = true;
|
||||
mNotificationData.add(entry);
|
||||
|
||||
when(mMockStatusBarNotification.getNotification()).thenReturn(
|
||||
new Notification.Builder(mContext, "").setCategory(CATEGORY_CALL).build());
|
||||
|
||||
assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry));
|
||||
assertTrue(mNotificationData.shouldSuppressAmbient(entry));
|
||||
assertFalse(entry.isExemptFromDndVisualSuppression());
|
||||
assertTrue(entry.shouldSuppressAmbient());
|
||||
|
||||
when(mMockStatusBarNotification.getNotification()).thenReturn(
|
||||
new Notification.Builder(mContext, "").setCategory(CATEGORY_REMINDER).build());
|
||||
|
||||
assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry));
|
||||
assertFalse(entry.isExemptFromDndVisualSuppression());
|
||||
|
||||
when(mMockStatusBarNotification.getNotification()).thenReturn(
|
||||
new Notification.Builder(mContext, "").setCategory(CATEGORY_ALARM).build());
|
||||
|
||||
assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry));
|
||||
assertFalse(entry.isExemptFromDndVisualSuppression());
|
||||
|
||||
when(mMockStatusBarNotification.getNotification()).thenReturn(
|
||||
new Notification.Builder(mContext, "").setCategory(CATEGORY_EVENT).build());
|
||||
|
||||
assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry));
|
||||
assertFalse(entry.isExemptFromDndVisualSuppression());
|
||||
|
||||
when(mMockStatusBarNotification.getNotification()).thenReturn(
|
||||
new Notification.Builder(mContext, "").setCategory(CATEGORY_MESSAGE).build());
|
||||
|
||||
assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry));
|
||||
assertFalse(entry.isExemptFromDndVisualSuppression());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -103,7 +103,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
@Mock private KeyguardEnvironment mEnvironment;
|
||||
@Mock private ExpandableNotificationRow mRow;
|
||||
@Mock private NotificationListContainer mListContainer;
|
||||
@Mock private NotificationEntryManager.Callback mCallback;
|
||||
@Mock
|
||||
private NotificationEntryListener mCallback;
|
||||
@Mock
|
||||
private NotificationRowBinder.BindRowCallback mBindCallback;
|
||||
@Mock private HeadsUpManager mHeadsUpManager;
|
||||
@@ -137,7 +138,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
|
||||
super(context);
|
||||
mBarService = barService;
|
||||
mCountDownLatch = new CountDownLatch(1);
|
||||
mUseHeadsUp = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Notification;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.test.annotation.UiThreadTest;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
|
||||
import com.android.systemui.ForegroundServiceController;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.statusbar.NotificationTestHelper;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.phone.NotificationGroupManager;
|
||||
import com.android.systemui.statusbar.phone.ShadeController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper
|
||||
public class NotificationFilterTest extends SysuiTestCase {
|
||||
|
||||
private static final int UID_NORMAL = 123;
|
||||
private static final int UID_ALLOW_DURING_SETUP = 456;
|
||||
private static final String TEST_HIDDEN_NOTIFICATION_KEY = "testHiddenNotificationKey";
|
||||
|
||||
private final StatusBarNotification mMockStatusBarNotification =
|
||||
mock(StatusBarNotification.class);
|
||||
|
||||
@Mock
|
||||
ForegroundServiceController mFsc;
|
||||
@Mock
|
||||
NotificationData.KeyguardEnvironment mEnvironment;
|
||||
private final IPackageManager mMockPackageManager = mock(IPackageManager.class);
|
||||
|
||||
private NotificationFilter mNotificationFilter;
|
||||
private ExpandableNotificationRow mRow;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL);
|
||||
|
||||
when(mMockPackageManager.checkUidPermission(
|
||||
eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
|
||||
eq(UID_NORMAL)))
|
||||
.thenReturn(PackageManager.PERMISSION_DENIED);
|
||||
when(mMockPackageManager.checkUidPermission(
|
||||
eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
|
||||
eq(UID_ALLOW_DURING_SETUP)))
|
||||
.thenReturn(PackageManager.PERMISSION_GRANTED);
|
||||
mDependency.injectTestDependency(ForegroundServiceController.class, mFsc);
|
||||
mDependency.injectTestDependency(NotificationGroupManager.class,
|
||||
new NotificationGroupManager());
|
||||
mDependency.injectMockDependency(ShadeController.class);
|
||||
mDependency.injectTestDependency(NotificationData.KeyguardEnvironment.class, mEnvironment);
|
||||
when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
|
||||
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
|
||||
mRow = new NotificationTestHelper(getContext()).createRow();
|
||||
mNotificationFilter = new NotificationFilter();
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
public void testShowNotificationEvenIfUnprovisioned_FalseIfNoExtra() {
|
||||
initStatusBarNotification(false);
|
||||
when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP);
|
||||
|
||||
assertFalse(
|
||||
NotificationFilter.showNotificationEvenIfUnprovisioned(
|
||||
mMockPackageManager,
|
||||
mMockStatusBarNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
public void testShowNotificationEvenIfUnprovisioned_FalseIfNoPermission() {
|
||||
initStatusBarNotification(true);
|
||||
|
||||
assertFalse(
|
||||
NotificationFilter.showNotificationEvenIfUnprovisioned(
|
||||
mMockPackageManager,
|
||||
mMockStatusBarNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
public void testShowNotificationEvenIfUnprovisioned_TrueIfHasPermissionAndExtra() {
|
||||
initStatusBarNotification(true);
|
||||
when(mMockStatusBarNotification.getUid()).thenReturn(UID_ALLOW_DURING_SETUP);
|
||||
|
||||
assertTrue(
|
||||
NotificationFilter.showNotificationEvenIfUnprovisioned(
|
||||
mMockPackageManager,
|
||||
mMockStatusBarNotification));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuppressSystemAlertNotification() {
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
|
||||
when(mFsc.isSystemAlertNotification(any())).thenReturn(true);
|
||||
StatusBarNotification sbn = mRow.getEntry().notification;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[]{"something"});
|
||||
sbn.getNotification().extras = bundle;
|
||||
|
||||
assertTrue(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotSuppressSystemAlertNotification() {
|
||||
StatusBarNotification sbn = mRow.getEntry().notification;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[]{"something"});
|
||||
sbn.getNotification().extras = bundle;
|
||||
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
|
||||
when(mFsc.isSystemAlertNotification(any())).thenReturn(true);
|
||||
|
||||
assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
|
||||
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
|
||||
when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
|
||||
|
||||
assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
|
||||
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
|
||||
when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
|
||||
|
||||
assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotSuppressMalformedSystemAlertNotification() {
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(true);
|
||||
|
||||
// missing extra
|
||||
assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
|
||||
|
||||
StatusBarNotification sbn = mRow.getEntry().notification;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[]{});
|
||||
sbn.getNotification().extras = bundle;
|
||||
|
||||
// extra missing values
|
||||
assertFalse(mNotificationFilter.shouldFilterOut(mRow.getEntry()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldFilterHiddenNotifications() {
|
||||
initStatusBarNotification(false);
|
||||
// setup
|
||||
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
|
||||
when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
|
||||
|
||||
// test should filter out hidden notifications:
|
||||
// hidden
|
||||
NotificationData.Entry entry = new NotificationData.Entry(mMockStatusBarNotification);
|
||||
entry.suspended = true;
|
||||
assertTrue(mNotificationFilter.shouldFilterOut(entry));
|
||||
|
||||
// not hidden
|
||||
entry = new NotificationData.Entry(mMockStatusBarNotification);
|
||||
entry.suspended = false;
|
||||
assertFalse(mNotificationFilter.shouldFilterOut(entry));
|
||||
}
|
||||
|
||||
private void initStatusBarNotification(boolean allowDuringSetup) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
|
||||
Notification notification = new Notification.Builder(mContext, "test")
|
||||
.addExtras(bundle)
|
||||
.build();
|
||||
when(mMockStatusBarNotification.getNotification()).thenReturn(notification);
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
|
||||
mPowerManager = new PowerManager(mContext, powerManagerService,
|
||||
Handler.createAsync(Looper.myLooper()));
|
||||
|
||||
mEntryManager = new TestableNotificationEntryManager(mDreamManager, mPowerManager,
|
||||
mEntryManager = new TestableNotificationEntryManager(mPowerManager,
|
||||
mContext);
|
||||
mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
|
||||
Dependency.get(InitController.class).executePostInitTasks();
|
||||
|
||||
@@ -32,9 +32,9 @@ import android.testing.TestableLooper;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.statusbar.AmbientPulseManager;
|
||||
import com.android.systemui.statusbar.notification.AlertTransferListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationData;
|
||||
import com.android.systemui.statusbar.notification.NotificationData.Entry;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.policy.HeadsUpManager;
|
||||
|
||||
@@ -61,8 +61,9 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
|
||||
private AmbientPulseManager mAmbientPulseManager;
|
||||
private HeadsUpManager mHeadsUpManager;
|
||||
@Mock private NotificationEntryManager mNotificationEntryManager;
|
||||
@Captor private ArgumentCaptor<AlertTransferListener> mListenerCaptor;
|
||||
private AlertTransferListener mAlertTransferListener;
|
||||
@Captor
|
||||
private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
|
||||
private NotificationEntryListener mNotificationEntryListener;
|
||||
private final HashMap<String, Entry> mPendingEntries = new HashMap<>();
|
||||
private final NotificationGroupTestHelper mGroupTestHelper =
|
||||
new NotificationGroupTestHelper(mContext);
|
||||
@@ -85,8 +86,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
|
||||
mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
|
||||
|
||||
mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager);
|
||||
verify(mNotificationEntryManager).setAlertTransferListener(mListenerCaptor.capture());
|
||||
mAlertTransferListener = mListenerCaptor.getValue();
|
||||
verify(mNotificationEntryManager).addNotificationEntryListener(mListenerCaptor.capture());
|
||||
mNotificationEntryListener = mListenerCaptor.getValue();
|
||||
mHeadsUpManager.addListener(mGroupAlertTransferHelper);
|
||||
mAmbientPulseManager.addListener(mGroupAlertTransferHelper);
|
||||
}
|
||||
@@ -121,7 +122,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
|
||||
|
||||
// Add second child notification so that summary is no longer suppressed.
|
||||
mPendingEntries.put(childEntry2.key, childEntry2);
|
||||
mAlertTransferListener.onPendingEntryAdded(childEntry2);
|
||||
mNotificationEntryListener.onPendingEntryAdded(childEntry2);
|
||||
mGroupManager.onEntryAdded(childEntry2);
|
||||
|
||||
// The alert state should transfer back to the summary as there is now more than one
|
||||
@@ -148,7 +149,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
|
||||
|
||||
// Add second child notification so that summary is no longer suppressed.
|
||||
mPendingEntries.put(childEntry2.key, childEntry2);
|
||||
mAlertTransferListener.onPendingEntryAdded(childEntry2);
|
||||
mNotificationEntryListener.onPendingEntryAdded(childEntry2);
|
||||
mGroupManager.onEntryAdded(childEntry2);
|
||||
|
||||
// Dozing changed so no reason to re-alert summary.
|
||||
@@ -186,7 +187,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
|
||||
|
||||
when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
|
||||
.thenReturn(true);
|
||||
mAlertTransferListener.onEntryReinflated(childEntry);
|
||||
mNotificationEntryListener.onEntryReinflated(childEntry);
|
||||
|
||||
// Alert is immediately removed from summary, and we show child as its content is inflated.
|
||||
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.key));
|
||||
@@ -210,13 +211,13 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
|
||||
|
||||
// Add second child notification so that summary is no longer suppressed.
|
||||
mPendingEntries.put(childEntry2.key, childEntry2);
|
||||
mAlertTransferListener.onPendingEntryAdded(childEntry2);
|
||||
mNotificationEntryListener.onPendingEntryAdded(childEntry2);
|
||||
mGroupManager.onEntryAdded(childEntry2);
|
||||
|
||||
// Child entry finishes its inflation.
|
||||
when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
|
||||
.thenReturn(true);
|
||||
mAlertTransferListener.onEntryReinflated(childEntry);
|
||||
mNotificationEntryListener.onEntryReinflated(childEntry);
|
||||
|
||||
verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager
|
||||
.getContentFlag());
|
||||
@@ -236,7 +237,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
|
||||
mGroupManager.onEntryAdded(summaryEntry);
|
||||
mGroupManager.onEntryAdded(childEntry);
|
||||
|
||||
mAlertTransferListener.onEntryRemoved(childEntry);
|
||||
mNotificationEntryListener.onEntryRemoved(childEntry);
|
||||
|
||||
assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.statusbar.phone;
|
||||
|
||||
import static android.app.NotificationManager.IMPORTANCE_HIGH;
|
||||
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
@@ -91,7 +92,10 @@ import com.android.systemui.statusbar.StatusBarState;
|
||||
import com.android.systemui.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.notification.NotificationData;
|
||||
import com.android.systemui.statusbar.notification.NotificationData.Entry;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationFilter;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.notification.VisualStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
|
||||
@@ -129,6 +133,8 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
@Mock private ArrayList<Entry> mNotificationList;
|
||||
@Mock private BiometricUnlockController mBiometricUnlockController;
|
||||
@Mock private NotificationData mNotificationData;
|
||||
@Mock
|
||||
private NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
|
||||
|
||||
// Mock dependencies:
|
||||
@Mock private NotificationViewHierarchyManager mViewHierarchyManager;
|
||||
@@ -141,13 +147,17 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
@Mock private StatusBarStateController mStatusBarStateController;
|
||||
@Mock private DeviceProvisionedController mDeviceProvisionedController;
|
||||
@Mock private NotificationPresenter mNotificationPresenter;
|
||||
@Mock private NotificationEntryManager.Callback mCallback;
|
||||
@Mock
|
||||
private NotificationEntryListener mCallback;
|
||||
@Mock private BubbleController mBubbleController;
|
||||
@Mock
|
||||
private NotificationFilter mNotificationFilter;
|
||||
|
||||
private TestableStatusBar mStatusBar;
|
||||
private FakeMetricsLogger mMetricsLogger;
|
||||
private PowerManager mPowerManager;
|
||||
private TestableNotificationEntryManager mEntryManager;
|
||||
private TestableNotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
|
||||
private NotificationLogger mNotificationLogger;
|
||||
private CommandQueue mCommandQueue;
|
||||
|
||||
@@ -168,6 +178,17 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
mDependency.injectTestDependency(DeviceProvisionedController.class,
|
||||
mDeviceProvisionedController);
|
||||
mDependency.injectMockDependency(BubbleController.class);
|
||||
mDependency.injectTestDependency(NotificationFilter.class, mNotificationFilter);
|
||||
|
||||
IPowerManager powerManagerService = mock(IPowerManager.class);
|
||||
mPowerManager = new PowerManager(mContext, powerManagerService,
|
||||
Handler.createAsync(Looper.myLooper()));
|
||||
|
||||
mNotificationInterruptionStateProvider =
|
||||
new TestableNotificationInterruptionStateProvider(mContext, mPowerManager,
|
||||
mDreamManager);
|
||||
mDependency.injectTestDependency(NotificationInterruptionStateProvider.class,
|
||||
mNotificationInterruptionStateProvider);
|
||||
|
||||
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
|
||||
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
|
||||
@@ -178,10 +199,6 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
mNotificationLogger = new NotificationLogger();
|
||||
DozeLog.traceDozing(mContext, false /* dozing */);
|
||||
|
||||
IPowerManager powerManagerService = mock(IPowerManager.class);
|
||||
mPowerManager = new PowerManager(mContext, powerManagerService,
|
||||
Handler.createAsync(Looper.myLooper()));
|
||||
|
||||
mCommandQueue = mock(CommandQueue.class);
|
||||
when(mCommandQueue.asBinder()).thenReturn(new Binder());
|
||||
mContext.putComponent(CommandQueue.class, mCommandQueue);
|
||||
@@ -205,7 +222,10 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
return null;
|
||||
}).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
|
||||
|
||||
mEntryManager = new TestableNotificationEntryManager(mDreamManager, mPowerManager, mContext);
|
||||
mNotificationInterruptionStateProvider.setUpWithPresenter(mNotificationPresenter,
|
||||
mHeadsUpManager, mHeadsUpSuppressor);
|
||||
|
||||
mEntryManager = new TestableNotificationEntryManager(mPowerManager, mContext);
|
||||
when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
|
||||
mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
|
||||
mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
|
||||
@@ -362,11 +382,9 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception {
|
||||
when(mPowerManager.isScreenOn()).thenReturn(true);
|
||||
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
|
||||
when(mNotificationData.shouldSuppressStatusBar(any())).thenReturn(false);
|
||||
when(mNotificationData.shouldFilterOut(any())).thenReturn(false);
|
||||
when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
|
||||
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||
when(mNotificationData.getImportance(any())).thenReturn(IMPORTANCE_HIGH);
|
||||
when(mCallback.canHeadsUp(any(), any())).thenReturn(true);
|
||||
when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
|
||||
|
||||
Notification n = new Notification.Builder(getContext(), "a")
|
||||
.setGroup("a")
|
||||
@@ -376,19 +394,18 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
|
||||
UserHandle.of(0), null, 0);
|
||||
NotificationData.Entry entry = new NotificationData.Entry(sbn);
|
||||
entry.importance = IMPORTANCE_HIGH;
|
||||
|
||||
assertTrue(mEntryManager.shouldHeadsUp(entry));
|
||||
assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldHeadsUp_suppressedGroupSummary() throws Exception {
|
||||
when(mPowerManager.isScreenOn()).thenReturn(true);
|
||||
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
|
||||
when(mNotificationData.shouldSuppressStatusBar(any())).thenReturn(false);
|
||||
when(mNotificationData.shouldFilterOut(any())).thenReturn(false);
|
||||
when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
|
||||
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||
when(mNotificationData.getImportance(any())).thenReturn(IMPORTANCE_HIGH);
|
||||
when(mCallback.canHeadsUp(any(), any())).thenReturn(true);
|
||||
when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
|
||||
|
||||
Notification n = new Notification.Builder(getContext(), "a")
|
||||
.setGroup("a")
|
||||
@@ -398,46 +415,44 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
|
||||
UserHandle.of(0), null, 0);
|
||||
NotificationData.Entry entry = new NotificationData.Entry(sbn);
|
||||
entry.importance = IMPORTANCE_HIGH;
|
||||
|
||||
assertFalse(mEntryManager.shouldHeadsUp(entry));
|
||||
assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldHeadsUp_suppressedHeadsUp() throws Exception {
|
||||
when(mPowerManager.isScreenOn()).thenReturn(true);
|
||||
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
|
||||
when(mNotificationData.shouldFilterOut(any())).thenReturn(false);
|
||||
when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
|
||||
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||
when(mNotificationData.getImportance(any())).thenReturn(IMPORTANCE_HIGH);
|
||||
when(mCallback.canHeadsUp(any(), any())).thenReturn(true);
|
||||
|
||||
when(mNotificationData.shouldSuppressPeek(any())).thenReturn(true);
|
||||
when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
|
||||
|
||||
Notification n = new Notification.Builder(getContext(), "a").build();
|
||||
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
|
||||
UserHandle.of(0), null, 0);
|
||||
NotificationData.Entry entry = new NotificationData.Entry(sbn);
|
||||
entry.suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK;
|
||||
entry.importance = IMPORTANCE_HIGH;
|
||||
|
||||
assertFalse(mEntryManager.shouldHeadsUp(entry));
|
||||
assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception {
|
||||
when(mPowerManager.isScreenOn()).thenReturn(true);
|
||||
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
|
||||
when(mNotificationData.shouldFilterOut(any())).thenReturn(false);
|
||||
when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
|
||||
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||
when(mNotificationData.getImportance(any())).thenReturn(IMPORTANCE_HIGH);
|
||||
when(mCallback.canHeadsUp(any(), any())).thenReturn(true);
|
||||
|
||||
when(mNotificationData.shouldSuppressPeek(any())).thenReturn(false);
|
||||
when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
|
||||
|
||||
Notification n = new Notification.Builder(getContext(), "a").build();
|
||||
StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
|
||||
UserHandle.of(0), null, 0);
|
||||
NotificationData.Entry entry = new NotificationData.Entry(sbn);
|
||||
entry.importance = IMPORTANCE_HIGH;
|
||||
|
||||
assertTrue(mEntryManager.shouldHeadsUp(entry));
|
||||
assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -725,20 +740,29 @@ public class StatusBarTest extends SysuiTestCase {
|
||||
|
||||
public static class TestableNotificationEntryManager extends NotificationEntryManager {
|
||||
|
||||
public TestableNotificationEntryManager(IDreamManager dreamManager,
|
||||
PowerManager powerManager, Context context) {
|
||||
public TestableNotificationEntryManager(PowerManager powerManager, Context context) {
|
||||
super(context);
|
||||
mDreamManager = dreamManager;
|
||||
mPowerManager = powerManager;
|
||||
}
|
||||
|
||||
public void setUpForTest(NotificationPresenter presenter,
|
||||
NotificationListContainer listContainer,
|
||||
Callback callback,
|
||||
NotificationEntryListener callback,
|
||||
HeadsUpManagerPhone headsUpManager,
|
||||
NotificationData notificationData) {
|
||||
super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager);
|
||||
mNotificationData = notificationData;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestableNotificationInterruptionStateProvider extends
|
||||
NotificationInterruptionStateProvider {
|
||||
|
||||
public TestableNotificationInterruptionStateProvider(
|
||||
Context context,
|
||||
PowerManager powerManager,
|
||||
IDreamManager dreamManager) {
|
||||
super(context, powerManager, dreamManager);
|
||||
mUseHeadsUp = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user