Loading packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java 0 → 100644 +129 −0 Original line number Diff line number Diff line /* * 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); } } } packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +12 −7 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ import android.content.Intent; 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 Loading @@ -26,7 +28,8 @@ import android.service.notification.NotificationListenerService; * 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 Loading Loading @@ -66,12 +69,6 @@ public interface NotificationPresenter { */ 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. Loading @@ -83,6 +80,14 @@ public interface NotificationPresenter { */ 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 Loading packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java 0 → 100644 +58 −0 Original line number Diff line number Diff line /* * 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); } packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +54 −0 Original line number Diff line number Diff line Loading @@ -21,17 +21,24 @@ import com.android.systemui.Dependency; 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<>(); Loading @@ -44,6 +51,53 @@ public class RemoteInputController { mDelegate = delegate; } /** * 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. * Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +36 −146 Original line number Diff line number Diff line Loading @@ -200,6 +200,7 @@ import com.android.systemui.statusbar.NotificationData; 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; Loading Loading @@ -249,6 +250,7 @@ import java.util.Collections; 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, Loading Loading @@ -816,14 +818,8 @@ public class StatusBar extends SystemUI implements DemoMode, } // 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( Loading Loading @@ -1516,13 +1512,19 @@ public class StatusBar extends SystemUI implements DemoMode, 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)) { Loading @@ -1536,11 +1538,11 @@ public class StatusBar extends SystemUI implements DemoMode, + 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 { Loading Loading @@ -1617,7 +1619,8 @@ public class StatusBar extends SystemUI implements DemoMode, } } protected void updateNotificationRanking(RankingMap ranking) { @Override public void updateNotificationRanking(RankingMap ranking) { mNotificationData.updateRanking(ranking); updateNotifications(); } Loading Loading @@ -1670,7 +1673,7 @@ public class StatusBar extends SystemUI implements DemoMode, newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); boolean updated = false; try { updateNotification(newSbn, null); updateNotificationInternal(newSbn, null); updated = true; } catch (InflationException e) { deferRemoval = false; Loading Loading @@ -5705,90 +5708,7 @@ public class StatusBar extends SystemUI implements DemoMode, } }; 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(), Loading Loading @@ -6075,51 +5995,6 @@ public class StatusBar extends SystemUI implements DemoMode, 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; Loading Loading @@ -6508,8 +6383,9 @@ public class StatusBar extends SystemUI implements DemoMode, 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(); Loading Loading @@ -6559,6 +6435,15 @@ public class StatusBar extends SystemUI implements DemoMode, setAreThereNotifications(); } @Override public void updateNotification(StatusBarNotification notification, RankingMap ranking) { try { updateNotificationInternal(notification, ranking); } catch (InflationException e) { handleInflationException(notification, e); } } protected void notifyHeadsUpGoingToSleep() { maybeEscalateHeadsUp(); } Loading Loading @@ -6755,6 +6640,11 @@ public class StatusBar extends SystemUI implements DemoMode, 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. Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java 0 → 100644 +129 −0 Original line number Diff line number Diff line /* * 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); } } }
packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +12 −7 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ import android.content.Intent; 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 Loading @@ -26,7 +28,8 @@ import android.service.notification.NotificationListenerService; * 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 Loading Loading @@ -66,12 +69,6 @@ public interface NotificationPresenter { */ 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. Loading @@ -83,6 +80,14 @@ public interface NotificationPresenter { */ 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 Loading
packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java 0 → 100644 +58 −0 Original line number Diff line number Diff line /* * 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); }
packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +54 −0 Original line number Diff line number Diff line Loading @@ -21,17 +21,24 @@ import com.android.systemui.Dependency; 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<>(); Loading @@ -44,6 +51,53 @@ public class RemoteInputController { mDelegate = delegate; } /** * 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. * Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +36 −146 Original line number Diff line number Diff line Loading @@ -200,6 +200,7 @@ import com.android.systemui.statusbar.NotificationData; 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; Loading Loading @@ -249,6 +250,7 @@ import java.util.Collections; 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, Loading Loading @@ -816,14 +818,8 @@ public class StatusBar extends SystemUI implements DemoMode, } // 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( Loading Loading @@ -1516,13 +1512,19 @@ public class StatusBar extends SystemUI implements DemoMode, 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)) { Loading @@ -1536,11 +1538,11 @@ public class StatusBar extends SystemUI implements DemoMode, + 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 { Loading Loading @@ -1617,7 +1619,8 @@ public class StatusBar extends SystemUI implements DemoMode, } } protected void updateNotificationRanking(RankingMap ranking) { @Override public void updateNotificationRanking(RankingMap ranking) { mNotificationData.updateRanking(ranking); updateNotifications(); } Loading Loading @@ -1670,7 +1673,7 @@ public class StatusBar extends SystemUI implements DemoMode, newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); boolean updated = false; try { updateNotification(newSbn, null); updateNotificationInternal(newSbn, null); updated = true; } catch (InflationException e) { deferRemoval = false; Loading Loading @@ -5705,90 +5708,7 @@ public class StatusBar extends SystemUI implements DemoMode, } }; 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(), Loading Loading @@ -6075,51 +5995,6 @@ public class StatusBar extends SystemUI implements DemoMode, 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; Loading Loading @@ -6508,8 +6383,9 @@ public class StatusBar extends SystemUI implements DemoMode, 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(); Loading Loading @@ -6559,6 +6435,15 @@ public class StatusBar extends SystemUI implements DemoMode, setAreThereNotifications(); } @Override public void updateNotification(StatusBarNotification notification, RankingMap ranking) { try { updateNotificationInternal(notification, ranking); } catch (InflationException e) { handleInflationException(notification, e); } } protected void notifyHeadsUpGoingToSleep() { maybeEscalateHeadsUp(); } Loading Loading @@ -6755,6 +6640,11 @@ public class StatusBar extends SystemUI implements DemoMode, 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. Loading