Loading src/com/android/launcher3/notification/NotificationListener.java +132 −186 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.launcher3.notification; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; Loading @@ -32,9 +33,10 @@ import android.text.TextUtils; import android.util.Log; import android.util.Pair; import androidx.annotation.AnyThread; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.SecureSettingsObserver; Loading @@ -44,6 +46,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * A {@link NotificationListenerService} that sends updates to its Loading @@ -59,16 +62,17 @@ public class NotificationListener extends NotificationListenerService { private static final int MSG_NOTIFICATION_POSTED = 1; private static final int MSG_NOTIFICATION_REMOVED = 2; private static final int MSG_NOTIFICATION_FULL_REFRESH = 3; private static final int MSG_CANCEL_NOTIFICATION = 4; private static final int MSG_RANKING_UPDATE = 5; private static NotificationListener sNotificationListenerInstance = null; private static NotificationsChangedListener sNotificationsChangedListener; private static StatusBarNotificationsChangedListener sStatusBarNotificationsChangedListener; private static boolean sIsConnected; private static boolean sIsCreated; private final Handler mWorkerHandler; private final Handler mUiHandler; private final Ranking mTempRanking = new Ranking(); /** Maps groupKey's to the corresponding group of notifications. */ private final Map<String, NotificationGroup> mNotificationGroupMap = new HashMap<>(); /** Maps keys to their corresponding current group key */ Loading @@ -79,53 +83,113 @@ public class NotificationListener extends NotificationListenerService { private SecureSettingsObserver mNotificationDotsObserver; private final Handler.Callback mWorkerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message message) { public NotificationListener() { mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage); mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage); sNotificationListenerInstance = this; } public static @Nullable NotificationListener getInstanceIfConnected() { return sIsConnected ? sNotificationListenerInstance : null; } public static void setNotificationsChangedListener(NotificationsChangedListener listener) { sNotificationsChangedListener = listener; NotificationListener notificationListener = getInstanceIfConnected(); if (notificationListener != null) { notificationListener.onNotificationFullRefresh(); } else { // User turned off dots globally, so we unbound this service; // tell the listener that there are no notifications to remove dots. MODEL_EXECUTOR.submit(() -> MAIN_EXECUTOR.submit(() -> listener.onNotificationFullRefresh(Collections.emptyList()))); } } public static void removeNotificationsChangedListener() { sNotificationsChangedListener = null; } private boolean handleWorkerMessage(Message message) { switch (message.what) { case MSG_NOTIFICATION_POSTED: mUiHandler.obtainMessage(message.what, message.obj).sendToTarget(); break; case MSG_NOTIFICATION_REMOVED: mUiHandler.obtainMessage(message.what, message.obj).sendToTarget(); break; case MSG_NOTIFICATION_POSTED: { StatusBarNotification sbn = (StatusBarNotification) message.obj; mUiHandler.obtainMessage(shouldBeFilteredOut(sbn) ? MSG_NOTIFICATION_REMOVED : MSG_NOTIFICATION_POSTED, toKeyPair(sbn)).sendToTarget(); return true; } case MSG_NOTIFICATION_REMOVED: { StatusBarNotification sbn = (StatusBarNotification) message.obj; mUiHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, toKeyPair(sbn)).sendToTarget(); NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey()); String key = sbn.getKey(); if (notificationGroup != null) { notificationGroup.removeChildKey(key); if (notificationGroup.isEmpty()) { if (key.equals(mLastKeyDismissedByLauncher)) { // Only cancel the group notification if launcher dismissed the // last child. cancelNotification(notificationGroup.getGroupSummaryKey()); } mNotificationGroupMap.remove(sbn.getGroupKey()); } } if (key.equals(mLastKeyDismissedByLauncher)) { mLastKeyDismissedByLauncher = null; } return true; } case MSG_NOTIFICATION_FULL_REFRESH: List<StatusBarNotification> activeNotifications; List<StatusBarNotification> activeNotifications = null; if (sIsConnected) { try { activeNotifications = filterNotifications(getActiveNotifications()); activeNotifications = Arrays.stream(getActiveNotifications()) .filter(this::shouldBeFilteredOut) .collect(Collectors.toList()); } catch (SecurityException ex) { Log.e(TAG, "SecurityException: failed to fetch notifications"); activeNotifications = new ArrayList<StatusBarNotification>(); activeNotifications = new ArrayList<>(); } } else { activeNotifications = new ArrayList<StatusBarNotification>(); activeNotifications = new ArrayList<>(); } mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget(); break; return true; case MSG_CANCEL_NOTIFICATION: { mLastKeyDismissedByLauncher = (String) message.obj; cancelNotification(mLastKeyDismissedByLauncher); return true; } case MSG_RANKING_UPDATE: { String[] keys = ((RankingMap) message.obj).getOrderedKeys(); for (StatusBarNotification sbn : getActiveNotifications(keys)) { updateGroupKeyIfNecessary(sbn); } return true; } }; } return false; } private final Handler.Callback mUiCallback = new Handler.Callback() { @Override public boolean handleMessage(Message message) { private boolean handleUiMessage(Message message) { switch (message.what) { case MSG_NOTIFICATION_POSTED: if (sNotificationsChangedListener != null) { NotificationPostedMsg msg = (NotificationPostedMsg) message.obj; sNotificationsChangedListener.onNotificationPosted(msg.packageUserKey, msg.notificationKey, msg.shouldBeFilteredOut); Pair<PackageUserKey, NotificationKeyData> msg = (Pair) message.obj; sNotificationsChangedListener.onNotificationPosted( msg.first, msg.second); } break; case MSG_NOTIFICATION_REMOVED: if (sNotificationsChangedListener != null) { Pair<PackageUserKey, NotificationKeyData> pair = (Pair<PackageUserKey, NotificationKeyData>) message.obj; sNotificationsChangedListener.onNotificationRemoved(pair.first, pair.second); Pair<PackageUserKey, NotificationKeyData> msg = (Pair) message.obj; sNotificationsChangedListener.onNotificationRemoved( msg.first, msg.second); } break; case MSG_NOTIFICATION_FULL_REFRESH: Loading @@ -137,57 +201,6 @@ public class NotificationListener extends NotificationListenerService { } return true; } }; public NotificationListener() { super(); mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), mWorkerCallback); mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback); sNotificationListenerInstance = this; } @Override public void onCreate() { super.onCreate(); sIsCreated = true; } @Override public void onDestroy() { super.onDestroy(); sIsCreated = false; } public static @Nullable NotificationListener getInstanceIfConnected() { return sIsConnected ? sNotificationListenerInstance : null; } public static void setNotificationsChangedListener(NotificationsChangedListener listener) { sNotificationsChangedListener = listener; NotificationListener notificationListener = getInstanceIfConnected(); if (notificationListener != null) { notificationListener.onNotificationFullRefresh(); } else if (!sIsCreated && sNotificationsChangedListener != null) { // User turned off dots globally, so we unbound this service; // tell the listener that there are no notifications to remove dots. sNotificationsChangedListener.onNotificationFullRefresh( Collections.<StatusBarNotification>emptyList()); } } public static void setStatusBarNotificationsChangedListener (StatusBarNotificationsChangedListener listener) { sStatusBarNotificationsChangedListener = listener; } public static void removeNotificationsChangedListener() { sNotificationsChangedListener = null; } public static void removeStatusBarNotificationsChangedListener() { sStatusBarNotificationsChangedListener = null; } @Override public void onListenerConnected() { Loading Loading @@ -217,84 +230,37 @@ public class NotificationListener extends NotificationListenerService { super.onListenerDisconnected(); sIsConnected = false; mNotificationDotsObserver.unregister(); onNotificationFullRefresh(); } @Override public void onNotificationPosted(final StatusBarNotification sbn) { super.onNotificationPosted(sbn); if (sbn == null) { // There is a bug in platform where we can get a null notification; just ignore it. return; } mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, new NotificationPostedMsg(sbn)) .sendToTarget(); if (sStatusBarNotificationsChangedListener != null) { sStatusBarNotificationsChangedListener.onNotificationPosted(sbn); } } /** * An object containing data to send to MSG_NOTIFICATION_POSTED targets. */ private class NotificationPostedMsg { final PackageUserKey packageUserKey; final NotificationKeyData notificationKey; final boolean shouldBeFilteredOut; NotificationPostedMsg(StatusBarNotification sbn) { packageUserKey = PackageUserKey.fromNotification(sbn); notificationKey = NotificationKeyData.fromNotification(sbn); shouldBeFilteredOut = shouldBeFilteredOut(sbn); if (sbn != null) { mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, sbn).sendToTarget(); } } @Override public void onNotificationRemoved(final StatusBarNotification sbn) { super.onNotificationRemoved(sbn); if (sbn == null) { // There is a bug in platform where we can get a null notification; just ignore it. return; if (sbn != null) { mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, sbn).sendToTarget(); } Pair<PackageUserKey, NotificationKeyData> packageUserKeyAndNotificationKey = new Pair<>(PackageUserKey.fromNotification(sbn), NotificationKeyData.fromNotification(sbn)); mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey) .sendToTarget(); if (sStatusBarNotificationsChangedListener != null) { sStatusBarNotificationsChangedListener.onNotificationRemoved(sbn); } NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey()); String key = sbn.getKey(); if (notificationGroup != null) { notificationGroup.removeChildKey(key); if (notificationGroup.isEmpty()) { if (key.equals(mLastKeyDismissedByLauncher)) { // Only cancel the group notification if launcher dismissed the last child. cancelNotification(notificationGroup.getGroupSummaryKey()); } mNotificationGroupMap.remove(sbn.getGroupKey()); } } if (key.equals(mLastKeyDismissedByLauncher)) { mLastKeyDismissedByLauncher = null; } } public void cancelNotificationFromLauncher(String key) { mLastKeyDismissedByLauncher = key; cancelNotification(key); } @Override public void onNotificationRankingUpdate(RankingMap rankingMap) { super.onNotificationRankingUpdate(rankingMap); String[] keys = rankingMap.getOrderedKeys(); for (StatusBarNotification sbn : getActiveNotifications(keys)) { updateGroupKeyIfNecessary(sbn); mWorkerHandler.obtainMessage(MSG_RANKING_UPDATE, rankingMap).sendToTarget(); } /** * Cancels a notification */ @AnyThread public void cancelNotificationFromLauncher(String key) { mWorkerHandler.obtainMessage(MSG_CANCEL_NOTIFICATION, key).sendToTarget(); } @WorkerThread private void updateGroupKeyIfNecessary(StatusBarNotification sbn) { String childKey = sbn.getKey(); String oldGroupKey = mNotificationGroupKeyMap.get(childKey); Loading Loading @@ -328,43 +294,23 @@ public class NotificationListener extends NotificationListenerService { } } /** This makes a potentially expensive binder call and should be run on a background thread. */ /** * This makes a potentially expensive binder call and should be run on a background thread. */ @WorkerThread public List<StatusBarNotification> getNotificationsForKeys(List<NotificationKeyData> keys) { StatusBarNotification[] notifications = NotificationListener.this .getActiveNotifications(NotificationKeyData.extractKeysOnly(keys) .toArray(new String[keys.size()])); return notifications == null ? Collections.<StatusBarNotification>emptyList() : Arrays.asList(notifications); StatusBarNotification[] notifications = getActiveNotifications( keys.stream().map(n -> n.notificationKey).toArray(String[]::new)); return notifications == null ? Collections.emptyList() : Arrays.asList(notifications); } /** * Filter out notifications that don't have an intent * or are headers for grouped notifications. * * @see #shouldBeFilteredOut(StatusBarNotification) * Returns true for notifications that don't have an intent * or are headers for grouped notifications and should be filtered out. */ private List<StatusBarNotification> filterNotifications( StatusBarNotification[] notifications) { if (notifications == null) return null; IntSet removedNotifications = new IntSet(); for (int i = 0; i < notifications.length; i++) { if (shouldBeFilteredOut(notifications[i])) { removedNotifications.add(i); } } List<StatusBarNotification> filteredNotifications = new ArrayList<>( notifications.length - removedNotifications.size()); for (int i = 0; i < notifications.length; i++) { if (!removedNotifications.contains(i)) { filteredNotifications.add(notifications[i]); } } return filteredNotifications; } @WorkerThread private boolean shouldBeFilteredOut(StatusBarNotification sbn) { Notification notification = sbn.getNotification(); updateGroupKeyIfNecessary(sbn); getCurrentRanking().getRanking(sbn.getKey(), mTempRanking); Loading @@ -385,16 +331,16 @@ public class NotificationListener extends NotificationListenerService { return (isGroupHeader || missingTitleAndText); } private static Pair<PackageUserKey, NotificationKeyData> toKeyPair(StatusBarNotification sbn) { return Pair.create(PackageUserKey.fromNotification(sbn), NotificationKeyData.fromNotification(sbn)); } public interface NotificationsChangedListener { void onNotificationPosted(PackageUserKey postedPackageUserKey, NotificationKeyData notificationKey, boolean shouldBeFilteredOut); NotificationKeyData notificationKey); void onNotificationRemoved(PackageUserKey removedPackageUserKey, NotificationKeyData notificationKey); void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications); } public interface StatusBarNotificationsChangedListener { void onNotificationPosted(StatusBarNotification sbn); void onNotificationRemoved(StatusBarNotification sbn); } } src/com/android/launcher3/popup/PopupDataProvider.java +9 −33 Original line number Diff line number Diff line Loading @@ -20,13 +20,15 @@ import android.content.ComponentName; import android.service.notification.StatusBarNotification; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.ShortcutUtil; Loading @@ -39,13 +41,9 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * Provides data for the popup menu that appears after long-clicking on apps. */ Loading Loading @@ -76,28 +74,14 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan @Override public void onNotificationPosted(PackageUserKey postedPackageUserKey, NotificationKeyData notificationKey, boolean shouldBeFilteredOut) { NotificationKeyData notificationKey) { DotInfo dotInfo = mPackageUserToDotInfos.get(postedPackageUserKey); boolean dotShouldBeRefreshed; if (dotInfo == null) { if (!shouldBeFilteredOut) { DotInfo newDotInfo = new DotInfo(); newDotInfo.addOrUpdateNotificationKey(notificationKey); mPackageUserToDotInfos.put(postedPackageUserKey, newDotInfo); dotShouldBeRefreshed = true; } else { dotShouldBeRefreshed = false; } } else { dotShouldBeRefreshed = shouldBeFilteredOut ? dotInfo.removeNotificationKey(notificationKey) : dotInfo.addOrUpdateNotificationKey(notificationKey); if (dotInfo.getNotificationKeys().size() == 0) { mPackageUserToDotInfos.remove(postedPackageUserKey); } dotInfo = new DotInfo(); mPackageUserToDotInfos.put(postedPackageUserKey, dotInfo); } if (dotShouldBeRefreshed) { updateNotificationDots(t -> postedPackageUserKey.equals(t)); if (dotInfo.addOrUpdateNotificationKey(notificationKey)) { updateNotificationDots(postedPackageUserKey::equals); } } Loading @@ -109,7 +93,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan if (oldDotInfo.getNotificationKeys().size() == 0) { mPackageUserToDotInfos.remove(removedPackageUserKey); } updateNotificationDots(t -> removedPackageUserKey.equals(t)); updateNotificationDots(removedPackageUserKey::equals); trimNotifications(mPackageUserToDotInfos); } } Loading Loading @@ -195,14 +179,6 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan : getNotificationsForItem(info, dotInfo.getNotificationKeys()); } /** This makes a potentially expensive binder call and should be run on a background thread. */ public @NonNull List<StatusBarNotification> getStatusBarNotificationsForKeys( List<NotificationKeyData> notificationKeys) { NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); return notificationListener == null ? Collections.EMPTY_LIST : notificationListener.getNotificationsForKeys(notificationKeys); } public void cancelNotification(String notificationKey) { NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); if (notificationListener == null) { Loading src/com/android/launcher3/popup/PopupPopulator.java +14 −10 Original line number Diff line number Diff line Loading @@ -20,7 +20,9 @@ import android.content.ComponentName; import android.content.pm.ShortcutInfo; import android.os.Handler; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; Loading @@ -28,6 +30,7 @@ import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.util.PackageUserKey; Loading @@ -37,9 +40,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.util.stream.Collectors; /** * Contains logic relevant to populating a {@link PopupContainerWithArrow}. In particular, Loading Loading @@ -130,12 +131,15 @@ public class PopupPopulator { final UserHandle user = originalInfo.user; return () -> { if (!notificationKeys.isEmpty()) { List<StatusBarNotification> notifications = launcher.getPopupDataProvider() .getStatusBarNotificationsForKeys(notificationKeys); List<NotificationInfo> infos = new ArrayList<>(notifications.size()); for (int i = 0; i < notifications.size(); i++) { StatusBarNotification notification = notifications.get(i); infos.add(new NotificationInfo(launcher, notification)); NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); final List<NotificationInfo> infos; if (notificationListener == null) { infos = Collections.emptyList(); } else { infos = notificationListener.getNotificationsForKeys(notificationKeys).stream() .map(sbn -> new NotificationInfo(launcher, sbn)) .collect(Collectors.toList()); } uiHandler.post(() -> container.applyNotificationInfos(infos)); } Loading Loading
src/com/android/launcher3/notification/NotificationListener.java +132 −186 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.launcher3.notification; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; Loading @@ -32,9 +33,10 @@ import android.text.TextUtils; import android.util.Log; import android.util.Pair; import androidx.annotation.AnyThread; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.SecureSettingsObserver; Loading @@ -44,6 +46,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * A {@link NotificationListenerService} that sends updates to its Loading @@ -59,16 +62,17 @@ public class NotificationListener extends NotificationListenerService { private static final int MSG_NOTIFICATION_POSTED = 1; private static final int MSG_NOTIFICATION_REMOVED = 2; private static final int MSG_NOTIFICATION_FULL_REFRESH = 3; private static final int MSG_CANCEL_NOTIFICATION = 4; private static final int MSG_RANKING_UPDATE = 5; private static NotificationListener sNotificationListenerInstance = null; private static NotificationsChangedListener sNotificationsChangedListener; private static StatusBarNotificationsChangedListener sStatusBarNotificationsChangedListener; private static boolean sIsConnected; private static boolean sIsCreated; private final Handler mWorkerHandler; private final Handler mUiHandler; private final Ranking mTempRanking = new Ranking(); /** Maps groupKey's to the corresponding group of notifications. */ private final Map<String, NotificationGroup> mNotificationGroupMap = new HashMap<>(); /** Maps keys to their corresponding current group key */ Loading @@ -79,53 +83,113 @@ public class NotificationListener extends NotificationListenerService { private SecureSettingsObserver mNotificationDotsObserver; private final Handler.Callback mWorkerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message message) { public NotificationListener() { mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage); mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage); sNotificationListenerInstance = this; } public static @Nullable NotificationListener getInstanceIfConnected() { return sIsConnected ? sNotificationListenerInstance : null; } public static void setNotificationsChangedListener(NotificationsChangedListener listener) { sNotificationsChangedListener = listener; NotificationListener notificationListener = getInstanceIfConnected(); if (notificationListener != null) { notificationListener.onNotificationFullRefresh(); } else { // User turned off dots globally, so we unbound this service; // tell the listener that there are no notifications to remove dots. MODEL_EXECUTOR.submit(() -> MAIN_EXECUTOR.submit(() -> listener.onNotificationFullRefresh(Collections.emptyList()))); } } public static void removeNotificationsChangedListener() { sNotificationsChangedListener = null; } private boolean handleWorkerMessage(Message message) { switch (message.what) { case MSG_NOTIFICATION_POSTED: mUiHandler.obtainMessage(message.what, message.obj).sendToTarget(); break; case MSG_NOTIFICATION_REMOVED: mUiHandler.obtainMessage(message.what, message.obj).sendToTarget(); break; case MSG_NOTIFICATION_POSTED: { StatusBarNotification sbn = (StatusBarNotification) message.obj; mUiHandler.obtainMessage(shouldBeFilteredOut(sbn) ? MSG_NOTIFICATION_REMOVED : MSG_NOTIFICATION_POSTED, toKeyPair(sbn)).sendToTarget(); return true; } case MSG_NOTIFICATION_REMOVED: { StatusBarNotification sbn = (StatusBarNotification) message.obj; mUiHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, toKeyPair(sbn)).sendToTarget(); NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey()); String key = sbn.getKey(); if (notificationGroup != null) { notificationGroup.removeChildKey(key); if (notificationGroup.isEmpty()) { if (key.equals(mLastKeyDismissedByLauncher)) { // Only cancel the group notification if launcher dismissed the // last child. cancelNotification(notificationGroup.getGroupSummaryKey()); } mNotificationGroupMap.remove(sbn.getGroupKey()); } } if (key.equals(mLastKeyDismissedByLauncher)) { mLastKeyDismissedByLauncher = null; } return true; } case MSG_NOTIFICATION_FULL_REFRESH: List<StatusBarNotification> activeNotifications; List<StatusBarNotification> activeNotifications = null; if (sIsConnected) { try { activeNotifications = filterNotifications(getActiveNotifications()); activeNotifications = Arrays.stream(getActiveNotifications()) .filter(this::shouldBeFilteredOut) .collect(Collectors.toList()); } catch (SecurityException ex) { Log.e(TAG, "SecurityException: failed to fetch notifications"); activeNotifications = new ArrayList<StatusBarNotification>(); activeNotifications = new ArrayList<>(); } } else { activeNotifications = new ArrayList<StatusBarNotification>(); activeNotifications = new ArrayList<>(); } mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget(); break; return true; case MSG_CANCEL_NOTIFICATION: { mLastKeyDismissedByLauncher = (String) message.obj; cancelNotification(mLastKeyDismissedByLauncher); return true; } case MSG_RANKING_UPDATE: { String[] keys = ((RankingMap) message.obj).getOrderedKeys(); for (StatusBarNotification sbn : getActiveNotifications(keys)) { updateGroupKeyIfNecessary(sbn); } return true; } }; } return false; } private final Handler.Callback mUiCallback = new Handler.Callback() { @Override public boolean handleMessage(Message message) { private boolean handleUiMessage(Message message) { switch (message.what) { case MSG_NOTIFICATION_POSTED: if (sNotificationsChangedListener != null) { NotificationPostedMsg msg = (NotificationPostedMsg) message.obj; sNotificationsChangedListener.onNotificationPosted(msg.packageUserKey, msg.notificationKey, msg.shouldBeFilteredOut); Pair<PackageUserKey, NotificationKeyData> msg = (Pair) message.obj; sNotificationsChangedListener.onNotificationPosted( msg.first, msg.second); } break; case MSG_NOTIFICATION_REMOVED: if (sNotificationsChangedListener != null) { Pair<PackageUserKey, NotificationKeyData> pair = (Pair<PackageUserKey, NotificationKeyData>) message.obj; sNotificationsChangedListener.onNotificationRemoved(pair.first, pair.second); Pair<PackageUserKey, NotificationKeyData> msg = (Pair) message.obj; sNotificationsChangedListener.onNotificationRemoved( msg.first, msg.second); } break; case MSG_NOTIFICATION_FULL_REFRESH: Loading @@ -137,57 +201,6 @@ public class NotificationListener extends NotificationListenerService { } return true; } }; public NotificationListener() { super(); mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), mWorkerCallback); mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback); sNotificationListenerInstance = this; } @Override public void onCreate() { super.onCreate(); sIsCreated = true; } @Override public void onDestroy() { super.onDestroy(); sIsCreated = false; } public static @Nullable NotificationListener getInstanceIfConnected() { return sIsConnected ? sNotificationListenerInstance : null; } public static void setNotificationsChangedListener(NotificationsChangedListener listener) { sNotificationsChangedListener = listener; NotificationListener notificationListener = getInstanceIfConnected(); if (notificationListener != null) { notificationListener.onNotificationFullRefresh(); } else if (!sIsCreated && sNotificationsChangedListener != null) { // User turned off dots globally, so we unbound this service; // tell the listener that there are no notifications to remove dots. sNotificationsChangedListener.onNotificationFullRefresh( Collections.<StatusBarNotification>emptyList()); } } public static void setStatusBarNotificationsChangedListener (StatusBarNotificationsChangedListener listener) { sStatusBarNotificationsChangedListener = listener; } public static void removeNotificationsChangedListener() { sNotificationsChangedListener = null; } public static void removeStatusBarNotificationsChangedListener() { sStatusBarNotificationsChangedListener = null; } @Override public void onListenerConnected() { Loading Loading @@ -217,84 +230,37 @@ public class NotificationListener extends NotificationListenerService { super.onListenerDisconnected(); sIsConnected = false; mNotificationDotsObserver.unregister(); onNotificationFullRefresh(); } @Override public void onNotificationPosted(final StatusBarNotification sbn) { super.onNotificationPosted(sbn); if (sbn == null) { // There is a bug in platform where we can get a null notification; just ignore it. return; } mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, new NotificationPostedMsg(sbn)) .sendToTarget(); if (sStatusBarNotificationsChangedListener != null) { sStatusBarNotificationsChangedListener.onNotificationPosted(sbn); } } /** * An object containing data to send to MSG_NOTIFICATION_POSTED targets. */ private class NotificationPostedMsg { final PackageUserKey packageUserKey; final NotificationKeyData notificationKey; final boolean shouldBeFilteredOut; NotificationPostedMsg(StatusBarNotification sbn) { packageUserKey = PackageUserKey.fromNotification(sbn); notificationKey = NotificationKeyData.fromNotification(sbn); shouldBeFilteredOut = shouldBeFilteredOut(sbn); if (sbn != null) { mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, sbn).sendToTarget(); } } @Override public void onNotificationRemoved(final StatusBarNotification sbn) { super.onNotificationRemoved(sbn); if (sbn == null) { // There is a bug in platform where we can get a null notification; just ignore it. return; if (sbn != null) { mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, sbn).sendToTarget(); } Pair<PackageUserKey, NotificationKeyData> packageUserKeyAndNotificationKey = new Pair<>(PackageUserKey.fromNotification(sbn), NotificationKeyData.fromNotification(sbn)); mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey) .sendToTarget(); if (sStatusBarNotificationsChangedListener != null) { sStatusBarNotificationsChangedListener.onNotificationRemoved(sbn); } NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey()); String key = sbn.getKey(); if (notificationGroup != null) { notificationGroup.removeChildKey(key); if (notificationGroup.isEmpty()) { if (key.equals(mLastKeyDismissedByLauncher)) { // Only cancel the group notification if launcher dismissed the last child. cancelNotification(notificationGroup.getGroupSummaryKey()); } mNotificationGroupMap.remove(sbn.getGroupKey()); } } if (key.equals(mLastKeyDismissedByLauncher)) { mLastKeyDismissedByLauncher = null; } } public void cancelNotificationFromLauncher(String key) { mLastKeyDismissedByLauncher = key; cancelNotification(key); } @Override public void onNotificationRankingUpdate(RankingMap rankingMap) { super.onNotificationRankingUpdate(rankingMap); String[] keys = rankingMap.getOrderedKeys(); for (StatusBarNotification sbn : getActiveNotifications(keys)) { updateGroupKeyIfNecessary(sbn); mWorkerHandler.obtainMessage(MSG_RANKING_UPDATE, rankingMap).sendToTarget(); } /** * Cancels a notification */ @AnyThread public void cancelNotificationFromLauncher(String key) { mWorkerHandler.obtainMessage(MSG_CANCEL_NOTIFICATION, key).sendToTarget(); } @WorkerThread private void updateGroupKeyIfNecessary(StatusBarNotification sbn) { String childKey = sbn.getKey(); String oldGroupKey = mNotificationGroupKeyMap.get(childKey); Loading Loading @@ -328,43 +294,23 @@ public class NotificationListener extends NotificationListenerService { } } /** This makes a potentially expensive binder call and should be run on a background thread. */ /** * This makes a potentially expensive binder call and should be run on a background thread. */ @WorkerThread public List<StatusBarNotification> getNotificationsForKeys(List<NotificationKeyData> keys) { StatusBarNotification[] notifications = NotificationListener.this .getActiveNotifications(NotificationKeyData.extractKeysOnly(keys) .toArray(new String[keys.size()])); return notifications == null ? Collections.<StatusBarNotification>emptyList() : Arrays.asList(notifications); StatusBarNotification[] notifications = getActiveNotifications( keys.stream().map(n -> n.notificationKey).toArray(String[]::new)); return notifications == null ? Collections.emptyList() : Arrays.asList(notifications); } /** * Filter out notifications that don't have an intent * or are headers for grouped notifications. * * @see #shouldBeFilteredOut(StatusBarNotification) * Returns true for notifications that don't have an intent * or are headers for grouped notifications and should be filtered out. */ private List<StatusBarNotification> filterNotifications( StatusBarNotification[] notifications) { if (notifications == null) return null; IntSet removedNotifications = new IntSet(); for (int i = 0; i < notifications.length; i++) { if (shouldBeFilteredOut(notifications[i])) { removedNotifications.add(i); } } List<StatusBarNotification> filteredNotifications = new ArrayList<>( notifications.length - removedNotifications.size()); for (int i = 0; i < notifications.length; i++) { if (!removedNotifications.contains(i)) { filteredNotifications.add(notifications[i]); } } return filteredNotifications; } @WorkerThread private boolean shouldBeFilteredOut(StatusBarNotification sbn) { Notification notification = sbn.getNotification(); updateGroupKeyIfNecessary(sbn); getCurrentRanking().getRanking(sbn.getKey(), mTempRanking); Loading @@ -385,16 +331,16 @@ public class NotificationListener extends NotificationListenerService { return (isGroupHeader || missingTitleAndText); } private static Pair<PackageUserKey, NotificationKeyData> toKeyPair(StatusBarNotification sbn) { return Pair.create(PackageUserKey.fromNotification(sbn), NotificationKeyData.fromNotification(sbn)); } public interface NotificationsChangedListener { void onNotificationPosted(PackageUserKey postedPackageUserKey, NotificationKeyData notificationKey, boolean shouldBeFilteredOut); NotificationKeyData notificationKey); void onNotificationRemoved(PackageUserKey removedPackageUserKey, NotificationKeyData notificationKey); void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications); } public interface StatusBarNotificationsChangedListener { void onNotificationPosted(StatusBarNotification sbn); void onNotificationRemoved(StatusBarNotification sbn); } }
src/com/android/launcher3/popup/PopupDataProvider.java +9 −33 Original line number Diff line number Diff line Loading @@ -20,13 +20,15 @@ import android.content.ComponentName; import android.service.notification.StatusBarNotification; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.ShortcutUtil; Loading @@ -39,13 +41,9 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * Provides data for the popup menu that appears after long-clicking on apps. */ Loading Loading @@ -76,28 +74,14 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan @Override public void onNotificationPosted(PackageUserKey postedPackageUserKey, NotificationKeyData notificationKey, boolean shouldBeFilteredOut) { NotificationKeyData notificationKey) { DotInfo dotInfo = mPackageUserToDotInfos.get(postedPackageUserKey); boolean dotShouldBeRefreshed; if (dotInfo == null) { if (!shouldBeFilteredOut) { DotInfo newDotInfo = new DotInfo(); newDotInfo.addOrUpdateNotificationKey(notificationKey); mPackageUserToDotInfos.put(postedPackageUserKey, newDotInfo); dotShouldBeRefreshed = true; } else { dotShouldBeRefreshed = false; } } else { dotShouldBeRefreshed = shouldBeFilteredOut ? dotInfo.removeNotificationKey(notificationKey) : dotInfo.addOrUpdateNotificationKey(notificationKey); if (dotInfo.getNotificationKeys().size() == 0) { mPackageUserToDotInfos.remove(postedPackageUserKey); } dotInfo = new DotInfo(); mPackageUserToDotInfos.put(postedPackageUserKey, dotInfo); } if (dotShouldBeRefreshed) { updateNotificationDots(t -> postedPackageUserKey.equals(t)); if (dotInfo.addOrUpdateNotificationKey(notificationKey)) { updateNotificationDots(postedPackageUserKey::equals); } } Loading @@ -109,7 +93,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan if (oldDotInfo.getNotificationKeys().size() == 0) { mPackageUserToDotInfos.remove(removedPackageUserKey); } updateNotificationDots(t -> removedPackageUserKey.equals(t)); updateNotificationDots(removedPackageUserKey::equals); trimNotifications(mPackageUserToDotInfos); } } Loading Loading @@ -195,14 +179,6 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan : getNotificationsForItem(info, dotInfo.getNotificationKeys()); } /** This makes a potentially expensive binder call and should be run on a background thread. */ public @NonNull List<StatusBarNotification> getStatusBarNotificationsForKeys( List<NotificationKeyData> notificationKeys) { NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); return notificationListener == null ? Collections.EMPTY_LIST : notificationListener.getNotificationsForKeys(notificationKeys); } public void cancelNotification(String notificationKey) { NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); if (notificationListener == null) { Loading
src/com/android/launcher3/popup/PopupPopulator.java +14 −10 Original line number Diff line number Diff line Loading @@ -20,7 +20,9 @@ import android.content.ComponentName; import android.content.pm.ShortcutInfo; import android.os.Handler; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; Loading @@ -28,6 +30,7 @@ import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.util.PackageUserKey; Loading @@ -37,9 +40,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.util.stream.Collectors; /** * Contains logic relevant to populating a {@link PopupContainerWithArrow}. In particular, Loading Loading @@ -130,12 +131,15 @@ public class PopupPopulator { final UserHandle user = originalInfo.user; return () -> { if (!notificationKeys.isEmpty()) { List<StatusBarNotification> notifications = launcher.getPopupDataProvider() .getStatusBarNotificationsForKeys(notificationKeys); List<NotificationInfo> infos = new ArrayList<>(notifications.size()); for (int i = 0; i < notifications.size(); i++) { StatusBarNotification notification = notifications.get(i); infos.add(new NotificationInfo(launcher, notification)); NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); final List<NotificationInfo> infos; if (notificationListener == null) { infos = Collections.emptyList(); } else { infos = notificationListener.getNotificationsForKeys(notificationKeys).stream() .map(sbn -> new NotificationInfo(launcher, sbn)) .collect(Collectors.toList()); } uiHandler.post(() -> container.applyNotificationInfos(infos)); } Loading