From 540913eadf39f1e8632d2b6f0bc33aa635214198 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Mon, 23 Jan 2017 11:47:51 -0800 Subject: Refactor DeepShortcutsContainer to PopupContainerWithArrow - Also added PopupItemView, which takes animation logic from DeepShortcutView, and which DeepShortcutView now extends. - Renamed ShortcutFilter to PopupPopulator, which has support for new item types (not yet used). Also moved populating logic (e.g. UpdateShortcutChild Runnable) to PopupPopulator. Bug: 32410600 Change-Id: Ib6e444ac7ca99c80ba438801c26e62d9542e0ad9 --- src/com/android/launcher3/badge/BadgeInfo.java | 2 +- .../android/launcher3/badge/NotificationInfo.java | 82 ++++++++ .../launcher3/badge/NotificationListener.java | 213 +++++++++++++++++++++ 3 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 src/com/android/launcher3/badge/NotificationInfo.java create mode 100644 src/com/android/launcher3/badge/NotificationListener.java (limited to 'src/com/android/launcher3/badge') diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java index 98d2277d0..4255c5132 100644 --- a/src/com/android/launcher3/badge/BadgeInfo.java +++ b/src/com/android/launcher3/badge/BadgeInfo.java @@ -30,7 +30,7 @@ public class BadgeInfo { private PackageUserKey mPackageUserKey; /** * The keys of the notifications that this badge represents. These keys can later be - * used to retrieve {@link com.android.launcher3.badging.NotificationInfo}'s. + * used to retrieve {@link NotificationInfo}'s. */ private Set mNotificationKeys; diff --git a/src/com/android/launcher3/badge/NotificationInfo.java b/src/com/android/launcher3/badge/NotificationInfo.java new file mode 100644 index 000000000..51f6a4f3a --- /dev/null +++ b/src/com/android/launcher3/badge/NotificationInfo.java @@ -0,0 +1,82 @@ +/* + * 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.badge; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.service.notification.StatusBarNotification; +import android.view.View; + +import com.android.launcher3.Launcher; +import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.util.PackageUserKey; + +/** + * An object that contains relevant information from a {@link StatusBarNotification}. This should + * only be created when we need to show the notification contents on the UI; until then, a + * {@link com.android.launcher3.badge.BadgeInfo} with only the notification key should + * be passed around, and then this can be constructed using the StatusBarNotification from + * {@link NotificationListener#getNotificationsForKeys(String[])}. + */ +public class NotificationInfo implements View.OnClickListener { + + public final PackageUserKey packageUserKey; + public final String notificationKey; + public final CharSequence title; + public final CharSequence text; + public final Drawable iconDrawable; + public final PendingIntent intent; + public final boolean autoCancel; + + /** + * Extracts the data that we need from the StatusBarNotification. + */ + public NotificationInfo(Context context, StatusBarNotification notification) { + packageUserKey = PackageUserKey.fromNotification(notification); + notificationKey = notification.getKey(); + title = notification.getNotification().extras.getCharSequence(Notification.EXTRA_TITLE); + text = notification.getNotification().extras.getCharSequence(Notification.EXTRA_TEXT); + Icon icon = notification.getNotification().getLargeIcon(); + if (icon == null) { + icon = notification.getNotification().getSmallIcon(); + iconDrawable = icon.loadDrawable(context); + iconDrawable.setTint(notification.getNotification().color); + } else { + iconDrawable = icon.loadDrawable(context); + } + intent = notification.getNotification().contentIntent; + autoCancel = (notification.getNotification().flags + & Notification.FLAG_AUTO_CANCEL) != 0; + } + + @Override + public void onClick(View view) { + final Launcher launcher = Launcher.getLauncher(view.getContext()); + try { + intent.send(); + } catch (PendingIntent.CanceledException e) { + e.printStackTrace(); + } + if (autoCancel) { + launcher.getPopupDataProvider().cancelNotification(notificationKey); + } + PopupContainerWithArrow.getOpen(launcher).close(true); + } +} diff --git a/src/com/android/launcher3/badge/NotificationListener.java b/src/com/android/launcher3/badge/NotificationListener.java new file mode 100644 index 000000000..1668a6267 --- /dev/null +++ b/src/com/android/launcher3/badge/NotificationListener.java @@ -0,0 +1,213 @@ +/* + * 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.badge; + +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 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 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 pair + = (Pair) message.obj; + sNotificationsChangedListener.onNotificationPosted(pair.first, pair.second); + } + break; + case MSG_NOTIFICATION_REMOVED: + if (sNotificationsChangedListener != null) { + Pair pair + = (Pair) message.obj; + sNotificationsChangedListener.onNotificationRemoved(pair.first, pair.second); + } + break; + case MSG_NOTIFICATION_FULL_REFRESH: + if (sNotificationsChangedListener != null) { + sNotificationsChangedListener.onNotificationFullRefresh( + (List) 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 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 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 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 filterNotifications( + StatusBarNotification[] notifications) { + if (notifications == null) return null; + Set removedNotifications = new HashSet<>(); + for (int i = 0; i < notifications.length; i++) { + if (shouldBeFilteredOut(notifications[i].getNotification())) { + removedNotifications.add(i); + } + } + List 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 activeNotifications); + } +} -- cgit v1.2.3