summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Eliot Courtney <edcourtney@google.com> 2017-10-20 13:26:58 +0900
committer Eliot Courtney <edcourtney@google.com> 2017-12-26 15:33:31 +0900
commita6d8cf294dfb587f130fdecc5e6897e75de7bf45 (patch)
tree70a783141d024bb7d6552fc8c9f6d336d1d0447b
parent9f5ceae640351fddb3e0c19d7c39729a44de901b (diff)
Split NotificationEntryManager out of StatusBar.
NotificationEntryManager is responsible for the adding, removing, and updating of notifications among other things, such as their inflation and their interaction with other Notification*Manager objects. Bug: 63874929 Bug: 62602530 Test: runtest systemui Test: Compile and run Change-Id: I56f8c524875900112cdf9f6120407b61e201172f
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java966
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java1185
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java254
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java85
19 files changed, 1756 insertions, 1041 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 3177c03d1afa..c32e08946621 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -23,16 +23,19 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.Dependency.DependencyProvider;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationEntryManager;
import com.android.systemui.statusbar.NotificationGutsManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -46,6 +49,7 @@ import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import java.util.function.Consumer;
@@ -119,6 +123,7 @@ public class SystemUIFactory {
providers.put(NotificationLockscreenUserManager.class,
() -> new NotificationLockscreenUserManager(context));
providers.put(NotificationGroupManager.class, NotificationGroupManager::new);
+ providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context));
providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(
Dependency.get(NotificationLockscreenUserManager.class), context));
providers.put(NotificationRemoteInputManager.class,
@@ -129,5 +134,20 @@ public class SystemUIFactory {
providers.put(NotificationLogger.class, () -> new NotificationLogger(
Dependency.get(NotificationListener.class),
Dependency.get(UiOffloadThread.class)));
+ providers.put(NotificationEntryManager.class, () ->
+ new NotificationEntryManager(
+ Dependency.get(NotificationLockscreenUserManager.class),
+ Dependency.get(NotificationGroupManager.class),
+ Dependency.get(NotificationGutsManager.class),
+ Dependency.get(NotificationRemoteInputManager.class),
+ Dependency.get(NotificationMediaManager.class),
+ Dependency.get(ForegroundServiceController.class),
+ Dependency.get(NotificationListener.class),
+ Dependency.get(MetricsLogger.class),
+ Dependency.get(DeviceProvisionedController.class),
+ Dependency.get(UiOffloadThread.class),
+ context));
+ providers.put(NotificationListener.class, () -> new NotificationListener(
+ Dependency.get(NotificationRemoteInputManager.class), context));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
new file mode 100644
index 000000000000..37172b63caae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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 android.service.notification.StatusBarNotification;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+public class CarNotificationEntryManager extends NotificationEntryManager {
+ public CarNotificationEntryManager(
+ NotificationLockscreenUserManager lockscreenUserManager,
+ NotificationGroupManager groupManager,
+ NotificationGutsManager gutsManager,
+ NotificationRemoteInputManager remoteInputManager,
+ NotificationMediaManager mediaManager,
+ ForegroundServiceController foregroundServiceController,
+ NotificationListener notificationListener,
+ MetricsLogger metricsLogger,
+ DeviceProvisionedController deviceProvisionedController,
+ UiOffloadThread uiOffloadThread, Context context) {
+ super(lockscreenUserManager, groupManager, gutsManager, remoteInputManager, mediaManager,
+ foregroundServiceController, notificationListener, metricsLogger,
+ deviceProvisionedController, uiOffloadThread, context);
+ }
+
+ /**
+ * Returns the
+ * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
+ * be triggered when a notification card is long-pressed.
+ */
+ @Override
+ public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+ // For the automative use case, we do not want to the user to be able to interact with
+ // a notification other than a regular click. As a result, just return null for the
+ // long click listener.
+ return null;
+ }
+
+ @Override
+ public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+ // 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 (!mPresenter.isPresenterFullyCollapsed()) {
+ return false;
+ }
+
+ return super.shouldPeek(entry, sbn);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
index 5a19e7dc60ed..24f8b97d539d 100644
--- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java
@@ -18,9 +18,21 @@ package com.android.systemui.car;
import android.content.Context;
import android.util.ArrayMap;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
import com.android.systemui.Dependency.DependencyProvider;
+import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SystemUIFactory;
+import com.android.systemui.UiOffloadThread;
import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.volume.car.CarVolumeDialogController;
/**
@@ -32,5 +44,17 @@ public class CarSystemUIFactory extends SystemUIFactory {
Context context) {
super.injectDependencies(providers, context);
providers.put(VolumeDialogController.class, () -> new CarVolumeDialogController(context));
+ providers.put(NotificationEntryManager.class, () -> new CarNotificationEntryManager(
+ Dependency.get(NotificationLockscreenUserManager.class),
+ Dependency.get(NotificationGroupManager.class),
+ Dependency.get(NotificationGutsManager.class),
+ Dependency.get(NotificationRemoteInputManager.class),
+ Dependency.get(NotificationMediaManager.class),
+ Dependency.get(ForegroundServiceController.class),
+ Dependency.get(NotificationListener.class),
+ Dependency.get(MetricsLogger.class),
+ Dependency.get(DeviceProvisionedController.class),
+ Dependency.get(UiOffloadThread.class),
+ context));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
new file mode 100644
index 000000000000..b60d2ac8ebf4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
@@ -0,0 +1,966 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
+import static com.android.systemui.statusbar.NotificationRemoteInputManager
+ .FORCE_REMOTE_INPUT_HISTORY;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.os.Build;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.util.NotificationMessagingUtil;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.EventLogTags;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.R;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.NotificationInflater;
+import com.android.systemui.statusbar.notification.RowInflaterTask;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import com.android.systemui.util.leak.LeakDetector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
+ * It also handles tasks such as their inflation and their interaction with other
+ * Notification.*Manager objects.
+ */
+public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
+ ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler {
+ private static final String TAG = "NotificationEntryManager";
+ protected static final boolean DEBUG = false;
+ protected static final boolean ENABLE_HEADS_UP = true;
+ protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
+ protected final NotificationMessagingUtil mMessagingUtil;
+ protected final Context mContext;
+ protected final NotificationLockscreenUserManager mLockscreenUserManager;
+ protected final NotificationGroupManager mGroupManager;
+ protected final NotificationGutsManager mGutsManager;
+ protected final NotificationRemoteInputManager mRemoteInputManager;
+ protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
+ protected final NotificationMediaManager mMediaManager;
+ protected final MetricsLogger mMetricsLogger;
+ protected final DeviceProvisionedController mDeviceProvisionedController;
+ protected final UiOffloadThread mUiOffloadThread;
+ protected final ForegroundServiceController mForegroundServiceController;
+ protected final NotificationListener mNotificationListener;
+ protected final NotificationClicker mNotificationClicker = new NotificationClicker();
+ protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch =
+ new ArraySet<>();
+
+ protected IStatusBarService mBarService;
+ protected NotificationPresenter mPresenter;
+ protected Callback mCallback;
+ protected NotificationStackScrollLayout mStackScroller;
+ protected PowerManager mPowerManager;
+ protected SystemServicesProxy mSystemServicesProxy;
+ protected NotificationListenerService.RankingMap mLatestRankingMap;
+ protected HeadsUpManager mHeadsUpManager;
+ protected NotificationData mNotificationData;
+ protected ContentObserver mHeadsUpObserver;
+ protected boolean mUseHeadsUp = false;
+ protected boolean mDisableNotificationAlerts;
+ protected VisualStabilityManager mVisualStabilityManager;
+
+ private final class NotificationClicker implements View.OnClickListener {
+
+ @Override
+ public void onClick(final View v) {
+ if (!(v instanceof ExpandableNotificationRow)) {
+ Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
+ return;
+ }
+
+ mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v);
+
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ final StatusBarNotification sbn = row.getStatusBarNotification();
+ if (sbn == null) {
+ Log.e(TAG, "NotificationClicker called on an unclickable notification,");
+ return;
+ }
+
+ // Check if the notification is displaying the menu, if so slide notification back
+ if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
+ row.animateTranslateNotification(0);
+ return;
+ }
+
+ // Mark notification for one frame.
+ row.setJustClicked(true);
+ DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
+
+ mCallback.onNotificationClicked(sbn, row);
+ }
+
+ public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
+ Notification notification = sbn.getNotification();
+ if (notification.contentIntent != null || notification.fullScreenIntent != null) {
+ row.setOnClickListener(this);
+ } else {
+ row.setOnClickListener(null);
+ }
+ }
+ }
+
+ private final DeviceProvisionedController.DeviceProvisionedListener
+ mDeviceProvisionedListener =
+ new DeviceProvisionedController.DeviceProvisionedListener() {
+ @Override
+ public void onDeviceProvisionedChanged() {
+ updateNotifications();
+ }
+ };
+
+ public NotificationListenerService.RankingMap getLatestRankingMap() {
+ return mLatestRankingMap;
+ }
+
+ public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) {
+ mLatestRankingMap = latestRankingMap;
+ }
+
+ public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
+ mDisableNotificationAlerts = disableNotificationAlerts;
+ mHeadsUpObserver.onChange(true);
+ }
+
+ public void destroy() {
+ mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+ }
+
+ public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+ removeNotification(entry.key, getLatestRankingMap());
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+ if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
+ setLatestRankingMap(null);
+ }
+ } else {
+ updateNotificationRanking(null);
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("NotificationEntryManager state:");
+ pw.print(" mPendingNotifications=");
+ if (mPendingNotifications.size() == 0) {
+ pw.println("null");
+ } else {
+ for (NotificationData.Entry entry : mPendingNotifications.values()) {
+ pw.println(entry.notification);
+ }
+ }
+ pw.print(" mUseHeadsUp=");
+ pw.println(mUseHeadsUp);
+ }
+
+ public NotificationEntryManager(NotificationLockscreenUserManager lockscreenUserManager,
+ NotificationGroupManager groupManager,
+ NotificationGutsManager gutsManager,
+ NotificationRemoteInputManager remoteInputManager,
+ NotificationMediaManager mediaManager,
+ ForegroundServiceController foregroundServiceController,
+ NotificationListener notificationListener,
+ MetricsLogger metricsLogger,
+ DeviceProvisionedController deviceProvisionedController,
+ UiOffloadThread uiOffloadThread, Context context) {
+ mLockscreenUserManager = lockscreenUserManager;
+ mGroupManager = groupManager;
+ mGutsManager = gutsManager;
+ mRemoteInputManager = remoteInputManager;
+ mMediaManager = mediaManager;
+ mForegroundServiceController = foregroundServiceController;
+ mNotificationListener = notificationListener;
+ mMetricsLogger = metricsLogger;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mUiOffloadThread = uiOffloadThread;
+ mContext = context;
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mMessagingUtil = new NotificationMessagingUtil(context);
+ mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
+ }
+
+ // TODO: Remove dependency on NotificationStackScrollLayout
+ public void setUpWithPresenter(NotificationPresenter presenter,
+ NotificationStackScrollLayout stackScroller,
+ Callback callback,
+ VisualStabilityManager visualStabilityManager,
+ HeadsUpManager headsUpManager) {
+ mPresenter = presenter;
+ mCallback = callback;
+ mStackScroller = stackScroller;
+ mVisualStabilityManager = visualStabilityManager;
+ mNotificationData = new NotificationData(presenter);
+ mHeadsUpManager = headsUpManager;
+ mNotificationData.setHeadsUpManager(mHeadsUpManager);
+
+ mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) {
+ @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);
+ }
+
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
+ mHeadsUpObserver.onChange(true); // set up
+ }
+
+ public NotificationData getNotificationData() {
+ return mNotificationData;
+ }
+
+ public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+ return mGutsManager::openGuts;
+ }
+
+ @Override
+ public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onNotificationExpansionChanged(key, userAction, expanded);
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+ });
+ }
+
+ private boolean shouldSuppressFullScreenIntent(String key) {
+ if (mPresenter.isDeviceInVrMode()) {
+ return true;
+ }
+
+ if (mPowerManager.isInteractive()) {
+ return mNotificationData.shouldSuppressScreenOn(key);
+ } else {
+ return mNotificationData.shouldSuppressScreenOff(key);
+ }
+ }
+
+ private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+ entry.notification.getUser().getIdentifier());
+
+ final StatusBarNotification sbn = entry.notification;
+ if (entry.row != null) {
+ entry.reset();
+ updateNotification(entry, pmUser, sbn, entry.row);
+ } else {
+ new RowInflaterTask().inflate(mContext, parent, entry,
+ row -> {
+ bindRow(entry, pmUser, sbn, row);
+ updateNotification(entry, pmUser, sbn, row);
+ });
+ }
+ }
+
+ private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
+ row.setExpansionLogger(this, entry.notification.getKey());
+ row.setGroupManager(mGroupManager);
+ row.setHeadsUpManager(mHeadsUpManager);
+ row.setOnExpandClickListener(mPresenter);
+ row.setInflationCallback(this);
+ row.setLongPressListener(getNotificationLongClicker());
+ mRemoteInputManager.bindRow(row);
+
+ // Get the app name.
+ // Note that Notification.Builder#bindHeaderAppName has similar logic
+ // but since this field is used in the guts, it must be accurate.
+ // Therefore we will only show the application label, or, failing that, the
+ // package name. No substitutions.
+ final String pkg = sbn.getPackageName();
+ String appname = pkg;
+ try {
+ final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS);
+ if (info != null) {
+ appname = String.valueOf(pmUser.getApplicationLabel(info));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Do nothing
+ }
+ row.setAppName(appname);
+ row.setOnDismissRunnable(() ->
+ performRemoveNotification(row.getStatusBarNotification()));
+ row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ if (ENABLE_REMOTE_INPUT) {
+ row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ }
+
+ mCallback.onBindRow(entry, pmUser, sbn, row);
+ }
+
+ public void performRemoveNotification(StatusBarNotification n) {
+ NotificationData.Entry entry = mNotificationData.get(n.getKey());
+ mRemoteInputManager.onPerformRemoveNotification(n, entry);
+ final String pkg = n.getPackageName();
+ final String tag = n.getTag();
+ final int id = n.getId();
+ final int userId = n.getUserId();
+ try {
+ int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+ if (isHeadsUp(n.getKey())) {
+ dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+ } else if (mStackScroller.hasPulsingNotifications()) {
+ dismissalSurface = NotificationStats.DISMISSAL_AOD;
+ }
+ mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
+ removeNotification(n.getKey(), null);
+
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+
+ mCallback.onPerformRemoveNotification(n);
+ }
+
+ /**
+ * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
+ * about the failure.
+ *
+ * WARNING: this will call back into us. Don't hold any locks.
+ */
+ void handleNotificationError(StatusBarNotification n, String message) {
+ removeNotification(n.getKey(), null);
+ try {
+ mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
+ n.getInitialPid(), message, n.getUserId());
+ } catch (RemoteException ex) {
+ // The end is nigh.
+ }
+ }
+
+ private void abortExistingInflation(String key) {
+ if (mPendingNotifications.containsKey(key)) {
+ NotificationData.Entry entry = mPendingNotifications.get(key);
+ entry.abortTask();
+ mPendingNotifications.remove(key);
+ }
+ NotificationData.Entry addedEntry = mNotificationData.get(key);
+ if (addedEntry != null) {
+ addedEntry.abortTask();
+ }
+ }
+
+ @Override
+ public void handleInflationException(StatusBarNotification notification, Exception e) {
+ handleNotificationError(notification, e.getMessage());
+ }
+
+ private void addEntry(NotificationData.Entry shadeEntry) {
+ boolean isHeadsUped = shouldPeek(shadeEntry);
+ if (isHeadsUped) {
+ mHeadsUpManager.showNotification(shadeEntry);
+ // Mark as seen immediately
+ setNotificationShown(shadeEntry.notification);
+ }
+ addNotificationViews(shadeEntry);
+ mCallback.onNotificationAdded(shadeEntry);
+ }
+
+ @Override
+ public void onAsyncInflationFinished(NotificationData.Entry entry) {
+ mPendingNotifications.remove(entry.key);
+ // If there was an async task started after the removal, we don't want to add it back to
+ // the list, otherwise we might get leaks.
+ boolean isNew = mNotificationData.get(entry.key) == null;
+ if (isNew && !entry.row.isRemoved()) {
+ addEntry(entry);
+ } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
+ mVisualStabilityManager.onLowPriorityUpdated(entry);
+ mPresenter.updateNotificationViews();
+ }
+ entry.row.setLowPriorityStateUpdated(false);
+ }
+
+ @Override
+ public void removeNotification(String key, NotificationListenerService.RankingMap ranking) {
+ boolean deferRemoval = false;
+ abortExistingInflation(key);
+ if (mHeadsUpManager.isHeadsUp(key)) {
+ // A cancel() in response to a remote input shouldn't be delayed, as it makes the
+ // sending look longer than it takes.
+ // Also we should not defer the removal if reordering isn't allowed since otherwise
+ // some notifications can't disappear before the panel is closed.
+ boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
+ && !FORCE_REMOTE_INPUT_HISTORY
+ || !mVisualStabilityManager.isReorderingAllowed();
+ deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
+ }
+ mMediaManager.onNotificationRemoved(key);
+
+ NotificationData.Entry entry = mNotificationData.get(key);
+ if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key)
+ && entry.row != null && !entry.row.isDismissed()) {
+ StatusBarNotification sbn = entry.notification;
+
+ Notification.Builder b = Notification.Builder
+ .recoverBuilder(mContext, sbn.getNotification().clone());
+ CharSequence[] oldHistory = sbn.getNotification().extras
+ .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
+ CharSequence[] newHistory;
+ if (oldHistory == null) {
+ newHistory = new CharSequence[1];
+ } else {
+ newHistory = new CharSequence[oldHistory.length + 1];
+ System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
+ }
+ newHistory[0] = String.valueOf(entry.remoteInputText);
+ b.setRemoteInputHistory(newHistory);
+
+ Notification newNotification = b.build();
+
+ // Undo any compatibility view inflation
+ newNotification.contentView = sbn.getNotification().contentView;
+ newNotification.bigContentView = sbn.getNotification().bigContentView;
+ newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
+
+ StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
+ sbn.getOpPkg(),
+ sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
+ newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
+ boolean updated = false;
+ try {
+ updateNotificationInternal(newSbn, null);
+ updated = true;
+ } catch (InflationException e) {
+ deferRemoval = false;
+ }
+ if (updated) {
+ Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
+ mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
+ return;
+ }
+ }
+ if (deferRemoval) {
+ mLatestRankingMap = ranking;
+ mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
+ return;
+ }
+
+ if (mRemoteInputManager.onRemoveNotification(entry)) {
+ mLatestRankingMap = ranking;
+ return;
+ }
+
+ if (entry != null && mGutsManager.getExposedGuts() != null
+ && mGutsManager.getExposedGuts() == entry.row.getGuts()
+ && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
+ Log.w(TAG, "Keeping notification because it's showing guts. " + key);
+ mLatestRankingMap = ranking;
+ mGutsManager.setKeyToRemoveOnGutsClosed(key);
+ return;
+ }
+
+ if (entry != null) {
+ mForegroundServiceController.removeNotification(entry.notification);
+ }
+
+ if (entry != null && entry.row != null) {
+ entry.row.setRemoved();
+ mStackScroller.cleanUpViewState(entry.row);
+ }
+ // Let's remove the children if this was a summary
+ handleGroupSummaryRemoved(key);
+ StatusBarNotification old = removeNotificationViews(key, ranking);
+
+ mCallback.onNotificationRemoved(key, old);
+ }
+
+ private StatusBarNotification removeNotificationViews(String key,
+ NotificationListenerService.RankingMap ranking) {
+ NotificationData.Entry entry = mNotificationData.remove(key, ranking);
+ if (entry == null) {
+ Log.w(TAG, "removeNotification for unknown key: " + key);
+ return null;
+ }
+ updateNotifications();
+ Dependency.get(LeakDetector.class).trackGarbage(entry);
+ return entry.notification;
+ }
+
+ /**
+ * Ensures that the group children are cancelled immediately when the group summary is cancelled
+ * instead of waiting for the notification manager to send all cancels. Otherwise this could
+ * lead to flickers.
+ *
+ * This also ensures that the animation looks nice and only consists of a single disappear
+ * animation instead of multiple.
+ * @param key the key of the notification was removed
+ *
+ */
+ private void handleGroupSummaryRemoved(String key) {
+ NotificationData.Entry entry = mNotificationData.get(key);
+ if (entry != null && entry.row != null
+ && entry.row.isSummaryWithChildren()) {
+ if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
+ // We don't want to remove children for autobundled notifications as they are not
+ // always cancelled. We only remove them if they were dismissed by the user.
+ return;
+ }
+ List<ExpandableNotificationRow> notificationChildren =
+ entry.row.getNotificationChildren();
+ for (int i = 0; i < notificationChildren.size(); i++) {
+ ExpandableNotificationRow row = notificationChildren.get(i);
+ if ((row.getStatusBarNotification().getNotification().flags
+ & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ // the child is a foreground service notification which we can't remove!
+ continue;
+ }
+ row.setKeepInParent(true);
+ // we need to set this state earlier as otherwise we might generate some weird
+ // animations
+ row.setRemoved();
+ }
+ }
+ }
+
+ public void updateNotificationsOnDensityOrFontScaleChanged() {
+ ArrayList<NotificationData.Entry> activeNotifications =
+ mNotificationData.getActiveNotifications();
+ for (int i = 0; i < activeNotifications.size(); i++) {
+ NotificationData.Entry entry = activeNotifications.get(i);
+ boolean exposedGuts = mGutsManager.getExposedGuts() != null
+ && entry.row.getGuts() == mGutsManager.getExposedGuts();
+ entry.row.onDensityOrFontScaleChanged();
+ if (exposedGuts) {
+ mGutsManager.setExposedGuts(entry.row.getGuts());
+ mGutsManager.bindGuts(entry.row);
+ }
+ }
+ }
+
+ private void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
+ row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
+ boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
+ boolean isUpdate = mNotificationData.get(entry.key) != null;
+ boolean wasLowPriority = row.isLowPriority();
+ row.setIsLowPriority(isLowPriority);
+ row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
+ // bind the click event to the content area
+ mNotificationClicker.register(row, sbn);
+
+ // Extract target SDK version.
+ try {
+ ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
+ entry.targetSdk = info.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException ex) {
+ Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
+ }
+ row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+ && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+ entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+ entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
+
+ entry.row = row;
+ entry.row.setOnActivatedListener(mPresenter);
+
+ boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
+ mNotificationData.getImportance(sbn.getKey()));
+ boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
+ && !mPresenter.isPresenterFullyCollapsed();
+ row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+ row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+ row.updateNotification(entry);
+ }
+
+
+ protected void addNotificationViews(NotificationData.Entry entry) {
+ if (entry == null) {
+ return;
+ }
+ // Add the expanded view and icon.
+ mNotificationData.add(entry);
+ updateNotifications();
+ }
+
+ protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
+ throws InflationException {
+ if (DEBUG) {
+ Log.d(TAG, "createNotificationViews(notification=" + sbn);
+ }
+ NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ Dependency.get(LeakDetector.class).trackInstance(entry);
+ entry.createIcons(mContext, sbn);
+ // Construct the expanded view.
+ inflateViews(entry, mStackScroller);
+ return entry;
+ }
+
+ private void addNotificationInternal(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) throws InflationException {
+ String key = notification.getKey();
+ if (DEBUG) Log.d(TAG, "addNotification key=" + key);
+
+ mNotificationData.updateRanking(ranking);
+ NotificationData.Entry shadeEntry = createNotificationViews(notification);
+ boolean isHeadsUped = shouldPeek(shadeEntry);
+ if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
+ if (shouldSuppressFullScreenIntent(key)) {
+ 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)
+ SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+
+ // 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) {
+ }
+ }
+ }
+ abortExistingInflation(key);
+
+ mForegroundServiceController.addNotification(notification,
+ mNotificationData.getImportance(key));
+
+ mPendingNotifications.put(key, shadeEntry);
+ }
+
+ @Override
+ public void addNotification(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) {
+ try {
+ addNotificationInternal(notification, ranking);
+ } catch (InflationException e) {
+ handleInflationException(notification, e);
+ }
+ }
+
+ private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) {
+ return oldEntry == null || !oldEntry.hasInterrupted()
+ || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
+ }
+
+ private void updateNotificationInternal(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) throws InflationException {
+ if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
+
+ final String key = notification.getKey();
+ abortExistingInflation(key);
+ NotificationData.Entry entry = mNotificationData.get(key);
+ if (entry == null) {
+ return;
+ }
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+ mRemoteInputManager.onUpdateNotification(entry);
+
+ if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
+ mGutsManager.setKeyToRemoveOnGutsClosed(null);
+ Log.w(TAG, "Notification that was kept for guts was updated. " + key);
+ }
+
+ Notification n = notification.getNotification();
+ mNotificationData.updateRanking(ranking);
+
+ final StatusBarNotification oldNotification = entry.notification;
+ entry.notification = notification;
+ mGroupManager.onEntryUpdated(entry, oldNotification);
+
+ entry.updateIcons(mContext, notification);
+ inflateViews(entry, mStackScroller);
+
+ mForegroundServiceController.updateNotification(notification,
+ mNotificationData.getImportance(key));
+
+ boolean shouldPeek = shouldPeek(entry, notification);
+ boolean alertAgain = alertAgain(entry, n);
+
+ updateHeadsUp(key, entry, shouldPeek, alertAgain);
+ updateNotifications();
+
+ if (!notification.isClearable()) {
+ // The user may have performed a dismiss action on the notification, since it's
+ // not clearable we should snap it back.
+ mStackScroller.snapViewIfNeeded(entry.row);
+ }
+
+ if (DEBUG) {
+ // Is this for you?
+ boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification);
+ Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
+ }
+
+ mCallback.onNotificationUpdated(notification);
+ }
+
+ @Override
+ public void updateNotification(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking) {
+ try {
+ updateNotificationInternal(notification, ranking);
+ } catch (InflationException e) {
+ handleInflationException(notification, e);
+ }
+ }
+
+ public void updateNotifications() {
+ mNotificationData.filterAndSort();
+
+ mPresenter.updateNotificationViews();
+ }
+
+ public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
+ mNotificationData.updateRanking(ranking);
+ updateNotifications();
+ }
+
+ protected boolean shouldPeek(NotificationData.Entry entry) {
+ return shouldPeek(entry, entry.notification);
+ }
+
+ public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
+ if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+ if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
+ return false;
+ }
+
+ if (mNotificationData.shouldFilterOut(sbn)) {
+ if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
+ return false;
+ }
+
+ boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
+
+ if (!inUse && !mPresenter.isDozing()) {
+ if (DEBUG) {
+ Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (!mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
+ if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+ return false;
+ }
+
+ if (mPresenter.isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) {
+ if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
+ return false;
+ }
+
+ if (entry.hasJustLaunchedFullScreenIntent()) {
+ if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
+ return false;
+ }
+
+ if (isSnoozedPackage(sbn)) {
+ if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
+ return false;
+ }
+
+ // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
+ int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
+ : NotificationManager.IMPORTANCE_HIGH;
+ if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
+ if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
+ return false;
+ }
+
+ // Don't peek notifications that are suppressed due to group alert behavior
+ if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
+ if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
+ return false;
+ }
+
+ if (!mCallback.shouldPeek(entry, sbn)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected void setNotificationShown(StatusBarNotification n) {
+ setNotificationsShown(new String[]{n.getKey()});
+ }
+
+ protected void setNotificationsShown(String[] keys) {
+ try {
+ mNotificationListener.setNotificationsShown(keys);
+ } catch (RuntimeException e) {
+ Log.d(TAG, "failed setNotificationsShown: ", e);
+ }
+ }
+
+ protected boolean isSnoozedPackage(StatusBarNotification sbn) {
+ return mHeadsUpManager.isSnoozed(sbn.getPackageName());
+ }
+
+ protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
+ boolean alertAgain) {
+ final boolean wasHeadsUp = isHeadsUp(key);
+ if (wasHeadsUp) {
+ if (!shouldPeek) {
+ // We don't want this to be interrupting anymore, lets remove it
+ mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
+ } else {
+ mHeadsUpManager.updateNotification(entry, alertAgain);
+ }
+ } else if (shouldPeek && alertAgain) {
+ // This notification was updated to be a heads-up, show it!
+ mHeadsUpManager.showNotification(entry);
+ }
+ }
+
+ protected boolean isHeadsUp(String key) {
+ return mHeadsUpManager.isHeadsUp(key);
+ }
+
+ /**
+ * 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);
+
+
+ /**
+ * Called when a notification is clicked.
+ *
+ * @param sbn notification that was clicked
+ * @param row row for that notification
+ */
+ void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row);
+
+ /**
+ * Called when a new notification and row is created.
+ *
+ * @param entry entry for the notification
+ * @param pmUser package manager for user
+ * @param sbn notification
+ * @param row row for the notification
+ */
+ void onBindRow(NotificationData.Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row);
+
+ /**
+ * Removes a notification immediately.
+ *
+ * @param statusBarNotification notification that is being removed
+ */
+ void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
+
+ /**
+ * Returns true if NotificationEntryManager should peek this notification.
+ *
+ * @param entry entry of the notification that might be peeked
+ * @param sbn notification that might be peeked
+ * @return true if the notification should be peeked
+ */
+ boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
index 2e572e17557a..d6a8af5cedd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -168,7 +168,8 @@ public class NotificationGutsManager implements Dumpable {
String key = sbn.getKey();
if (key.equals(mKeyToRemoveOnGutsClosed)) {
mKeyToRemoveOnGutsClosed = null;
- mPresenter.removeNotification(key, mPresenter.getLatestRankingMap());
+ mPresenter.getEntryManager().removeNotification(key,
+ mPresenter.getEntryManager().getLatestRankingMap());
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index a72e8ac77af5..cd51f03fcba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -59,7 +59,7 @@ public class NotificationListener extends NotificationListenerWithPlugins {
final RankingMap currentRanking = getCurrentRanking();
mPresenter.getHandler().post(() -> {
for (StatusBarNotification sbn : notifications) {
- mPresenter.addNotification(sbn, currentRanking);
+ mPresenter.getEntryManager().addNotification(sbn, currentRanking);
}
});
}
@@ -73,7 +73,8 @@ public class NotificationListener extends NotificationListenerWithPlugins {
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
mRemoteInputManager.getKeysKeptForRemoteInput().remove(key);
- boolean isUpdate = mPresenter.getNotificationData().get(key) != null;
+ boolean isUpdate =
+ mPresenter.getEntryManager().getNotificationData().get(key) != null;
// In case we don't allow child notifications, we ignore children of
// notifications that have a summary, since` we're not going to show them
// anyway. This is true also when the summary is canceled,
@@ -86,16 +87,17 @@ public class NotificationListener extends NotificationListenerWithPlugins {
// Remove existing notification to avoid stale data.
if (isUpdate) {
- mPresenter.removeNotification(key, rankingMap);
+ mPresenter.getEntryManager().removeNotification(key, rankingMap);
} else {
- mPresenter.getNotificationData().updateRanking(rankingMap);
+ mPresenter.getEntryManager().getNotificationData()
+ .updateRanking(rankingMap);
}
return;
}
if (isUpdate) {
- mPresenter.updateNotification(sbn, rankingMap);
+ mPresenter.getEntryManager().updateNotification(sbn, rankingMap);
} else {
- mPresenter.addNotification(sbn, rankingMap);
+ mPresenter.getEntryManager().addNotification(sbn, rankingMap);
}
});
}
@@ -107,7 +109,9 @@ public class NotificationListener extends NotificationListenerWithPlugins {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
final String key = sbn.getKey();
- mPresenter.getHandler().post(() -> mPresenter.removeNotification(key, rankingMap));
+ mPresenter.getHandler().post(() -> {
+ mPresenter.getEntryManager().removeNotification(key, rankingMap);
+ });
}
}
@@ -116,7 +120,9 @@ public class NotificationListener extends NotificationListenerWithPlugins {
if (DEBUG) Log.d(TAG, "onRankingUpdate");
if (rankingMap != null) {
RankingMap r = onPluginRankingUpdate(rankingMap);
- mPresenter.getHandler().post(() -> mPresenter.updateNotificationRanking(r));
+ mPresenter.getHandler().post(() -> {
+ mPresenter.getEntryManager().updateNotificationRanking(r);
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 644d834e42c6..25eb9cccf4ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -81,7 +81,7 @@ public class NotificationLockscreenUserManager implements Dumpable {
isCurrentProfile(getSendingUserId())) {
mUsersAllowingPrivateNotifications.clear();
updateLockscreenNotificationSetting();
- mPresenter.updateNotifications();
+ mPresenter.getEntryManager().updateNotifications();
} else if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
if (userId != mCurrentUserId && isCurrentProfile(userId)) {
mPresenter.onWorkChallengeChanged();
@@ -182,7 +182,7 @@ public class NotificationLockscreenUserManager implements Dumpable {
mUsersAllowingNotifications.clear();
// ... and refresh all the notifications
updateLockscreenNotificationSetting();
- mPresenter.updateNotifications();
+ mPresenter.getEntryManager().updateNotifications();
}
};
@@ -191,7 +191,7 @@ public class NotificationLockscreenUserManager implements Dumpable {
public void onChange(boolean selfChange) {
updateLockscreenNotificationSetting();
if (mDeviceProvisionedController.isDeviceProvisioned()) {
- mPresenter.updateNotifications();
+ mPresenter.getEntryManager().updateNotifications();
}
}
};
@@ -271,13 +271,13 @@ public class NotificationLockscreenUserManager implements Dumpable {
*/
public boolean shouldHideNotifications(String key) {
return isLockscreenPublicMode(mCurrentUserId)
- && mPresenter.getNotificationData().getVisibilityOverride(key) ==
+ && mPresenter.getEntryManager().getNotificationData().getVisibilityOverride(key) ==
Notification.VISIBILITY_SECRET;
}
public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
return mShowLockscreenNotifications
- && !mPresenter.getNotificationData().isAmbient(sbn.getKey());
+ && !mPresenter.getEntryManager().getNotificationData().isAmbient(sbn.getKey());
}
private void setShowLockscreenNotifications(boolean show) {
@@ -395,7 +395,7 @@ public class NotificationLockscreenUserManager implements Dumpable {
}
private boolean packageHasVisibilityOverride(String key) {
- return mPresenter.getNotificationData().getVisibilityOverride(key) ==
+ return mPresenter.getEntryManager().getNotificationData().getVisibilityOverride(key) ==
Notification.VISIBILITY_PRIVATE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
index e58d80186b3a..e958f3fe54d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLogger.java
@@ -99,7 +99,7 @@ public class NotificationLogger {
// notifications.
// 3. Report newly visible and no-longer visible notifications.
// 4. Keep currently visible notifications for next report.
- ArrayList<NotificationData.Entry> activeNotifications = mPresenter.
+ ArrayList<NotificationData.Entry> activeNotifications = mPresenter.getEntryManager().
getNotificationData().getActiveNotifications();
int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 283a6e385582..158a986f2cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -40,9 +40,10 @@ public class NotificationMediaManager implements Dumpable {
private static final String TAG = "NotificationMediaManager";
public static final boolean DEBUG_MEDIA = false;
- private final NotificationPresenter mPresenter;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
+
+ private NotificationPresenter mPresenter;
private MediaController mMediaController;
private String mMediaNotificationKey;
private MediaMetadata mMediaMetadata;
@@ -73,8 +74,7 @@ public class NotificationMediaManager implements Dumpable {
}
};
- public NotificationMediaManager(NotificationPresenter presenter, Context context) {
- mPresenter = presenter;
+ public NotificationMediaManager(Context context) {
mContext = context;
mMediaSessionManager
= (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -82,6 +82,10 @@ public class NotificationMediaManager implements Dumpable {
// in session state
}
+ public void setUpWithPresenter(NotificationPresenter presenter) {
+ mPresenter = presenter;
+ }
+
public void onNotificationRemoved(String key) {
if (key.equals(mMediaNotificationKey)) {
clearCurrentMediaNotification();
@@ -100,8 +104,8 @@ public class NotificationMediaManager implements Dumpable {
public void findAndUpdateMediaNotifications() {
boolean metaDataChanged = false;
- synchronized (mPresenter.getNotificationData()) {
- ArrayList<NotificationData.Entry> activeNotifications = mPresenter
+ synchronized (mPresenter.getEntryManager().getNotificationData()) {
+ ArrayList<NotificationData.Entry> activeNotifications = mPresenter.getEntryManager()
.getNotificationData().getActiveNotifications();
final int N = activeNotifications.size();
@@ -188,7 +192,7 @@ public class NotificationMediaManager implements Dumpable {
}
if (metaDataChanged) {
- mPresenter.updateNotifications();
+ mPresenter.getEntryManager().updateNotifications();
}
mPresenter.updateMediaMetaData(metaDataChanged, true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 33c72534b1b6..c1dd958506f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -16,12 +16,11 @@
package com.android.systemui.statusbar;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Handler;
-import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
import android.view.View;
-import java.util.Set;
-
/**
* An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
* for both querying the state of the system (some modularised piece of functionality may
@@ -29,9 +28,11 @@ import java.util.Set;
* for affecting the state of the system (e.g. starting an intent, given that the presenter may
* want to perform some action before doing so).
*/
-public interface NotificationPresenter extends NotificationUpdateHandler,
- NotificationData.Environment, NotificationRemoteInputManager.Callback {
-
+public interface NotificationPresenter extends NotificationData.Environment,
+ NotificationRemoteInputManager.Callback,
+ ExpandableNotificationRow.OnExpandClickListener,
+ ActivatableNotificationView.OnActivatedListener,
+ NotificationEntryManager.Callback {
/**
* Returns true if the presenter is not visible. For example, it may not be necessary to do
* animations if this returns true.
@@ -50,32 +51,15 @@ public interface NotificationPresenter extends NotificationUpdateHandler,
void startNotificationGutsIntent(Intent intent, int appUid);
/**
- * Returns NotificationData.
- */
- NotificationData getNotificationData();
-
- /**
* Returns the Handler for NotificationPresenter.
*/
Handler getHandler();
- // TODO: Create NotificationEntryManager and move this method to there.
- /**
- * Signals that some notifications have changed, and NotificationPresenter should update itself.
- */
- void updateNotifications();
-
/**
* Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
*/
void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
- // TODO: Create NotificationEntryManager and move this method to there.
- /**
- * Gets the latest ranking map.
- */
- NotificationListenerService.RankingMap getLatestRankingMap();
-
/**
* Called when the locked status of the device is changed for a work profile.
*/
@@ -107,4 +91,27 @@ public interface NotificationPresenter extends NotificationUpdateHandler,
* @return true iff the device is locked
*/
boolean isDeviceLocked(int userId);
+
+ /**
+ * @return true iff the device is in vr mode
+ */
+ boolean isDeviceInVrMode();
+
+ /**
+ * Returns the NotificationEntryManager for this presenter.
+ *
+ * @return NotificationEntryManager
+ */
+ NotificationEntryManager getEntryManager();
+
+ // TODO: Remove this once the view managing code is pulled out of StatusBar.
+ /**
+ * Updates the visual representation of the notifications.
+ */
+ void updateNotificationViews();
+
+ /**
+ * @return true iff the device is dozing
+ */
+ boolean isDozing();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 7827f62970e7..d0428d0ee51d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -282,7 +282,7 @@ public class NotificationRemoteInputManager implements Dumpable {
@Override
public void onRemoteInputSent(NotificationData.Entry entry) {
if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) {
- mPresenter.removeNotification(entry.key, null);
+ mPresenter.getEntryManager().removeNotification(entry.key, null);
} else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) {
// We're currently holding onto this notification, but from the apps point of
// view it is already canceled, so we'll need to cancel it on the apps behalf
@@ -290,7 +290,7 @@ public class NotificationRemoteInputManager implements Dumpable {
// bit.
mPresenter.getHandler().postDelayed(() -> {
if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) {
- mPresenter.removeNotification(entry.key, null);
+ mPresenter.getEntryManager().removeNotification(entry.key, null);
}
}, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
}
@@ -336,7 +336,8 @@ public class NotificationRemoteInputManager implements Dumpable {
for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
mRemoteInputController.removeRemoteInput(entry, null);
- mPresenter.removeNotification(entry.key, mPresenter.getLatestRankingMap());
+ mPresenter.getEntryManager().removeNotification(entry.key,
+ mPresenter.getEntryManager().getLatestRankingMap());
}
mRemoteInputEntriesToRemoveOnCollapse.clear();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 5ba6f6af05ab..3ebeb4d45c26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -247,19 +247,6 @@ public class CarStatusBar extends StatusBar implements
return null;
}
- /**
- * Returns the
- * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
- * be triggered when a notification card is long-pressed.
- */
- @Override
- protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
- // For the automative use case, we do not want to the user to be able to interact with
- // a notification other than a regular click. As a result, just return null for the
- // long click listener.
- return null;
- }
-
@Override
public void showBatteryView() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -388,18 +375,6 @@ public class CarStatusBar extends StatusBar implements
}
@Override
- protected boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
- // 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 (mPanelExpanded) {
- return false;
- }
-
- return super.shouldPeek(entry, sbn);
- }
-
- @Override
public void animateExpandNotificationsPanel() {
// 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 removing all heads-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index c5349d186b86..04fe7f206021 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -29,9 +29,6 @@ import static com.android.systemui.statusbar.NotificationLockscreenUserManager
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
-import static com.android.systemui.statusbar.NotificationRemoteInputManager
- .FORCE_REMOTE_INPUT_HISTORY;
-import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -66,14 +63,12 @@ import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.om.IOverlayManager;
import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
@@ -88,7 +83,6 @@ import android.media.MediaMetadata;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -103,12 +97,9 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
-import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -139,7 +130,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.MessagingGroup;
import com.android.internal.widget.MessagingMessage;
@@ -149,11 +139,9 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.AutoReinflateContainer;
-import com.android.systemui.DejankUtils;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
-import com.android.systemui.ForegroundServiceController;
import com.android.systemui.Interpolators;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -200,6 +188,7 @@ import com.android.systemui.statusbar.KeyboardShortcuts;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationEntryManager;
import com.android.systemui.statusbar.NotificationGutsManager;
import com.android.systemui.statusbar.NotificationInfo;
import com.android.systemui.statusbar.NotificationListener;
@@ -214,8 +203,6 @@ import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
-import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.RowInflaterTask;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -239,7 +226,6 @@ import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.util.NotificationChannels;
-import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.volume.VolumeComponent;
import java.io.FileDescriptor;
@@ -255,9 +241,6 @@ import java.util.Stack;
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks,
- ActivatableNotificationView.OnActivatedListener,
- ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
- ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
public static final boolean MULTIUSER_DEBUG = false;
@@ -270,10 +253,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
protected static final int MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU = 1027;
- protected static final boolean ENABLE_HEADS_UP = true;
- protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
-
-
// Should match the values in PhoneWindowManager
public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
@@ -309,7 +288,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// Time after we abort the launch transition.
private static final long LAUNCH_TRANSITION_TIMEOUT_MS = 5000;
- private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
+ protected static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
private static final int STATUS_OR_NAV_TRANSIENT =
View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
@@ -427,6 +406,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private NotificationGutsManager mGutsManager;
protected NotificationLogger mNotificationLogger;
+ protected NotificationEntryManager mEntryManager;
// for disabling the status bar
private int mDisabled1 = 0;
@@ -478,23 +458,6 @@ public class StatusBar extends SystemUI implements DemoMode,
};
protected final H mHandler = createHandler();
- final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
- @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();
- }
- }
- }
- };
private int mInteractingWindows;
private boolean mAutohideSuspended;
@@ -588,7 +551,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- private NotificationMessagingUtil mMessagingUtil;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private UserSwitcherController mUserSwitcherController;
private NetworkController mNetworkController;
@@ -603,11 +565,9 @@ public class StatusBar extends SystemUI implements DemoMode,
private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
protected NotificationIconAreaController mNotificationIconAreaController;
private boolean mReinflateNotificationsOnUserSwitched;
- private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
private boolean mClearAllEnabled;
@Nullable private View mAmbientIndicationContainer;
private SysuiColorExtractor mColorExtractor;
- private ForegroundServiceController mForegroundServiceController;
private ScreenLifecycle mScreenLifecycle;
@VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
@@ -619,7 +579,6 @@ public class StatusBar extends SystemUI implements DemoMode,
};
private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
mTmpChildOrderMap = new HashMap<>();
- private RankingMap mLatestRankingMap;
private boolean mNoAnimationOnNextBarModeChange;
private FalsingManager mFalsingManager;
@@ -641,6 +600,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mGroupManager = Dependency.get(NotificationGroupManager.class);
mNotificationLogger = Dependency.get(NotificationLogger.class);
mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
+ mNotificationListener = Dependency.get(NotificationListener.class);
+ mGroupManager = Dependency.get(NotificationGroupManager.class);
mNetworkController = Dependency.get(NetworkController.class);
mUserSwitcherController = Dependency.get(UserSwitcherController.class);
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
@@ -649,19 +610,18 @@ public class StatusBar extends SystemUI implements DemoMode,
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mBatteryController = Dependency.get(BatteryController.class);
mAssistManager = Dependency.get(AssistManager.class);
- mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class);
mGutsManager = Dependency.get(NotificationGutsManager.class);
+ mMediaManager = Dependency.get(NotificationMediaManager.class);
+ mEntryManager = Dependency.get(NotificationEntryManager.class);
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
-
mDisplay = mWindowManager.getDefaultDisplay();
updateDisplaySize();
@@ -679,16 +639,12 @@ public class StatusBar extends SystemUI implements DemoMode,
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
- mNotificationData = new NotificationData(this);
- mMessagingUtil = new NotificationMessagingUtil(mContext);
-
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
- mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -698,8 +654,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mLockPatternUtils = new LockPatternUtils(mContext);
- mMediaManager = new NotificationMediaManager(this, mContext);
- mLockscreenUserManager.setUpWithPresenter(this);
+ mMediaManager.setUpWithPresenter(this);
// Connect in to the status bar manager service
mCommandQueue = getComponent(CommandQueue.class);
@@ -725,6 +680,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter);
mWallpaperChangedReceiver.onReceive(mContext, null);
+ mLockscreenUserManager.setUpWithPresenter(this);
mCommandQueue.disable(switches[0], switches[6], false /* animate */);
setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff,
fullscreenStackBounds, dockedStackBounds);
@@ -739,7 +695,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
// Set up the initial notification state.
- mNotificationListener = Dependency.get(NotificationListener.class);
mNotificationListener.setUpWithPresenter(this);
if (DEBUG) {
@@ -774,15 +729,6 @@ public class StatusBar extends SystemUI implements DemoMode,
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
- mHeadsUpObserver.onChange(true); // set up
- 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);
- }
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
mUnlockMethodCache.addListener(this);
startKeyguard();
@@ -864,11 +810,13 @@ public class StatusBar extends SystemUI implements DemoMode,
mHeadsUpManager.addListener(mGroupManager);
mHeadsUpManager.addListener(mVisualStabilityManager);
mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
- mNotificationData.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
putComponent(HeadsUpManager.class, mHeadsUpManager);
+ mEntryManager.setUpWithPresenter(this, mStackScroller, this, mVisualStabilityManager,
+ mHeadsUpManager);
+
if (MULTIUSER_DEBUG) {
mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
mNotificationPanelDebugText.setVisibility(View.VISIBLE);
@@ -884,7 +832,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// no window manager? good luck with that
}
- mStackScroller.setLongPressListener(getNotificationLongClicker());
+ mStackScroller.setLongPressListener(mEntryManager.getNotificationLongClicker());
mStackScroller.setStatusBar(this);
mStackScroller.setGroupManager(mGroupManager);
mStackScroller.setHeadsUpManager(mHeadsUpManager);
@@ -1107,7 +1055,7 @@ public class StatusBar extends SystemUI implements DemoMode,
MessagingGroup.dropCache();
// start old BaseStatusBar.onDensityOrFontScaleChanged().
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
- updateNotificationsOnDensityOrFontScaleChanged();
+ mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
} else {
mReinflateNotificationsOnUserSwitched = true;
}
@@ -1171,20 +1119,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- private void updateNotificationsOnDensityOrFontScaleChanged() {
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- for (int i = 0; i < activeNotifications.size(); i++) {
- Entry entry = activeNotifications.get(i);
- boolean exposedGuts = mGutsManager.getExposedGuts() != null
- && entry.row.getGuts() == mGutsManager.getExposedGuts();
- entry.row.onDensityOrFontScaleChanged();
- if (exposedGuts) {
- mGutsManager.setExposedGuts(entry.row.getGuts());
- mGutsManager.bindGuts(entry.row);
- }
- }
- }
-
private void inflateSignalClusters() {
if (mKeyguardStatusBar != null) reinflateSignalCluster(mKeyguardStatusBar);
}
@@ -1298,7 +1232,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mStackScroller.setDismissAllInProgress(false);
for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
if (mStackScroller.canChildBeDismissed(rowToRemove)) {
- removeNotification(rowToRemove.getEntry().key, null);
+ mEntryManager.removeNotification(rowToRemove.getEntry().key, null);
} else {
rowToRemove.resetTranslation();
}
@@ -1406,303 +1340,29 @@ public class StatusBar extends SystemUI implements DemoMode,
return true;
}
- void awakenDreams() {
- SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
- }
-
- @Override
- public void addNotification(StatusBarNotification notification, RankingMap ranking) {
- String key = notification.getKey();
- if (DEBUG) Log.d(TAG, "addNotification key=" + key);
-
- mNotificationData.updateRanking(ranking);
- Entry shadeEntry = null;
- try {
- shadeEntry = createNotificationViews(notification);
- } catch (InflationException e) {
- handleInflationException(notification, e);
- return;
- }
- boolean isHeadsUped = shouldPeek(shadeEntry);
- if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
- if (shouldSuppressFullScreenIntent(key)) {
- 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)
- awakenDreams();
-
- // 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) {
- }
- }
- }
- abortExistingInflation(key);
-
- mForegroundServiceController.addNotification(notification,
- mNotificationData.getImportance(key));
-
- mPendingNotifications.put(key, shadeEntry);
- }
-
- private void abortExistingInflation(String key) {
- if (mPendingNotifications.containsKey(key)) {
- Entry entry = mPendingNotifications.get(key);
- entry.abortTask();
- mPendingNotifications.remove(key);
- }
- Entry addedEntry = mNotificationData.get(key);
- if (addedEntry != null) {
- addedEntry.abortTask();
- }
- }
-
- private void addEntry(Entry shadeEntry) {
- boolean isHeadsUped = shouldPeek(shadeEntry);
- if (isHeadsUped) {
- mHeadsUpManager.showNotification(shadeEntry);
- // Mark as seen immediately
- setNotificationShown(shadeEntry.notification);
- }
- addNotificationViews(shadeEntry);
- // Recalculate the position of the sliding windows and the titles.
- setAreThereNotifications();
- }
-
- @Override
- public void handleInflationException(StatusBarNotification notification, Exception e) {
- handleNotificationError(notification, e.getMessage());
- }
-
- @Override
- public void onAsyncInflationFinished(Entry entry) {
- mPendingNotifications.remove(entry.key);
- // If there was an async task started after the removal, we don't want to add it back to
- // the list, otherwise we might get leaks.
- boolean isNew = mNotificationData.get(entry.key) == null;
- if (isNew && !entry.row.isRemoved()) {
- addEntry(entry);
- } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
- mVisualStabilityManager.onLowPriorityUpdated(entry);
- updateNotificationShade();
- }
- entry.row.setLowPriorityStateUpdated(false);
- }
-
- private boolean shouldSuppressFullScreenIntent(String key) {
- if (isDeviceInVrMode()) {
- return true;
- }
-
- if (mPowerManager.isInteractive()) {
- return mNotificationData.shouldSuppressScreenOn(key);
- } else {
- return mNotificationData.shouldSuppressScreenOff(key);
- }
- }
-
- @Override
- public void updateNotificationRanking(RankingMap ranking) {
- mNotificationData.updateRanking(ranking);
- updateNotifications();
- }
-
@Override
- public void removeNotification(String key, RankingMap ranking) {
- boolean deferRemoval = false;
- abortExistingInflation(key);
- if (mHeadsUpManager.isHeadsUp(key)) {
- // A cancel() in response to a remote input shouldn't be delayed, as it makes the
- // sending look longer than it takes.
- // Also we should not defer the removal if reordering isn't allowed since otherwise
- // some notifications can't disappear before the panel is closed.
- boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
- && !FORCE_REMOTE_INPUT_HISTORY
- || !mVisualStabilityManager.isReorderingAllowed();
- deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
- }
- mMediaManager.onNotificationRemoved(key);
-
- Entry entry = mNotificationData.get(key);
- if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key)
- && entry.row != null && !entry.row.isDismissed()) {
- StatusBarNotification sbn = entry.notification;
-
- Notification.Builder b = Notification.Builder
- .recoverBuilder(mContext, sbn.getNotification().clone());
- CharSequence[] oldHistory = sbn.getNotification().extras
- .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
- CharSequence[] newHistory;
- if (oldHistory == null) {
- newHistory = new CharSequence[1];
- } else {
- newHistory = new CharSequence[oldHistory.length + 1];
- System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
- }
- newHistory[0] = String.valueOf(entry.remoteInputText);
- b.setRemoteInputHistory(newHistory);
-
- Notification newNotification = b.build();
-
- // Undo any compatibility view inflation
- newNotification.contentView = sbn.getNotification().contentView;
- newNotification.bigContentView = sbn.getNotification().bigContentView;
- newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
-
- StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
- sbn.getOpPkg(),
- sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
- newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
- boolean updated = false;
- try {
- updateNotificationInternal(newSbn, null);
- updated = true;
- } catch (InflationException e) {
- deferRemoval = false;
- }
- if (updated) {
- Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
- mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
- return;
- }
- }
- if (deferRemoval) {
- mLatestRankingMap = ranking;
- mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
- return;
- }
-
- if (mRemoteInputManager.onRemoveNotification(entry)) {
- mLatestRankingMap = ranking;
- return;
- }
-
- if (entry != null && mGutsManager.getExposedGuts() != null
- && mGutsManager.getExposedGuts() == entry.row.getGuts()
- && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
- Log.w(TAG, "Keeping notification because it's showing guts. " + key);
- mLatestRankingMap = ranking;
- mGutsManager.setKeyToRemoveOnGutsClosed(key);
- return;
- }
-
- if (entry != null) {
- mForegroundServiceController.removeNotification(entry.notification);
- }
-
- if (entry != null && entry.row != null) {
- entry.row.setRemoved();
- mStackScroller.cleanUpViewState(entry.row);
- }
- // Let's remove the children if this was a summary
- handleGroupSummaryRemoved(key);
- StatusBarNotification old = removeNotificationViews(key, ranking);
- if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
-
- if (old != null) {
- if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
- && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
- if (mState == StatusBarState.SHADE) {
- animateCollapsePanels();
- } else if (mState == StatusBarState.SHADE_LOCKED && !isCollapsing()) {
- goToKeyguard();
- }
- }
- }
- setAreThereNotifications();
- }
-
- /**
- * Ensures that the group children are cancelled immediately when the group summary is cancelled
- * instead of waiting for the notification manager to send all cancels. Otherwise this could
- * lead to flickers.
- *
- * This also ensures that the animation looks nice and only consists of a single disappear
- * animation instead of multiple.
- * @param key the key of the notification was removed
- *
- */
- private void handleGroupSummaryRemoved(String key) {
- Entry entry = mNotificationData.get(key);
- if (entry != null && entry.row != null
- && entry.row.isSummaryWithChildren()) {
- if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
- // We don't want to remove children for autobundled notifications as they are not
- // always cancelled. We only remove them if they were dismissed by the user.
- return;
- }
- List<ExpandableNotificationRow> notificationChildren =
- entry.row.getNotificationChildren();
- for (int i = 0; i < notificationChildren.size(); i++) {
- ExpandableNotificationRow row = notificationChildren.get(i);
- if ((row.getStatusBarNotification().getNotification().flags
- & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- // the child is a foreground service notification which we can't remove!
- continue;
- }
- row.setKeepInParent(true);
- // we need to set this state earlier as otherwise we might generate some weird
- // animations
- row.setRemoved();
- }
- }
- }
-
- protected void performRemoveNotification(StatusBarNotification n) {
- Entry entry = mNotificationData.get(n.getKey());
- mRemoteInputManager.onPerformRemoveNotification(n, entry);
- // start old BaseStatusBar.performRemoveNotification.
- final String pkg = n.getPackageName();
- final String tag = n.getTag();
- final int id = n.getId();
- final int userId = n.getUserId();
- try {
- int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
- if (isHeadsUp(n.getKey())) {
- dismissalSurface = NotificationStats.DISMISSAL_PEEK;
- } else if (mStackScroller.hasPulsingNotifications()) {
- dismissalSurface = NotificationStats.DISMISSAL_AOD;
- }
- mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
- removeNotification(n.getKey(), null);
-
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
+ public void onPerformRemoveNotification(StatusBarNotification n) {
if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) {
// We were showing a pulse for a notification, but no notifications are pulsing anymore.
// Finish the pulse.
mDozeScrimController.pulseOutNow();
}
- // end old BaseStatusBar.performRemoveNotification.
}
- private void updateNotificationShade() {
- if (mStackScroller == null) return;
+ @Override
+ public void updateNotificationViews() {
+ // The function updateRowStates depends on both of these being non-null, so check them here.
+ // We may be called before they are set from DeviceProvisionedController's callback.
+ if (mStackScroller == null || mScrimController == null) return;
// Do not modify the notifications during collapse.
if (isCollapsing()) {
- addPostCollapseAction(this::updateNotificationShade);
+ addPostCollapseAction(this::updateNotificationViews);
return;
}
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
+ ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData()
+ .getActiveNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
@@ -1814,7 +1474,36 @@ public class StatusBar extends SystemUI implements DemoMode,
updateQsExpansionEnabled();
// Let's also update the icons
- mNotificationIconAreaController.updateNotificationIcons(mNotificationData);
+ mNotificationIconAreaController.updateNotificationIcons(
+ mEntryManager.getNotificationData());
+ }
+
+ @Override
+ public void onNotificationAdded(Entry shadeEntry) {
+ // Recalculate the position of the sliding windows and the titles.
+ setAreThereNotifications();
+ }
+
+ @Override
+ public void onNotificationUpdated(StatusBarNotification notification) {
+ setAreThereNotifications();
+ }
+
+ @Override
+ public void onNotificationRemoved(String key, StatusBarNotification old) {
+ if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
+
+ if (old != null) {
+ if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
+ && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
+ if (mState == StatusBarState.SHADE) {
+ animateCollapsePanels();
+ } else if (mState == StatusBarState.SHADE_LOCKED && !isCollapsing()) {
+ goToKeyguard();
+ }
+ }
+ }
+ setAreThereNotifications();
}
/**
@@ -1896,7 +1585,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
for (ExpandableNotificationRow remove : toRemove) {
parent.removeChildNotification(remove);
- if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) {
+ if (mEntryManager.getNotificationData().get(
+ remove.getStatusBarNotification().getKey()) == null) {
// We only want to add an animation if the view is completely removed
// otherwise it's just a transfer
mStackScroller.notifyGroupChildRemoved(remove,
@@ -1949,7 +1639,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private void updateEmptyShadeView() {
boolean showEmptyShadeView =
mState != StatusBarState.KEYGUARD &&
- mNotificationData.getActiveNotifications().size() == 0;
+ mEntryManager.getNotificationData().getActiveNotifications().size() == 0;
mNotificationPanel.showEmptyShadeView(showEmptyShadeView);
}
@@ -1964,7 +1654,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
currentIndex++;
- if (!mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) {
+ if (!mEntryManager.getNotificationData().isAmbient(
+ row.getStatusBarNotification().getKey())) {
speedBumpIndex = currentIndex;
}
}
@@ -1976,15 +1667,9 @@ public class StatusBar extends SystemUI implements DemoMode,
return entry.row.getParent() instanceof NotificationStackScrollLayout;
}
- @Override
- public void updateNotifications() {
- mNotificationData.filterAndSort();
-
- updateNotificationShade();
- }
public void requestNotificationUpdate() {
- updateNotifications();
+ mEntryManager.updateNotifications();
}
protected void setAreThereNotifications() {
@@ -1993,7 +1678,7 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean clearable = hasActiveNotifications() &&
hasActiveClearableNotifications();
Log.d(TAG, "setAreThereNotifications: N=" +
- mNotificationData.getActiveNotifications().size() + " any=" +
+ mEntryManager.getNotificationData().getActiveNotifications().size() + " any=" +
hasActiveNotifications() + " clearable=" + clearable);
}
@@ -2278,9 +1963,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
- mDisableNotificationAlerts =
- (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
- mHeadsUpObserver.onChange(true);
+ mEntryManager.setDisableNotificationAlerts(
+ (state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0);
}
if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
@@ -2343,10 +2027,40 @@ public class StatusBar extends SystemUI implements DemoMode,
return getBarState() == StatusBarState.KEYGUARD;
}
+ @Override
public boolean isDozing() {
return mDozing;
}
+ @Override
+ public boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
+ if (mIsOccluded && !isDozing()) {
+ boolean devicePublic = mLockscreenUserManager.
+ isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
+ boolean userPublic = devicePublic
+ || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
+ boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
+ if (userPublic && needsRedaction) {
+ return false;
+ }
+ }
+
+ if (sbn.getNotification().fullScreenIntent != null) {
+ if (mAccessibilityManager.isTouchExplorationEnabled()) {
+ if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
+ return false;
+ } else if (isDozing()) {
+ // We never want heads up when we are dozing.
+ return false;
+ } else {
+ // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
+ return !mStatusBarKeyguardViewManager.isShowing()
+ || mStatusBarKeyguardViewManager.isOccluded();
+ }
+ }
+ return true;
+ }
+
@Override // NotificationData.Environment
public String getCurrentMediaNotificationKey() {
return mMediaManager.getMediaNotificationKey();
@@ -2415,34 +2129,10 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
- if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
- removeNotification(entry.key, mLatestRankingMap);
- mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
- if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
- mLatestRankingMap = null;
- }
- } else {
- updateNotificationRanking(null);
- if (isHeadsUp) {
- mDozeServiceHost.fireNotificationHeadsUp();
- }
- }
-
- }
+ mEntryManager.onHeadsUpStateChanged(entry, isHeadsUp);
- protected void updateHeadsUp(String key, Entry entry, boolean shouldPeek,
- boolean alertAgain) {
- final boolean wasHeadsUp = isHeadsUp(key);
- if (wasHeadsUp) {
- if (!shouldPeek) {
- // We don't want this to be interrupting anymore, lets remove it
- mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
- } else {
- mHeadsUpManager.updateNotification(entry, alertAgain);
- }
- } else if (shouldPeek && alertAgain) {
- // This notification was updated to be a heads-up, show it!
- mHeadsUpManager.showNotification(entry);
+ if (isHeadsUp) {
+ mDozeServiceHost.fireNotificationHeadsUp();
}
}
@@ -2452,14 +2142,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- public boolean isHeadsUp(String key) {
- return mHeadsUpManager.isHeadsUp(key);
- }
-
- protected boolean isSnoozedPackage(StatusBarNotification sbn) {
- return mHeadsUpManager.isSnoozed(sbn.getPackageName());
- }
-
public boolean isKeyguardCurrentlySecure() {
return !mUnlockMethodCache.canSkipBouncer();
}
@@ -2491,7 +2173,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onReorderingAllowed() {
- updateNotifications();
+ mEntryManager.updateNotifications();
}
public boolean isLaunchTransitionFadingAway() {
@@ -3127,14 +2809,6 @@ public class StatusBar extends SystemUI implements DemoMode,
+ " scroll " + mStackScroller.getScrollX()
+ "," + mStackScroller.getScrollY());
}
- pw.print(" mPendingNotifications=");
- if (mPendingNotifications.size() == 0) {
- pw.println("null");
- } else {
- for (Entry entry : mPendingNotifications.values()) {
- pw.println(entry.notification);
- }
- }
pw.print(" mInteractingWindows="); pw.println(mInteractingWindows);
pw.print(" mStatusBarWindowState=");
@@ -3146,8 +2820,7 @@ public class StatusBar extends SystemUI implements DemoMode,
pw.println(Settings.Global.zenModeToString(Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.ZEN_MODE,
Settings.Global.ZEN_MODE_OFF)));
- pw.print(" mUseHeadsUp=");
- pw.println(mUseHeadsUp);
+
if (mStatusBarView != null) {
dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
}
@@ -3189,8 +2862,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
if (DUMPTRUCK) {
- synchronized (mNotificationData) {
- mNotificationData.dump(pw, " ");
+ synchronized (mEntryManager.getNotificationData()) {
+ mEntryManager.getNotificationData().dump(pw, " ");
}
if (false) {
@@ -3429,7 +3102,8 @@ public class StatusBar extends SystemUI implements DemoMode,
};
public void resetUserExpandedStates() {
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
+ ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData()
+ .getActiveNotifications();
final int notificationCount = activeNotifications.size();
for (int i = 0; i < notificationCount; i++) {
NotificationData.Entry entry = activeNotifications.get(i);
@@ -3484,12 +3158,12 @@ public class StatusBar extends SystemUI implements DemoMode,
if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
animateCollapsePanels();
updatePublicMode();
- mNotificationData.filterAndSort();
+ mEntryManager.getNotificationData().filterAndSort();
if (mReinflateNotificationsOnUserSwitched) {
- updateNotificationsOnDensityOrFontScaleChanged();
+ mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
mReinflateNotificationsOnUserSwitched = false;
}
- updateNotificationShade();
+ updateNotificationViews();
mMediaManager.clearCurrentMediaNotification();
setLockscreenUser(newUserId);
}
@@ -3499,6 +3173,13 @@ public class StatusBar extends SystemUI implements DemoMode,
return mLockscreenUserManager;
}
+ @Override
+ public void onBindRow(Entry entry, PackageManager pmUser,
+ StatusBarNotification sbn, ExpandableNotificationRow row) {
+ row.setAboveShelfChangedListener(mAboveShelfObserver);
+ row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
+ }
+
protected void setLockscreenUser(int newUserId) {
mLockscreenWallpaper.setCurrentUser(newUserId);
mScrimController.setCurrentUser(newUserId);
@@ -3559,7 +3240,8 @@ public class StatusBar extends SystemUI implements DemoMode,
try {
// consider the transition from peek to expanded to be a panel open,
// but not one that clears notification effects.
- int notificationLoad = mNotificationData.getActiveNotifications().size();
+ int notificationLoad = mEntryManager.getNotificationData()
+ .getActiveNotifications().size();
mBarService.onPanelRevealed(false, notificationLoad);
} catch (RemoteException ex) {
// Won't fail unless the world has ended.
@@ -3577,7 +3259,8 @@ public class StatusBar extends SystemUI implements DemoMode,
!isPresenterFullyCollapsed() &&
(mState == StatusBarState.SHADE
|| mState == StatusBarState.SHADE_LOCKED);
- int notificationLoad = mNotificationData.getActiveNotifications().size();
+ int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications()
+ .size();
if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
notificationLoad = 1;
}
@@ -3712,7 +3395,7 @@ public class StatusBar extends SystemUI implements DemoMode,
} catch (RemoteException e) {
// Ignore.
}
- mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+ mEntryManager.destroy();
// End old BaseStatusBar.destroy().
if (mStatusBarWindow != null) {
mWindowManager.removeViewImmediate(mStatusBarWindow);
@@ -4165,7 +3848,7 @@ public class StatusBar extends SystemUI implements DemoMode,
updateDozingState();
updatePublicMode();
updateStackScrollerState(goingToFullShade, fromShadeLocked);
- updateNotifications();
+ mEntryManager.updateNotifications();
checkBarModes();
updateScrimController();
updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
@@ -4679,7 +4362,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onWorkChallengeChanged() {
updatePublicMode();
- updateNotifications();
+ mEntryManager.updateNotifications();
if (mPendingWorkRemoteInputView != null
&& !mLockscreenUserManager.isAnyProfilePublicMode()) {
// Expand notification panel and the notification row, then click on remote input view
@@ -4889,7 +4572,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public boolean hasActiveNotifications() {
- return !mNotificationData.getActiveNotifications().isEmpty();
+ return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
}
@Override
@@ -5268,7 +4951,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected IStatusBarService mBarService;
// all notifications
- protected NotificationData mNotificationData;
protected NotificationStackScrollLayout mStackScroller;
protected NotificationGroupManager mGroupManager;
@@ -5288,14 +4970,10 @@ public class StatusBar extends SystemUI implements DemoMode,
protected boolean mDeviceInteractive;
protected boolean mVisible;
- protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
// mScreenOnFromKeyguard && mVisible.
private boolean mVisibleToUser;
- protected boolean mUseHeadsUp = false;
- protected boolean mDisableNotificationAlerts = false;
-
protected DevicePolicyManager mDevicePolicyManager;
protected PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -5304,7 +4982,6 @@ public class StatusBar extends SystemUI implements DemoMode,
private LockPatternUtils mLockPatternUtils;
private DeviceProvisionedController mDeviceProvisionedController
= Dependency.get(DeviceProvisionedController.class);
- protected SystemServicesProxy mSystemServicesProxy;
// UI-specific methods
@@ -5319,8 +4996,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected DismissView mDismissView;
protected EmptyShadeView mEmptyShadeView;
- private final NotificationClicker mNotificationClicker = new NotificationClicker();
-
protected AssistManager mAssistManager;
protected boolean mVrMode;
@@ -5345,13 +5020,10 @@ public class StatusBar extends SystemUI implements DemoMode,
return mVrMode;
}
- private final DeviceProvisionedListener mDeviceProvisionedListener =
- new DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- updateNotifications();
- }
- };
+ @Override
+ public NotificationEntryManager getEntryManager() {
+ return mEntryManager;
+ }
private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -5377,6 +5049,121 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
+ @Override
+ public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
+ Notification notification = sbn.getNotification();
+ final PendingIntent intent = notification.contentIntent != null
+ ? notification.contentIntent
+ : notification.fullScreenIntent;
+ final String notificationKey = sbn.getKey();
+
+ final boolean afterKeyguardGone = intent.isActivity()
+ && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+ mLockscreenUserManager.getCurrentUserId());
+ dismissKeyguardThenExecute(() -> {
+ // TODO: Some of this code may be able to move to NotificationEntryManager.
+ if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+ // Release the HUN notification to the shade.
+
+ if (isPresenterFullyCollapsed()) {
+ HeadsUpManager.setIsClickedNotification(row, true);
+ }
+ //
+ // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
+ // become canceled shortly by NoMan, but we can't assume that.
+ mHeadsUpManager.releaseImmediately(notificationKey);
+ }
+ StatusBarNotification parentToCancel = null;
+ if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
+ StatusBarNotification summarySbn =
+ mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
+ if (shouldAutoCancel(summarySbn)) {
+ parentToCancel = summarySbn;
+ }
+ }
+ final StatusBarNotification parentToCancelFinal = parentToCancel;
+ final Runnable runnable = () -> {
+ try {
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ if (intent != null) {
+ // If we are launching a work activity and require to launch
+ // separate work challenge, we defer the activity action and cancel
+ // notification until work challenge is unlocked.
+ if (intent.isActivity()) {
+ final int userId = intent.getCreatorUserHandle().getIdentifier();
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+ && mKeyguardManager.isDeviceLocked(userId)) {
+ // TODO(b/28935539): should allow certain activities to
+ // bypass work challenge
+ if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
+ notificationKey)) {
+ // Show work challenge, do not run PendingIntent and
+ // remove notification
+ return;
+ }
+ }
+ }
+ try {
+ intent.send(null, 0, null, null, null, null, getActivityOptions());
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending contentIntent failed: " + e);
+
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
+ }
+ }
+
+ try {
+ mBarService.onNotificationClick(notificationKey);
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ if (parentToCancelFinal != null) {
+ // We have to post it to the UI thread for synchronization
+ mHandler.post(() -> {
+ Runnable removeRunnable =
+ () -> mEntryManager.performRemoveNotification(parentToCancelFinal);
+ if (isCollapsing()) {
+ // To avoid lags we're only performing the remove
+ // after the shade was collapsed
+ addPostCollapseAction(removeRunnable);
+ } else {
+ removeRunnable.run();
+ }
+ });
+ }
+ };
+
+ if (mStatusBarKeyguardViewManager.isShowing()
+ && mStatusBarKeyguardViewManager.isOccluded()) {
+ mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+ } else {
+ new Thread(runnable).start();
+ }
+
+ if (!mNotificationPanel.isFullyCollapsed()) {
+ // close the shade if it was open
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+ true /* delayed */);
+ visibilityChanged(false);
+
+ return true;
+ } else {
+ return false;
+ }
+ }, afterKeyguardGone);
+ }
+
protected NotificationListener mNotificationListener;
protected void notifyUserAboutHiddenNotifications() {
@@ -5438,14 +5225,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return mLockscreenUserManager.isCurrentProfile(notificationUserId);
}
- protected void setNotificationShown(StatusBarNotification n) {
- try {
- mNotificationListener.setNotificationsShown(new String[]{n.getKey()});
- } catch (RuntimeException e) {
- Log.d(TAG, "failed setNotificationsShown: ", e);
- }
- }
-
@Override
public NotificationGroupManager getGroupManager() {
return mGroupManager;
@@ -5475,15 +5254,15 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
- return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item);
- }
-
@Override
public void toggleSplitScreen() {
toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
}
+ void awakenDreams() {
+ SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
+ }
+
@Override
public void preloadRecentApps() {
int msg = MSG_PRELOAD_RECENT_APPS;
@@ -5569,96 +5348,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- protected void inflateViews(Entry entry, ViewGroup parent) {
- PackageManager pmUser = getPackageManagerForUser(mContext,
- entry.notification.getUser().getIdentifier());
-
- final StatusBarNotification sbn = entry.notification;
- if (entry.row != null) {
- entry.reset();
- updateNotification(entry, pmUser, sbn, entry.row);
- } else {
- new RowInflaterTask().inflate(mContext, parent, entry,
- row -> {
- bindRow(entry, pmUser, sbn, row);
- updateNotification(entry, pmUser, sbn, row);
- });
- }
-
- }
-
- private void bindRow(Entry entry, PackageManager pmUser,
- StatusBarNotification sbn, ExpandableNotificationRow row) {
- row.setExpansionLogger(this, entry.notification.getKey());
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setAboveShelfChangedListener(mAboveShelfObserver);
- row.setOnExpandClickListener(this);
- row.setInflationCallback(this);
- row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
- row.setLongPressListener(getNotificationLongClicker());
- mRemoteInputManager.bindRow(row);
-
- // Get the app name.
- // Note that Notification.Builder#bindHeaderAppName has similar logic
- // but since this field is used in the guts, it must be accurate.
- // Therefore we will only show the application label, or, failing that, the
- // package name. No substitutions.
- final String pkg = sbn.getPackageName();
- String appname = pkg;
- try {
- final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS);
- if (info != null) {
- appname = String.valueOf(pmUser.getApplicationLabel(info));
- }
- } catch (NameNotFoundException e) {
- // Do nothing
- }
- row.setAppName(appname);
- row.setOnDismissRunnable(() ->
- performRemoveNotification(row.getStatusBarNotification()));
- row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- if (ENABLE_REMOTE_INPUT) {
- row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
- }
- }
-
- private void updateNotification(Entry entry, PackageManager pmUser,
- StatusBarNotification sbn, ExpandableNotificationRow row) {
- row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
- boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
- boolean isUpdate = mNotificationData.get(entry.key) != null;
- boolean wasLowPriority = row.isLowPriority();
- row.setIsLowPriority(isLowPriority);
- row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
- // bind the click event to the content area
- mNotificationClicker.register(row, sbn);
-
- // Extract target SDK version.
- try {
- ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
- entry.targetSdk = info.targetSdkVersion;
- } catch (NameNotFoundException ex) {
- Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
- }
- row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
- && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
- entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
- entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
-
- entry.row = row;
- entry.row.setOnActivatedListener(this);
-
- boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
- mNotificationData.getImportance(sbn.getKey()));
- boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight && mPanelExpanded;
- row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
- row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
- row.updateNotification(entry);
- }
-
public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
if (!isDeviceProvisioned()) return;
@@ -5702,166 +5391,15 @@ public class StatusBar extends SystemUI implements DemoMode,
}, afterKeyguardGone);
}
-
- private final class NotificationClicker implements View.OnClickListener {
-
- @Override
- public void onClick(final View v) {
- if (!(v instanceof ExpandableNotificationRow)) {
- Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
- return;
- }
-
- wakeUpIfDozing(SystemClock.uptimeMillis(), v);
-
- final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- final StatusBarNotification sbn = row.getStatusBarNotification();
- if (sbn == null) {
- Log.e(TAG, "NotificationClicker called on an unclickable notification,");
- return;
- }
-
- // Check if the notification is displaying the menu, if so slide notification back
- if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
- row.animateTranslateNotification(0);
- return;
- }
-
- Notification notification = sbn.getNotification();
- final PendingIntent intent = notification.contentIntent != null
- ? notification.contentIntent
- : notification.fullScreenIntent;
- final String notificationKey = sbn.getKey();
-
- // Mark notification for one frame.
- row.setJustClicked(true);
- DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
-
- final boolean afterKeyguardGone = intent.isActivity()
- && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
- mLockscreenUserManager.getCurrentUserId());
- dismissKeyguardThenExecute(() -> {
- if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
- // Release the HUN notification to the shade.
-
- if (isPresenterFullyCollapsed()) {
- HeadsUpManager.setIsClickedNotification(row, true);
- }
- //
- // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
- // become canceled shortly by NoMan, but we can't assume that.
- mHeadsUpManager.releaseImmediately(notificationKey);
- }
- StatusBarNotification parentToCancel = null;
- if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
- StatusBarNotification summarySbn =
- mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
- if (shouldAutoCancel(summarySbn)) {
- parentToCancel = summarySbn;
- }
- }
- final StatusBarNotification parentToCancelFinal = parentToCancel;
- final Runnable runnable = () -> {
- try {
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- if (intent != null) {
- // If we are launching a work activity and require to launch
- // separate work challenge, we defer the activity action and cancel
- // notification until work challenge is unlocked.
- if (intent.isActivity()) {
- final int userId = intent.getCreatorUserHandle().getIdentifier();
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
- && mKeyguardManager.isDeviceLocked(userId)) {
- // TODO(b/28935539): should allow certain activities to
- // bypass work challenge
- if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
- notificationKey)) {
- // Show work challenge, do not run PendingIntent and
- // remove notification
- return;
- }
- }
- }
- try {
- intent.send(null, 0, null, null, null, null, getActivityOptions());
- } catch (PendingIntent.CanceledException e) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending contentIntent failed: " + e);
-
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity()) {
- mAssistManager.hideAssist();
- }
- }
-
- try {
- mBarService.onNotificationClick(notificationKey);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- if (parentToCancelFinal != null) {
- // We have to post it to the UI thread for synchronization
- mHandler.post(() -> {
- Runnable removeRunnable =
- () -> performRemoveNotification(parentToCancelFinal);
- if (isCollapsing()) {
- // To avoid lags we're only performing the remove
- // after the shade was collapsed
- addPostCollapseAction(removeRunnable);
- } else {
- removeRunnable.run();
- }
- });
- }
- };
-
- if (mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isOccluded()) {
- mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
- } else {
- new Thread(runnable).start();
- }
-
- if (!mNotificationPanel.isFullyCollapsed()) {
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
- true /* delayed */);
- visibilityChanged(false);
-
- return true;
- } else {
- return false;
- }
- }, afterKeyguardGone);
- }
-
- private boolean shouldAutoCancel(StatusBarNotification sbn) {
- int flags = sbn.getNotification().flags;
- if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
- return false;
- }
- if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- return false;
- }
- return true;
+ private boolean shouldAutoCancel(StatusBarNotification sbn) {
+ int flags = sbn.getNotification().flags;
+ if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) {
+ return false;
}
-
- public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
- Notification notification = sbn.getNotification();
- if (notification.contentIntent != null || notification.fullScreenIntent != null) {
- row.setOnClickListener(this);
- } else {
- row.setOnClickListener(null);
- }
+ if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ return false;
}
+ return true;
}
protected Bundle getActivityOptions() {
@@ -5904,55 +5442,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
/**
- * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
- * about the failure.
- *
- * WARNING: this will call back into us. Don't hold any locks.
- */
- void handleNotificationError(StatusBarNotification n, String message) {
- removeNotification(n.getKey(), null);
- try {
- mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
- n.getInitialPid(), message, n.getUserId());
- } catch (RemoteException ex) {
- // The end is nigh.
- }
- }
-
- protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
- NotificationData.Entry entry = mNotificationData.remove(key, ranking);
- if (entry == null) {
- Log.w(TAG, "removeNotification for unknown key: " + key);
- return null;
- }
- updateNotifications();
- Dependency.get(LeakDetector.class).trackGarbage(entry);
- return entry.notification;
- }
-
- protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
- throws InflationException {
- if (DEBUG) {
- Log.d(TAG, "createNotificationViews(notification=" + sbn);
- }
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
- Dependency.get(LeakDetector.class).trackInstance(entry);
- entry.createIcons(mContext, sbn);
- // Construct the expanded view.
- inflateViews(entry, mStackScroller);
- return entry;
- }
-
- protected void addNotificationViews(Entry entry) {
- if (entry == null) {
- return;
- }
- // Add the expanded view and icon.
- mNotificationData.add(entry);
- updateNotifications();
- }
-
- /**
* Updates expanded, dimmed and locked states of notification rows.
*/
protected void updateRowStates() {
@@ -6047,163 +5536,10 @@ public class StatusBar extends SystemUI implements DemoMode,
mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount());
}
- // TODO: Move this to NotificationEntryManager once it is created.
- private void updateNotificationInternal(StatusBarNotification notification,
- RankingMap ranking) throws InflationException {
- if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
-
- final String key = notification.getKey();
- abortExistingInflation(key);
- Entry entry = mNotificationData.get(key);
- if (entry == null) {
- return;
- }
- mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
- mRemoteInputManager.onUpdateNotification(entry);
-
- if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
- mGutsManager.setKeyToRemoveOnGutsClosed(null);
- Log.w(TAG, "Notification that was kept for guts was updated. " + key);
- }
-
- Notification n = notification.getNotification();
- mNotificationData.updateRanking(ranking);
-
- final StatusBarNotification oldNotification = entry.notification;
- entry.notification = notification;
- mGroupManager.onEntryUpdated(entry, oldNotification);
-
- entry.updateIcons(mContext, notification);
- inflateViews(entry, mStackScroller);
-
- mForegroundServiceController.updateNotification(notification,
- mNotificationData.getImportance(key));
-
- boolean shouldPeek = shouldPeek(entry, notification);
- boolean alertAgain = alertAgain(entry, n);
-
- updateHeadsUp(key, entry, shouldPeek, alertAgain);
- updateNotifications();
-
- if (!notification.isClearable()) {
- // The user may have performed a dismiss action on the notification, since it's
- // not clearable we should snap it back.
- mStackScroller.snapViewIfNeeded(entry.row);
- }
-
- if (DEBUG) {
- // Is this for you?
- boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
- Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
- }
-
- setAreThereNotifications();
- }
-
- @Override
- public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
- try {
- updateNotificationInternal(notification, ranking);
- } catch (InflationException e) {
- handleInflationException(notification, e);
- }
- }
-
protected void notifyHeadsUpGoingToSleep() {
maybeEscalateHeadsUp();
}
- private boolean alertAgain(Entry oldEntry, Notification newNotification) {
- return oldEntry == null || !oldEntry.hasInterrupted()
- || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
- }
-
- protected boolean shouldPeek(Entry entry) {
- return shouldPeek(entry, entry.notification);
- }
-
- protected boolean shouldPeek(Entry entry, StatusBarNotification sbn) {
- if (!mUseHeadsUp || isDeviceInVrMode()) {
- if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
- return false;
- }
-
- if (mNotificationData.shouldFilterOut(sbn)) {
- if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
- return false;
- }
-
- boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
-
- if (!inUse && !isDozing()) {
- if (DEBUG) {
- Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
- }
- return false;
- }
-
- if (!isDozing() && mNotificationData.shouldSuppressScreenOn(sbn.getKey())) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
- return false;
- }
-
- if (isDozing() && mNotificationData.shouldSuppressScreenOff(sbn.getKey())) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
- return false;
- }
-
- if (entry.hasJustLaunchedFullScreenIntent()) {
- if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
- return false;
- }
-
- if (isSnoozedPackage(sbn)) {
- if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
- return false;
- }
-
- // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
- int importanceLevel = isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
- : NotificationManager.IMPORTANCE_HIGH;
- if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
- if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
- return false;
- }
-
- if (mIsOccluded && !isDozing()) {
- boolean devicePublic = mLockscreenUserManager.
- isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
- boolean userPublic = devicePublic
- || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId());
- boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry);
- if (userPublic && needsRedaction) {
- return false;
- }
- }
-
- if (sbn.getNotification().fullScreenIntent != null) {
- if (mAccessibilityManager.isTouchExplorationEnabled()) {
- if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
- return false;
- } else if (mDozing) {
- // We never want heads up when we are dozing.
- return false;
- } else {
- // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
- return !mStatusBarKeyguardViewManager.isShowing()
- || mStatusBarKeyguardViewManager.isOccluded();
- }
- }
-
- // Don't peek notifications that are suppressed due to group alert behavior
- if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
- if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
- return false;
- }
-
- return true;
- }
-
/**
* @return Whether the security bouncer from Keyguard is showing.
*/
@@ -6233,17 +5569,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return contextForUser.getPackageManager();
}
- @Override
- public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- mUiOffloadThread.submit(() -> {
- try {
- mBarService.onNotificationExpansionChanged(key, userAction, expanded);
- } catch (RemoteException e) {
- // Ignore.
- }
- });
- }
-
public boolean isKeyguardSecure() {
if (mStatusBarKeyguardViewManager == null) {
// startKeyguard() hasn't been called yet, so we don't know.
@@ -6291,20 +5616,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@Override
- public NotificationData getNotificationData() {
- return mNotificationData;
- }
-
- @Override
public Handler getHandler() {
return mHandler;
}
- @Override
- public RankingMap getLatestRankingMap() {
- return mLatestRankingMap;
- }
-
private final NotificationInfo.CheckSaveListener mCheckSaveListener =
(Runnable saveImportance, StatusBarNotification sbn) -> {
// If the user has security enabled, show challenge if the setting is changed.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
new file mode 100644
index 000000000000..dcd0c8393756
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.UiOffloadThread;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationEntryManagerTest extends SysuiTestCase {
+ private static final String TEST_PACKAGE_NAME = "test";
+ private static final int TEST_UID = 0;
+
+ @Mock private NotificationPresenter mPresenter;
+ @Mock private ExpandableNotificationRow mRow;
+ @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock private NotificationGroupManager mGroupManager;
+ @Mock private NotificationGutsManager mGutsManager;
+ @Mock private NotificationRemoteInputManager mRemoteInputManager;
+ @Mock private NotificationMediaManager mMediaManager;
+ @Mock private ForegroundServiceController mForegroundServiceController;
+ @Mock private NotificationListener mNotificationListener;
+ @Mock private MetricsLogger mMetricsLogger;
+ @Mock private DeviceProvisionedController mDeviceProvisionedController;
+ @Mock private NotificationStackScrollLayout mStackScroller;
+ @Mock private NotificationEntryManager.Callback mCallback;
+ @Mock private VisualStabilityManager mVisualStabilityManager;
+ @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private NotificationListenerService.RankingMap mRankingMap;
+ @Mock private RemoteInputController mRemoteInputController;
+ @Mock private IStatusBarService mBarService;
+
+ private NotificationData.Entry mEntry;
+ private StatusBarNotification mSbn;
+ private Handler mHandler;
+ private TestableNotificationEntryManager mEntryManager;
+ private CountDownLatch mCountDownLatch;
+
+ private class TestableNotificationEntryManager extends NotificationEntryManager {
+ private final CountDownLatch mCountDownLatch;
+
+ public TestableNotificationEntryManager(
+ NotificationLockscreenUserManager lockscreenUserManager,
+ NotificationGroupManager groupManager,
+ NotificationGutsManager gutsManager,
+ NotificationRemoteInputManager remoteInputManager,
+ NotificationMediaManager mediaManager,
+ ForegroundServiceController foregroundServiceController,
+ NotificationListener notificationListener,
+ MetricsLogger metricsLogger,
+ DeviceProvisionedController deviceProvisionedController,
+ UiOffloadThread uiOffloadThread, Context context,
+ IStatusBarService barService) {
+ super(lockscreenUserManager, groupManager, gutsManager, remoteInputManager,
+ mediaManager, foregroundServiceController, notificationListener, metricsLogger,
+ deviceProvisionedController, uiOffloadThread, context);
+ mBarService = barService;
+ mCountDownLatch = new CountDownLatch(1);
+ mUseHeadsUp = true;
+ }
+
+ @Override
+ public void onAsyncInflationFinished(NotificationData.Entry entry) {
+ super.onAsyncInflationFinished(entry);
+
+ mCountDownLatch.countDown();
+ }
+
+ public CountDownLatch getCountDownLatch() {
+ return mCountDownLatch;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mHandler = new Handler(Looper.getMainLooper());
+ mCountDownLatch = new CountDownLatch(1);
+
+ when(mPresenter.getHandler()).thenReturn(mHandler);
+ when(mPresenter.getEntryManager()).thenReturn(mEntryManager);
+ when(mPresenter.getNotificationLockscreenUserManager()).thenReturn(mLockscreenUserManager);
+ when(mPresenter.getGroupManager()).thenReturn(mGroupManager);
+ when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
+ // Necessary for layout inflation.
+ when(mStackScroller.generateLayoutParams(any())).thenReturn(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ Notification.Builder n = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text");
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
+ 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+ mEntry = new NotificationData.Entry(mSbn);
+ mEntry.expandedIcon = mock(StatusBarIconView.class);
+
+ mEntryManager = new TestableNotificationEntryManager(mLockscreenUserManager,
+ mGroupManager, mGutsManager, mRemoteInputManager, mMediaManager,
+ mForegroundServiceController, mNotificationListener, mMetricsLogger,
+ mDeviceProvisionedController, mDependency.get(UiOffloadThread.class), mContext,
+ mBarService);
+ mEntryManager.setUpWithPresenter(mPresenter, mStackScroller, mCallback,
+ mVisualStabilityManager, mHeadsUpManager);
+ }
+
+ @Test
+ public void testAddNotification() throws Exception {
+ com.android.systemui.util.Assert.isNotMainThread();
+
+ doAnswer(invocation -> {
+ mCountDownLatch.countDown();
+ return null;
+ }).when(mCallback).onBindRow(any(), any(), any(), any());
+
+ // Post on main thread, otherwise we will be stuck waiting here for the inflation finished
+ // callback forever, since it won't execute until the tests ends.
+ mHandler.post(() -> {
+ mEntryManager.addNotification(mSbn, mRankingMap);
+ });
+ assertTrue(mCountDownLatch.await(1, TimeUnit.MINUTES));
+ assertTrue(mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES));
+ waitForIdleSync(mHandler);
+
+ // Check that no inflation error occurred.
+ verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+ any(), anyInt());
+ verify(mForegroundServiceController).addNotification(eq(mSbn), anyInt());
+
+ // Row inflation:
+ ArgumentCaptor<NotificationData.Entry> entryCaptor = ArgumentCaptor.forClass(
+ NotificationData.Entry.class);
+ verify(mCallback).onBindRow(entryCaptor.capture(), any(), eq(mSbn), any());
+ NotificationData.Entry entry = entryCaptor.getValue();
+ verify(mRemoteInputManager).bindRow(entry.row);
+
+ // Row content inflation:
+ verify(mCallback).onNotificationAdded(entry);
+ verify(mPresenter).updateNotificationViews();
+
+ assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry);
+ assertNotNull(entry.row);
+ }
+
+ @Test
+ public void testUpdateNotification() throws Exception {
+ com.android.systemui.util.Assert.isNotMainThread();
+
+ mEntryManager.getNotificationData().add(mEntry);
+
+ mHandler.post(() -> {
+ mEntryManager.updateNotification(mSbn, mRankingMap);
+ });
+ // Wait for content update.
+ mEntryManager.getCountDownLatch().await(1, TimeUnit.MINUTES);
+ waitForIdleSync(mHandler);
+
+ verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+ any(), anyInt());
+
+ verify(mRemoteInputManager).onUpdateNotification(mEntry);
+ verify(mPresenter).updateNotificationViews();
+ verify(mForegroundServiceController).updateNotification(eq(mSbn), anyInt());
+ verify(mCallback).onNotificationUpdated(mSbn);
+ assertNotNull(mEntry.row);
+ }
+
+ @Test
+ public void testRemoveNotification() throws Exception {
+ com.android.systemui.util.Assert.isNotMainThread();
+
+ mEntry.row = mRow;
+ mEntryManager.getNotificationData().add(mEntry);
+
+ mHandler.post(() -> {
+ mEntryManager.removeNotification(mSbn.getKey(), mRankingMap);
+ });
+ waitForIdleSync(mHandler);
+
+ verify(mBarService, never()).onNotificationError(any(), any(), anyInt(), anyInt(), anyInt(),
+ any(), anyInt());
+
+ verify(mMediaManager).onNotificationRemoved(mSbn.getKey());
+ verify(mRemoteInputManager).onRemoveNotification(mEntry);
+ verify(mForegroundServiceController).removeNotification(mSbn);
+ verify(mStackScroller).cleanUpViewState(mRow);
+ verify(mPresenter).updateNotificationViews();
+ verify(mCallback).onNotificationRemoved(mSbn.getKey(), mSbn);
+ verify(mRow).setRemoved();
+
+ assertNull(mEntryManager.getNotificationData().get(mSbn.getKey()));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index ccc300625c8b..b71f51089fe7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -57,6 +57,7 @@ public class NotificationListenerTest extends SysuiTestCase {
private Set<String> mKeysKeptForRemoteInput;
private NotificationData mNotificationData;
private NotificationRemoteInputManager mRemoteInputManager;
+ private NotificationEntryManager mEntryManager;
@Before
public void setUp() {
@@ -65,10 +66,12 @@ public class NotificationListenerTest extends SysuiTestCase {
mNotificationData = mock(NotificationData.class);
mRanking = mock(NotificationListenerService.RankingMap.class);
mRemoteInputManager = mock(NotificationRemoteInputManager.class);
+ mEntryManager = mock(NotificationEntryManager.class);
mKeysKeptForRemoteInput = new HashSet<>();
when(mPresenter.getHandler()).thenReturn(mHandler);
- when(mPresenter.getNotificationData()).thenReturn(mNotificationData);
+ when(mPresenter.getEntryManager()).thenReturn(mEntryManager);
+ when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
when(mRemoteInputManager.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput);
mListener = new NotificationListener(mRemoteInputManager, mContext);
@@ -82,7 +85,7 @@ public class NotificationListenerTest extends SysuiTestCase {
public void testNotificationAddCallsAddNotification() {
mListener.onNotificationPosted(mSbn, mRanking);
waitForIdleSync(mHandler);
- verify(mPresenter).addNotification(mSbn, mRanking);
+ verify(mEntryManager).addNotification(mSbn, mRanking);
}
@Test
@@ -98,14 +101,14 @@ public class NotificationListenerTest extends SysuiTestCase {
when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationData.Entry(mSbn));
mListener.onNotificationPosted(mSbn, mRanking);
waitForIdleSync(mHandler);
- verify(mPresenter).updateNotification(mSbn, mRanking);
+ verify(mEntryManager).updateNotification(mSbn, mRanking);
}
@Test
public void testNotificationRemovalCallsRemoveNotification() {
mListener.onNotificationRemoved(mSbn, mRanking);
waitForIdleSync(mHandler);
- verify(mPresenter).removeNotification(mSbn.getKey(), mRanking);
+ verify(mEntryManager).removeNotification(mSbn.getKey(), mRanking);
}
@Test
@@ -113,6 +116,6 @@ public class NotificationListenerTest extends SysuiTestCase {
mListener.onNotificationRankingUpdate(mRanking);
waitForIdleSync(mHandler);
// RankingMap may be modified by plugins.
- verify(mPresenter).updateNotificationRanking(any());
+ verify(mEntryManager).updateNotificationRanking(any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 40459952dc95..e4368d8b2f2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -55,6 +55,7 @@ import org.junit.runner.RunWith;
@TestableLooper.RunWithLooper
public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
private NotificationPresenter mPresenter;
+ private NotificationEntryManager mEntryManager;
private TestNotificationLockscreenUserManager mLockscreenUserManager;
private DeviceProvisionedController mDeviceProvisionedController;
private int mCurrentUserId;
@@ -64,27 +65,30 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@Before
public void setUp() {
mUserManager = mock(UserManager.class);
- mContext.addMockSystemService(UserManager.class, mUserManager);
- mHandler = new Handler(Looper.getMainLooper());
+ mPresenter = mock(NotificationPresenter.class);
+ mEntryManager = mock(NotificationEntryManager.class);
mDependency.injectMockDependency(DeviceProvisionedController.class);
mDeviceProvisionedController = mDependency.get(DeviceProvisionedController.class);
- mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
- mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
- mLockscreenUserManager);
+
+ mHandler = new Handler(Looper.getMainLooper());
+ mContext.addMockSystemService(UserManager.class, mUserManager);
+ mCurrentUserId = ActivityManager.getCurrentUser();
when(mUserManager.getProfiles(mCurrentUserId)).thenReturn(Lists.newArrayList(
new UserInfo(mCurrentUserId, "", 0), new UserInfo(mCurrentUserId + 1, "", 0)));
-
- mPresenter = mock(NotificationPresenter.class);
when(mPresenter.getHandler()).thenReturn(mHandler);
+ when(mPresenter.getEntryManager()).thenReturn(mEntryManager);
+
+ mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
+ mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
+ mLockscreenUserManager);
mLockscreenUserManager.setUpWithPresenter(mPresenter);
- mCurrentUserId = ActivityManager.getCurrentUser();
}
@Test
public void testLockScreenShowNotificationsChangeUpdatesNotifications() {
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
- verify(mPresenter, times(1)).updateNotifications();
+ verify(mEntryManager, times(1)).updateNotifications();
}
@Test
@@ -123,7 +127,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
public void testSettingsObserverUpdatesNotifications() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
mLockscreenUserManager.getSettingsObserverForTest().onChange(false);
- verify(mPresenter, times(1)).updateNotifications();
+ verify(mEntryManager, times(1)).updateNotifications();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
index 142ce63afbd8..b0396ef5131e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLoggerTest.java
@@ -55,6 +55,7 @@ public class NotificationLoggerTest extends SysuiTestCase {
private static final int TEST_UID = 0;
@Mock private NotificationPresenter mPresenter;
+ @Mock private NotificationEntryManager mEntryManager;
@Mock private NotificationListener mListener;
@Mock private NotificationStackScrollLayout mStackScroller;
@Mock private IStatusBarService mBarService;
@@ -69,7 +70,8 @@ public class NotificationLoggerTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mPresenter.getNotificationData()).thenReturn(mNotificationData);
+ when(mPresenter.getEntryManager()).thenReturn(mEntryManager);
+ when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
0, new Notification(), UserHandle.CURRENT, null, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index b881c098a1a3..e943fd39cd5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -40,6 +40,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
private NotificationData.Entry mEntry;
@Mock private NotificationPresenter mPresenter;
+ @Mock private NotificationEntryManager mEntryManager;
@Mock private RemoteInputController.Delegate mDelegate;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationRemoteInputManager.Callback mCallback;
@@ -53,7 +54,8 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
mHandler = new Handler(Looper.getMainLooper());
when(mPresenter.getHandler()).thenReturn(mHandler);
- when(mPresenter.getLatestRankingMap()).thenReturn(mRanking);
+ when(mPresenter.getEntryManager()).thenReturn(mEntryManager);
+ when(mEntryManager.getLatestRankingMap()).thenReturn(mRanking);
mRemoteInputManager = new TestableNotificationRemoteInputManager(mLockscreenUserManager,
mContext);
@@ -97,7 +99,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty());
verify(mController).removeRemoteInput(mEntry, null);
- verify(mPresenter).removeNotification(mEntry.key, mRanking);
+ verify(mEntryManager).removeNotification(mEntry.key, mRanking);
}
private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 073286617979..db83655058c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -37,6 +37,7 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.StatusBarManager;
import android.app.trust.TrustManager;
+import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.metrics.LogMaker;
import android.os.Binder;
@@ -61,6 +62,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
+import com.android.systemui.ForegroundServiceController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.UiOffloadThread;
@@ -72,10 +74,16 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.NotificationGutsManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLogger;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -113,12 +121,19 @@ public class StatusBarTest extends SysuiTestCase {
ArrayList<Entry> mNotificationList;
FingerprintUnlockController mFingerprintUnlockController;
private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ private TestableNotificationEntryManager mEntryManager;
@Before
public void setup() throws Exception {
mContext.setTheme(R.style.Theme_SystemUI_Light);
mDependency.injectMockDependency(AssistManager.class);
mDependency.injectMockDependency(DeviceProvisionedController.class);
+ mDependency.injectMockDependency(NotificationGroupManager.class);
+ mDependency.injectMockDependency(NotificationGutsManager.class);
+ mDependency.injectMockDependency(NotificationRemoteInputManager.class);
+ mDependency.injectMockDependency(NotificationMediaManager.class);
+ mDependency.injectMockDependency(ForegroundServiceController.class);
+ mDependency.injectMockDependency(NotificationListener.class);
mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitorImpl.class));
CommandQueue commandQueue = mock(CommandQueue.class);
when(commandQueue.asBinder()).thenReturn(new Binder());
@@ -151,11 +166,16 @@ public class StatusBarTest extends SysuiTestCase {
UiOffloadThread.class));
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+
+ mNotificationListener = mDependency.get(NotificationListener.class);
+ mNotificationLogger = mDependency.get(NotificationLogger.class);
+ mEntryManager = new TestableNotificationEntryManager(mMetricsLogger,
+ mSystemServicesProxy, mPowerManager, mContext);
+
mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
- mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView,
- mBarService, mNotificationListener, mNotificationLogger, mScrimController,
- mFingerprintUnlockController);
+ mPowerManager, mNotificationPanelView, mBarService, mNotificationListener,
+ mNotificationLogger, mEntryManager, mScrimController, mFingerprintUnlockController);
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mContext.getComponents();
doAnswer(invocation -> {
@@ -170,6 +190,8 @@ public class StatusBarTest extends SysuiTestCase {
return null;
}).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
+ mEntryManager.setUpForTest(mStatusBar, mStackScroller, mStatusBar,
+ mock(VisualStabilityManager.class), mHeadsUpManager, mNotificationData);
mNotificationLogger.setUpWithPresenter(mStatusBar, mStackScroller);
when(mStackScroller.getActivatedChild()).thenReturn(null);
@@ -334,7 +356,7 @@ public class StatusBarTest extends SysuiTestCase {
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertTrue(mStatusBar.shouldPeek(entry, sbn));
+ assertTrue(mEntryManager.shouldPeek(entry, sbn));
}
@Test
@@ -355,7 +377,7 @@ public class StatusBarTest extends SysuiTestCase {
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertFalse(mStatusBar.shouldPeek(entry, sbn));
+ assertFalse(mEntryManager.shouldPeek(entry, sbn));
}
@Test
@@ -375,7 +397,7 @@ public class StatusBarTest extends SysuiTestCase {
UserHandle.of(0), null, 0);
NotificationData.Entry entry = new NotificationData.Entry(sbn);
- assertTrue(mStatusBar.shouldPeek(entry, sbn));
+ assertTrue(mEntryManager.shouldPeek(entry, sbn));
}
@Test
@@ -394,7 +416,7 @@ 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);
- assertFalse(mStatusBar.shouldPeek(entry, sbn));
+ assertFalse(mEntryManager.shouldPeek(entry, sbn));
}
@Test
public void testShouldPeek_suppressedScreenOff_dozing() {
@@ -412,7 +434,7 @@ 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);
- assertFalse(mStatusBar.shouldPeek(entry, sbn));
+ assertFalse(mEntryManager.shouldPeek(entry, sbn));
}
@Test
@@ -431,7 +453,7 @@ 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);
- assertTrue(mStatusBar.shouldPeek(entry, sbn));
+ assertTrue(mEntryManager.shouldPeek(entry, sbn));
}
@@ -564,25 +586,24 @@ public class StatusBarTest extends SysuiTestCase {
static class TestableStatusBar extends StatusBar {
public TestableStatusBar(StatusBarKeyguardViewManager man,
UnlockMethodCache unlock, KeyguardIndicationController key,
- NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd,
- PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView,
+ NotificationStackScrollLayout stack, HeadsUpManager hum,
+ PowerManager pm, NotificationPanelView panelView,
IStatusBarService barService, NotificationListener notificationListener,
- NotificationLogger notificationLogger, ScrimController scrimController,
+ NotificationLogger notificationLogger,
+ TestableNotificationEntryManager entryManager, ScrimController scrimController,
FingerprintUnlockController fingerprintUnlockController) {
mStatusBarKeyguardViewManager = man;
mUnlockMethodCache = unlock;
mKeyguardIndicationController = key;
mStackScroller = stack;
mHeadsUpManager = hum;
- mNotificationData = nd;
- mUseHeadsUp = true;
mPowerManager = pm;
- mSystemServicesProxy = ssp;
mNotificationPanel = panelView;
mBarService = barService;
mNotificationListener = notificationListener;
mNotificationLogger = notificationLogger;
mWakefulnessLifecycle = createAwakeWakefulnessLifecycle();
+ mEntryManager = entryManager;
mScrimController = scrimController;
mFingerprintUnlockController = fingerprintUnlockController;
}
@@ -606,5 +627,39 @@ public class StatusBarTest extends SysuiTestCase {
public void setUserSetupForTest(boolean userSetup) {
mUserSetup = userSetup;
}
+
+ }
+
+ private class TestableNotificationEntryManager extends NotificationEntryManager {
+
+ public TestableNotificationEntryManager(MetricsLogger metricsLogger,
+ SystemServicesProxy systemServicesProxy, PowerManager powerManager,
+ Context context) {
+ super(mDependency.get(NotificationLockscreenUserManager.class),
+ mDependency.get(NotificationGroupManager.class),
+ mDependency.get(NotificationGutsManager.class),
+ mDependency.get(NotificationRemoteInputManager.class),
+ mDependency.get(NotificationMediaManager.class),
+ mDependency.get(ForegroundServiceController.class),
+ mDependency.get(NotificationListener.class),
+ metricsLogger,
+ mDependency.get(DeviceProvisionedController.class),
+ mDependency.get(UiOffloadThread.class),
+ context);
+ mSystemServicesProxy = systemServicesProxy;
+ mPowerManager = powerManager;
+ }
+
+ public void setUpForTest(NotificationPresenter presenter,
+ NotificationStackScrollLayout stackScroller,
+ Callback callback,
+ VisualStabilityManager visualStabilityManager,
+ HeadsUpManager headsUpManager,
+ NotificationData notificationData) {
+ super.setUpWithPresenter(presenter, stackScroller, callback, visualStabilityManager,
+ headsUpManager);
+ mNotificationData = notificationData;
+ mUseHeadsUp = true;
+ }
}
}