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

Commit d3616f77 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Split NotificationListener out from StatusBar."

parents 60e07a09 21bc05f7
Loading
Loading
Loading
Loading
+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);
        }
    }
}
+12 −7
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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.
@@ -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
+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);
}
+54 −0
Original line number Diff line number Diff line
@@ -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<>();
@@ -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.
     *
+36 −146
Original line number Diff line number Diff line
@@ -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;
@@ -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,
@@ -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(
@@ -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)) {
@@ -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 {
@@ -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();
    }
@@ -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;
@@ -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(),
@@ -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;

@@ -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();
@@ -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();
    }
@@ -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