diff options
author | Tony Wickham <twickham@google.com> | 2017-01-20 09:38:25 -0800 |
---|---|---|
committer | Tony Wickham <twickham@google.com> | 2017-01-25 17:36:31 -0800 |
commit | 9438ed414fdabadb4cd09da184867b1c44b91095 (patch) | |
tree | 9d84de236a89cde247b9de8f11408e815e6d6702 /src/com/android/launcher3/notification/NotificationListener.java | |
parent | f3d02e4716f89d14d9017851db9ad6141ad26875 (diff) | |
download | android_packages_apps_Trebuchet-9438ed414fdabadb4cd09da184867b1c44b91095.tar.gz android_packages_apps_Trebuchet-9438ed414fdabadb4cd09da184867b1c44b91095.tar.bz2 android_packages_apps_Trebuchet-9438ed414fdabadb4cd09da184867b1c44b91095.zip |
Add swipe-to-dismiss notifications in popup menu.
- Next secondary icon animates up to replace dismissed main notification
- Add padding around main notification so it always aligns with the
straight edges of the view (not the rounded corners); looks more
dismissable
- Notification view collapses as notifications are dismissed
- To mimic system notification behavior, we copy SwipeHelper,
FlingAnimationUtils, and Interpolators. We also apply elevation
to notifications and reveal a darker color beneath when dismissing.
Bug: 32410600
Change-Id: I9fbf10e73bb4996f17ef061c856efb013967d972
Diffstat (limited to 'src/com/android/launcher3/notification/NotificationListener.java')
-rw-r--r-- | src/com/android/launcher3/notification/NotificationListener.java | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java new file mode 100644 index 000000000..3f9a58413 --- /dev/null +++ b/src/com/android/launcher3/notification/NotificationListener.java @@ -0,0 +1,214 @@ +/* + * 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.launcher3.notification; + +import android.app.Notification; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.support.annotation.Nullable; +import android.support.v4.util.Pair; +import android.util.Log; + +import com.android.launcher3.LauncherModel; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.PackageUserKey; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A {@link NotificationListenerService} that sends updates to its + * {@link NotificationsChangedListener} when notifications are posted or canceled, + * as well and when this service first connects. An instance of NotificationListener, + * and its methods for getting notifications, can be obtained via {@link #getInstance()}. + */ +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 NotificationListener sNotificationListenerInstance = null; + private static NotificationsChangedListener sNotificationsChangedListener; + + private final Handler mWorkerHandler; + private final Handler mUiHandler; + + private Handler.Callback mWorkerCallback = new Handler.Callback() { + @Override + public boolean handleMessage(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_FULL_REFRESH: + final List<StatusBarNotification> activeNotifications + = filterNotifications(getActiveNotifications()); + mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget(); + break; + } + return true; + } + }; + + private Handler.Callback mUiCallback = new Handler.Callback() { + @Override + public boolean handleMessage(Message message) { + switch (message.what) { + case MSG_NOTIFICATION_POSTED: + if (sNotificationsChangedListener != null) { + Pair<PackageUserKey, String> pair + = (Pair<PackageUserKey, String>) message.obj; + sNotificationsChangedListener.onNotificationPosted(pair.first, pair.second); + } + break; + case MSG_NOTIFICATION_REMOVED: + if (sNotificationsChangedListener != null) { + Pair<PackageUserKey, String> pair + = (Pair<PackageUserKey, String>) message.obj; + sNotificationsChangedListener.onNotificationRemoved(pair.first, pair.second); + } + break; + case MSG_NOTIFICATION_FULL_REFRESH: + if (sNotificationsChangedListener != null) { + sNotificationsChangedListener.onNotificationFullRefresh( + (List<StatusBarNotification>) message.obj); + } + break; + } + return true; + } + }; + + public NotificationListener() { + super(); + mWorkerHandler = new Handler(LauncherModel.getWorkerLooper(), mWorkerCallback); + mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback); + } + + public static @Nullable NotificationListener getInstance() { + return sNotificationListenerInstance; + } + + public static void setNotificationsChangedListener(NotificationsChangedListener listener) { + if (!FeatureFlags.BADGE_ICONS) { + return; + } + sNotificationsChangedListener = listener; + + NotificationListener notificationListener = getInstance(); + if (notificationListener != null) { + notificationListener.onNotificationFullRefresh(); + } + } + + public static void removeNotificationsChangedListener() { + sNotificationsChangedListener = null; + } + + @Override + public void onListenerConnected() { + super.onListenerConnected(); + sNotificationListenerInstance = this; + onNotificationFullRefresh(); + } + + private void onNotificationFullRefresh() { + mWorkerHandler.obtainMessage(MSG_NOTIFICATION_FULL_REFRESH).sendToTarget(); + } + + @Override + public void onListenerDisconnected() { + super.onListenerDisconnected(); + sNotificationListenerInstance = null; + } + + @Override + public void onNotificationPosted(final StatusBarNotification sbn) { + super.onNotificationPosted(sbn); + if (!shouldBeFilteredOut(sbn.getNotification())) { + Pair<PackageUserKey, String> packageUserKeyAndNotificationKey + = new Pair<>(PackageUserKey.fromNotification(sbn), sbn.getKey()); + mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, packageUserKeyAndNotificationKey) + .sendToTarget(); + } + } + + @Override + public void onNotificationRemoved(final StatusBarNotification sbn) { + super.onNotificationRemoved(sbn); + if (!shouldBeFilteredOut(sbn.getNotification())) { + Pair<PackageUserKey, String> packageUserKeyAndNotificationKey + = new Pair<>(PackageUserKey.fromNotification(sbn), sbn.getKey()); + mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey) + .sendToTarget(); + } + } + + /** This makes a potentially expensive binder call and should be run on a background thread. */ + public List<StatusBarNotification> getNotificationsForKeys(String[] keys) { + StatusBarNotification[] notifications = NotificationListener.this + .getActiveNotifications(keys); + return notifications == null ? Collections.EMPTY_LIST : Arrays.asList(notifications); + } + + /** + * Filter out notifications that don't have an intent + * or are headers for grouped notifications. + * + * TODO: use the system concept of a badged notification instead + */ + private List<StatusBarNotification> filterNotifications( + StatusBarNotification[] notifications) { + if (notifications == null) return null; + Set<Integer> removedNotifications = new HashSet<>(); + for (int i = 0; i < notifications.length; i++) { + if (shouldBeFilteredOut(notifications[i].getNotification())) { + 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; + } + + private boolean shouldBeFilteredOut(Notification notification) { + boolean isGroupHeader = (notification.flags & Notification.FLAG_GROUP_SUMMARY) != 0; + return (notification.contentIntent == null || isGroupHeader); + } + + public interface NotificationsChangedListener { + void onNotificationPosted(PackageUserKey postedPackageUserKey, String notificationKey); + void onNotificationRemoved(PackageUserKey removedPackageUserKey, String notificationKey); + void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications); + } +} |