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:
TreeHugger Robot
2018-12-20 18:18:32 +00:00
committed by Android (Google) Code Review
26 changed files with 1157 additions and 799 deletions

View File

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

View File

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

View File

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

View File

@@ -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,

View File

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

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(

View File

@@ -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

View File

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

View File

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

View File

@@ -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(() -> {

View File

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

View File

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

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View File

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

View File

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