| /* |
| * 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.popup; |
| |
| import android.content.ComponentName; |
| import android.service.notification.StatusBarNotification; |
| import android.support.annotation.NonNull; |
| import android.util.Log; |
| |
| import com.android.launcher3.ItemInfo; |
| import com.android.launcher3.Launcher; |
| import com.android.launcher3.Utilities; |
| import com.android.launcher3.badge.BadgeInfo; |
| import com.android.launcher3.model.PackageItemInfo; |
| import com.android.launcher3.model.WidgetItem; |
| 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.util.ComponentKey; |
| import com.android.launcher3.util.MultiHashMap; |
| import com.android.launcher3.util.PackageUserKey; |
| import com.android.launcher3.widget.WidgetListRowEntry; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Provides data for the popup menu that appears after long-clicking on apps. |
| */ |
| public class PopupDataProvider implements NotificationListener.NotificationsChangedListener { |
| |
| private static final boolean LOGD = false; |
| private static final String TAG = "PopupDataProvider"; |
| |
| /** Note that these are in order of priority. */ |
| private static final SystemShortcut[] SYSTEM_SHORTCUTS = new SystemShortcut[] { |
| new SystemShortcut.AppInfo(), |
| new SystemShortcut.Widgets(), |
| new SystemShortcut.Install() |
| }; |
| |
| private final Launcher mLauncher; |
| |
| /** Maps launcher activity components to their list of shortcut ids. */ |
| private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>(); |
| /** Maps packages to their BadgeInfo's . */ |
| private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>(); |
| /** Maps packages to their Widgets */ |
| private ArrayList<WidgetListRowEntry> mAllWidgets = new ArrayList<>(); |
| |
| public PopupDataProvider(Launcher launcher) { |
| mLauncher = launcher; |
| } |
| |
| @Override |
| public void onNotificationPosted(PackageUserKey postedPackageUserKey, |
| NotificationKeyData notificationKey, boolean shouldBeFilteredOut) { |
| BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(postedPackageUserKey); |
| boolean badgeShouldBeRefreshed; |
| if (badgeInfo == null) { |
| if (!shouldBeFilteredOut) { |
| BadgeInfo newBadgeInfo = new BadgeInfo(postedPackageUserKey); |
| newBadgeInfo.addOrUpdateNotificationKey(notificationKey); |
| mPackageUserToBadgeInfos.put(postedPackageUserKey, newBadgeInfo); |
| badgeShouldBeRefreshed = true; |
| } else { |
| badgeShouldBeRefreshed = false; |
| } |
| } else { |
| badgeShouldBeRefreshed = shouldBeFilteredOut |
| ? badgeInfo.removeNotificationKey(notificationKey) |
| : badgeInfo.addOrUpdateNotificationKey(notificationKey); |
| if (badgeInfo.getNotificationKeys().size() == 0) { |
| mPackageUserToBadgeInfos.remove(postedPackageUserKey); |
| } |
| } |
| updateLauncherIconBadges(Utilities.singletonHashSet(postedPackageUserKey), |
| badgeShouldBeRefreshed); |
| } |
| |
| @Override |
| public void onNotificationRemoved(PackageUserKey removedPackageUserKey, |
| NotificationKeyData notificationKey) { |
| BadgeInfo oldBadgeInfo = mPackageUserToBadgeInfos.get(removedPackageUserKey); |
| if (oldBadgeInfo != null && oldBadgeInfo.removeNotificationKey(notificationKey)) { |
| if (oldBadgeInfo.getNotificationKeys().size() == 0) { |
| mPackageUserToBadgeInfos.remove(removedPackageUserKey); |
| } |
| updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey)); |
| trimNotifications(mPackageUserToBadgeInfos); |
| } |
| } |
| |
| @Override |
| public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) { |
| if (activeNotifications == null) return; |
| // This will contain the PackageUserKeys which have updated badges. |
| HashMap<PackageUserKey, BadgeInfo> updatedBadges = new HashMap<>(mPackageUserToBadgeInfos); |
| mPackageUserToBadgeInfos.clear(); |
| for (StatusBarNotification notification : activeNotifications) { |
| PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification); |
| BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(packageUserKey); |
| if (badgeInfo == null) { |
| badgeInfo = new BadgeInfo(packageUserKey); |
| mPackageUserToBadgeInfos.put(packageUserKey, badgeInfo); |
| } |
| badgeInfo.addOrUpdateNotificationKey(NotificationKeyData |
| .fromNotification(notification)); |
| } |
| |
| // Add and remove from updatedBadges so it contains the PackageUserKeys of updated badges. |
| for (PackageUserKey packageUserKey : mPackageUserToBadgeInfos.keySet()) { |
| BadgeInfo prevBadge = updatedBadges.get(packageUserKey); |
| BadgeInfo newBadge = mPackageUserToBadgeInfos.get(packageUserKey); |
| if (prevBadge == null) { |
| updatedBadges.put(packageUserKey, newBadge); |
| } else { |
| if (!prevBadge.shouldBeInvalidated(newBadge)) { |
| updatedBadges.remove(packageUserKey); |
| } |
| } |
| } |
| |
| if (!updatedBadges.isEmpty()) { |
| updateLauncherIconBadges(updatedBadges.keySet()); |
| } |
| trimNotifications(updatedBadges); |
| } |
| |
| private void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) { |
| PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher); |
| if (openContainer != null) { |
| openContainer.trimNotifications(updatedBadges); |
| } |
| } |
| |
| private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges) { |
| updateLauncherIconBadges(updatedBadges, true); |
| } |
| |
| /** |
| * Updates the icons on launcher (workspace, folders, all apps) to refresh their badges. |
| * @param updatedBadges The packages whose badges should be refreshed (either a notification was |
| * added or removed, or the badge should show the notification icon). |
| * @param shouldRefresh An optional parameter that will allow us to only refresh badges that |
| * have actually changed. If a notification updated its content but not |
| * its count or icon, then the badge doesn't change. |
| */ |
| private void updateLauncherIconBadges(Set<PackageUserKey> updatedBadges, |
| boolean shouldRefresh) { |
| Iterator<PackageUserKey> iterator = updatedBadges.iterator(); |
| while (iterator.hasNext()) { |
| BadgeInfo badgeInfo = mPackageUserToBadgeInfos.get(iterator.next()); |
| if (badgeInfo != null && !updateBadgeIcon(badgeInfo) && !shouldRefresh) { |
| // The notification icon isn't used, and the badge hasn't changed |
| // so there is no update to be made. |
| iterator.remove(); |
| } |
| } |
| if (!updatedBadges.isEmpty()) { |
| mLauncher.updateIconBadges(updatedBadges); |
| } |
| } |
| |
| /** |
| * Determines whether the badge should show a notification icon rather than a number, |
| * and sets that icon on the BadgeInfo if so. |
| * @param badgeInfo The badge to update with an icon (null if it shouldn't show one). |
| * @return Whether the badge icon potentially changed (true unless it stayed null). |
| */ |
| private boolean updateBadgeIcon(BadgeInfo badgeInfo) { |
| boolean hadNotificationToShow = badgeInfo.hasNotificationToShow(); |
| NotificationInfo notificationInfo = null; |
| NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); |
| if (notificationListener != null && badgeInfo.getNotificationKeys().size() >= 1) { |
| // Look for the most recent notification that has an icon that should be shown in badge. |
| for (NotificationKeyData notificationKeyData : badgeInfo.getNotificationKeys()) { |
| String notificationKey = notificationKeyData.notificationKey; |
| StatusBarNotification[] activeNotifications = notificationListener |
| .getActiveNotifications(new String[]{notificationKey}); |
| if (activeNotifications.length == 1) { |
| notificationInfo = new NotificationInfo(mLauncher, activeNotifications[0]); |
| if (notificationInfo.shouldShowIconInBadge()) { |
| // Found an appropriate icon. |
| break; |
| } else { |
| // Keep looking. |
| notificationInfo = null; |
| } |
| } |
| } |
| } |
| badgeInfo.setNotificationToShow(notificationInfo); |
| return hadNotificationToShow || badgeInfo.hasNotificationToShow(); |
| } |
| |
| public void setDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMapCopy) { |
| mDeepShortcutMap = deepShortcutMapCopy; |
| if (LOGD) Log.d(TAG, "bindDeepShortcutMap: " + mDeepShortcutMap); |
| } |
| |
| public List<String> getShortcutIdsForItem(ItemInfo info) { |
| if (!DeepShortcutManager.supportsShortcuts(info)) { |
| return Collections.EMPTY_LIST; |
| } |
| ComponentName component = info.getTargetComponent(); |
| if (component == null) { |
| return Collections.EMPTY_LIST; |
| } |
| |
| List<String> ids = mDeepShortcutMap.get(new ComponentKey(component, info.user)); |
| return ids == null ? Collections.EMPTY_LIST : ids; |
| } |
| |
| public BadgeInfo getBadgeInfoForItem(ItemInfo info) { |
| if (!DeepShortcutManager.supportsShortcuts(info)) { |
| return null; |
| } |
| |
| return mPackageUserToBadgeInfos.get(PackageUserKey.fromItemInfo(info)); |
| } |
| |
| public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) { |
| BadgeInfo badgeInfo = getBadgeInfoForItem(info); |
| return badgeInfo == null ? Collections.EMPTY_LIST : badgeInfo.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 @NonNull List<SystemShortcut> getEnabledSystemShortcutsForItem(ItemInfo info) { |
| List<SystemShortcut> systemShortcuts = new ArrayList<>(); |
| for (SystemShortcut systemShortcut : SYSTEM_SHORTCUTS) { |
| if (systemShortcut.getOnClickListener(mLauncher, info) != null) { |
| systemShortcuts.add(systemShortcut); |
| } |
| } |
| return systemShortcuts; |
| } |
| |
| public void cancelNotification(String notificationKey) { |
| NotificationListener notificationListener = NotificationListener.getInstanceIfConnected(); |
| if (notificationListener == null) { |
| return; |
| } |
| notificationListener.cancelNotification(notificationKey); |
| } |
| |
| public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) { |
| mAllWidgets = allWidgets; |
| } |
| |
| public ArrayList<WidgetListRowEntry> getAllWidgets() { |
| return mAllWidgets; |
| } |
| |
| public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) { |
| for (WidgetListRowEntry entry : mAllWidgets) { |
| if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) { |
| ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets); |
| // Remove widgets not associated with the correct user. |
| Iterator<WidgetItem> iterator = widgets.iterator(); |
| while (iterator.hasNext()) { |
| if (!iterator.next().user.equals(packageUserKey.mUser)) { |
| iterator.remove(); |
| } |
| } |
| return widgets.isEmpty() ? null : widgets; |
| } |
| } |
| return null; |
| } |
| } |