Split NotificationListener out from StatusBar.
This decouples the part of status bar that listens for notifications
being posted, updated, or removed.
Bug: 63874929
Bug: 62602530
Test: runtest systemui
Test: Compile and run
Change-Id: I4b685d03f6641bdefa297b752c93e5728cb84132
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
new file mode 100644
index 0000000..6bcd174
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -0,0 +1,129 @@
+/*
+ * 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.RemoteInputController.processForRemoteInput;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
+import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
+
+/**
+ * This class handles listening to notification updates and passing them along to
+ * NotificationPresenter to be displayed to the user.
+ */
+public class NotificationListener extends NotificationListenerWithPlugins {
+ private static final String TAG = "NotificationListener";
+
+ private final NotificationPresenter mPresenter;
+ private final Context mContext;
+
+ public NotificationListener(NotificationPresenter presenter, Context context) {
+ mPresenter = presenter;
+ mContext = context;
+ }
+
+ @Override
+ public void onListenerConnected() {
+ if (DEBUG) Log.d(TAG, "onListenerConnected");
+ onPluginConnected();
+ final StatusBarNotification[] notifications = getActiveNotifications();
+ if (notifications == null) {
+ Log.w(TAG, "onListenerConnected unable to get active notifications.");
+ return;
+ }
+ final RankingMap currentRanking = getCurrentRanking();
+ mPresenter.getHandler().post(() -> {
+ for (StatusBarNotification sbn : notifications) {
+ mPresenter.addNotification(sbn, currentRanking);
+ }
+ });
+ }
+
+ @Override
+ public void onNotificationPosted(final StatusBarNotification sbn,
+ final RankingMap rankingMap) {
+ if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
+ if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
+ mPresenter.getHandler().post(() -> {
+ processForRemoteInput(sbn.getNotification(), mContext);
+ String key = sbn.getKey();
+ mPresenter.getKeysKeptForRemoteInput().remove(key);
+ boolean isUpdate = mPresenter.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,
+ // because children are automatically canceled by NoMan in that case.
+ if (!ENABLE_CHILD_NOTIFICATIONS
+ && mPresenter.getGroupManager().isChildInGroupWithSummary(sbn)) {
+ if (DEBUG) {
+ Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+ }
+
+ // Remove existing notification to avoid stale data.
+ if (isUpdate) {
+ mPresenter.removeNotification(key, rankingMap);
+ } else {
+ mPresenter.getNotificationData().updateRanking(rankingMap);
+ }
+ return;
+ }
+ if (isUpdate) {
+ mPresenter.updateNotification(sbn, rankingMap);
+ } else {
+ mPresenter.addNotification(sbn, rankingMap);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn,
+ final RankingMap rankingMap) {
+ 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));
+ }
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(final RankingMap rankingMap) {
+ if (DEBUG) Log.d(TAG, "onRankingUpdate");
+ if (rankingMap != null) {
+ RankingMap r = onPluginRankingUpdate(rankingMap);
+ mPresenter.getHandler().post(() -> mPresenter.updateNotificationRanking(r));
+ }
+ }
+
+ public void register() {
+ try {
+ registerAsSystemService(mContext,
+ new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
+ UserHandle.USER_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to register notification listener", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 8670887..4eca241 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -19,6 +19,8 @@
import android.os.Handler;
import android.service.notification.NotificationListenerService;
+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
@@ -26,7 +28,8 @@
* 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 {
+public interface NotificationPresenter extends NotificationUpdateHandler,
+ NotificationData.Environment {
/**
* Returns true if the presenter is not visible. For example, it may not be necessary to do
@@ -66,12 +69,6 @@
*/
void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
- // TODO: Create NotificationUpdateHandler and move this method to there.
- /**
- * Removes a notification.
- */
- void removeNotification(String key, NotificationListenerService.RankingMap ranking);
-
// TODO: Create NotificationEntryManager and move this method to there.
/**
* Gets the latest ranking map.
@@ -84,6 +81,14 @@
void onWorkChallengeChanged();
/**
+ * Notifications in this set are kept around when they were canceled in response to a remote
+ * input interaction. This allows us to show what you replied and allows you to continue typing
+ * into it.
+ */
+ // TODO: Create NotificationEntryManager and move this method to there.
+ Set<String> getKeysKeptForRemoteInput();
+
+ /**
* Called when the current user changes.
* @param newUserId new user id
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java
new file mode 100644
index 0000000..0044194
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java
@@ -0,0 +1,58 @@
+/*
+ * 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 android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+
+/**
+ * Interface for accepting notification updates from {@link NotificationListener}.
+ */
+public interface NotificationUpdateHandler {
+ /**
+ * Add a new notification and update the current notification ranking map.
+ *
+ * @param notification Notification to add
+ * @param ranking RankingMap to update with
+ */
+ void addNotification(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking);
+
+ /**
+ * Remove a notification and update the current notification ranking map.
+ *
+ * @param key Key identifying the notification to remove
+ * @param ranking RankingMap to update with
+ */
+ void removeNotification(String key, NotificationListenerService.RankingMap ranking);
+
+ /**
+ * Update a given notification and the current notification ranking map.
+ *
+ * @param notification Updated notification
+ * @param ranking RankingMap to update with
+ */
+ void updateNotification(StatusBarNotification notification,
+ NotificationListenerService.RankingMap ranking);
+
+ /**
+ * Update with a new notification ranking map.
+ *
+ * @param ranking RankingMap to update with
+ */
+ void updateNotificationRanking(NotificationListenerService.RankingMap ranking);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index ff6c775..97e3d22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -21,17 +21,24 @@
import com.android.systemui.statusbar.phone.StatusBarWindowManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
+import android.app.Notification;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.List;
/**
* Keeps track of the currently active {@link RemoteInputView}s.
*/
public class RemoteInputController {
+ private static final boolean ENABLE_REMOTE_INPUT =
+ SystemProperties.getBoolean("debug.enable_remote_input", true);
private final ArrayList<Pair<WeakReference<NotificationData.Entry>, Object>> mOpen
= new ArrayList<>();
@@ -45,6 +52,53 @@
}
/**
+ * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
+ * via first-class API.
+ *
+ * TODO: Remove once enough apps specify remote inputs on their own.
+ */
+ public static void processForRemoteInput(Notification n, Context context) {
+ if (!ENABLE_REMOTE_INPUT) {
+ return;
+ }
+
+ if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
+ (n.actions == null || n.actions.length == 0)) {
+ Notification.Action viableAction = null;
+ Notification.WearableExtender we = new Notification.WearableExtender(n);
+
+ List<Notification.Action> actions = we.getActions();
+ final int numActions = actions.size();
+
+ for (int i = 0; i < numActions; i++) {
+ Notification.Action action = actions.get(i);
+ if (action == null) {
+ continue;
+ }
+ RemoteInput[] remoteInputs = action.getRemoteInputs();
+ if (remoteInputs == null) {
+ continue;
+ }
+ for (RemoteInput ri : remoteInputs) {
+ if (ri.getAllowFreeFormInput()) {
+ viableAction = action;
+ break;
+ }
+ }
+ if (viableAction != null) {
+ break;
+ }
+ }
+
+ if (viableAction != null) {
+ Notification.Builder rebuilder = Notification.Builder.recoverBuilder(context, n);
+ rebuilder.setActions(viableAction);
+ rebuilder.build(); // will rewrite n
+ }
+ }
+ }
+
+ /**
* Adds a currently active remote input.
*
* @param entry the entry for which a remote input is now active.
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 7c33437..6071df0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -200,6 +200,7 @@
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.NotificationGutsManager;
import com.android.systemui.statusbar.NotificationInfo;
+import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -249,6 +250,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.Stack;
public class StatusBar extends SystemUI implements DemoMode,
@@ -819,14 +821,8 @@
}
// Set up the initial notification state.
- try {
- mNotificationListener.registerAsSystemService(mContext,
- new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
- UserHandle.USER_ALL);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to register notification listener", e);
- }
-
+ mNotificationListener = new NotificationListener(this, mContext);
+ mNotificationListener.register();
if (DEBUG) {
Log.d(TAG, String.format(
@@ -1519,13 +1515,19 @@
SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
}
- public void addNotification(StatusBarNotification notification, RankingMap ranking)
- throws InflationException {
+ @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 = createNotificationViews(notification);
+ 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)) {
@@ -1539,11 +1541,11 @@
+ key);
}
} else {
- // Stop screensaver if the notification has a full-screen intent.
+ // Stop screensaver if the notification has a fullscreen intent.
// (like an incoming phone call)
awakenDreams();
- // not immersive & a full-screen alert should be shown
+ // not immersive & a fullscreen alert should be shown
if (DEBUG)
Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
try {
@@ -1620,7 +1622,8 @@
}
}
- protected void updateNotificationRanking(RankingMap ranking) {
+ @Override
+ public void updateNotificationRanking(RankingMap ranking) {
mNotificationData.updateRanking(ranking);
updateNotifications();
}
@@ -1673,7 +1676,7 @@
newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
boolean updated = false;
try {
- updateNotification(newSbn, null);
+ updateNotificationInternal(newSbn, null);
updated = true;
} catch (InflationException e) {
deferRemoval = false;
@@ -5693,90 +5696,7 @@
}
};
- private final NotificationListenerWithPlugins mNotificationListener =
- new NotificationListenerWithPlugins() {
- @Override
- public void onListenerConnected() {
- if (DEBUG) Log.d(TAG, "onListenerConnected");
- onPluginConnected();
- final StatusBarNotification[] notifications = getActiveNotifications();
- if (notifications == null) {
- Log.w(TAG, "onListenerConnected unable to get active notifications.");
- return;
- }
- final RankingMap currentRanking = getCurrentRanking();
- mHandler.post(() -> {
- for (StatusBarNotification sbn : notifications) {
- try {
- addNotification(sbn, currentRanking);
- } catch (InflationException e) {
- handleInflationException(sbn, e);
- }
- }
- });
- }
-
- @Override
- public void onNotificationPosted(final StatusBarNotification sbn,
- final RankingMap rankingMap) {
- if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
- if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
- mHandler.post(() -> {
- processForRemoteInput(sbn.getNotification());
- String key = sbn.getKey();
- mKeysKeptForRemoteInput.remove(key);
- boolean isUpdate = mNotificationData.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,
- // because children are automatically canceled by NoMan in that case.
- if (!ENABLE_CHILD_NOTIFICATIONS
- && mGroupManager.isChildInGroupWithSummary(sbn)) {
- if (DEBUG) {
- Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
- }
-
- // Remove existing notification to avoid stale data.
- if (isUpdate) {
- removeNotification(key, rankingMap);
- } else {
- mNotificationData.updateRanking(rankingMap);
- }
- return;
- }
- try {
- if (isUpdate) {
- updateNotification(sbn, rankingMap);
- } else {
- addNotification(sbn, rankingMap);
- }
- } catch (InflationException e) {
- handleInflationException(sbn, e);
- }
- });
- }
- }
-
- @Override
- public void onNotificationRemoved(StatusBarNotification sbn,
- final RankingMap rankingMap) {
- if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
- if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
- final String key = sbn.getKey();
- mHandler.post(() -> removeNotification(key, rankingMap));
- }
- }
-
- @Override
- public void onNotificationRankingUpdate(final RankingMap rankingMap) {
- if (DEBUG) Log.d(TAG, "onRankingUpdate");
- if (rankingMap != null) {
- RankingMap r = onPluginRankingUpdate(rankingMap);
- mHandler.post(() -> updateNotificationRanking(r));
- }
- }
-
- };
+ protected NotificationListener mNotificationListener;
protected void notifyUserAboutHiddenNotifications() {
if (0 != Settings.Secure.getInt(mContext.getContentResolver(),
@@ -6063,51 +5983,6 @@
row.updateNotification(entry);
}
- /**
- * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
- * via first-class API.
- *
- * TODO: Remove once enough apps specify remote inputs on their own.
- */
- private void processForRemoteInput(Notification n) {
- if (!ENABLE_REMOTE_INPUT) return;
-
- if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
- (n.actions == null || n.actions.length == 0)) {
- Notification.Action viableAction = null;
- Notification.WearableExtender we = new Notification.WearableExtender(n);
-
- List<Notification.Action> actions = we.getActions();
- final int numActions = actions.size();
-
- for (int i = 0; i < numActions; i++) {
- Notification.Action action = actions.get(i);
- if (action == null) {
- continue;
- }
- RemoteInput[] remoteInputs = action.getRemoteInputs();
- if (remoteInputs == null) {
- continue;
- }
- for (RemoteInput ri : remoteInputs) {
- if (ri.getAllowFreeFormInput()) {
- viableAction = action;
- break;
- }
- }
- if (viableAction != null) {
- break;
- }
- }
-
- if (viableAction != null) {
- Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n);
- rebuilder.setActions(viableAction);
- rebuilder.build(); // will rewrite n
- }
- }
- }
-
public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
if (!isDeviceProvisioned()) return;
@@ -6496,8 +6371,9 @@
mScrimController.setNotificationCount(mStackScroller.getNotGoneChildCount());
}
- public void updateNotification(StatusBarNotification notification, RankingMap ranking)
- throws InflationException {
+ // 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();
@@ -6547,6 +6423,15 @@
setAreThereNotifications();
}
+ @Override
+ public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
+ try {
+ updateNotificationInternal(notification, ranking);
+ } catch (InflationException e) {
+ handleInflationException(notification, e);
+ }
+ }
+
protected void notifyHeadsUpGoingToSleep() {
maybeEscalateHeadsUp();
}
@@ -6743,6 +6628,11 @@
return mLatestRankingMap;
}
+ @Override
+ public Set<String> getKeysKeptForRemoteInput() {
+ return mKeysKeptForRemoteInput;
+ }
+
private final NotificationInfo.CheckSaveListener mCheckSaveListener =
(Runnable saveImportance, StatusBarNotification sbn) -> {
// If the user has security enabled, show challenge if the setting is changed.