Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7c53d355 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Moving some notification binder calls to worker thread

Change-Id: I16c4aded7b709282a9b3d0df64111a9a40366b50
parent 221823c3
Loading
Loading
Loading
Loading
+132 −186
Original line number Diff line number Diff line
@@ -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;

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

@@ -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
@@ -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 */
@@ -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:
@@ -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() {
@@ -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);
@@ -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);
@@ -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);
    }
}
+9 −33
Original line number Diff line number Diff line
@@ -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;
@@ -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.
 */
@@ -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);
        }
    }

@@ -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);
        }
    }
@@ -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) {
+14 −10
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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,
@@ -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));
            }