1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.popup; 18 19 import android.content.ComponentName; 20 import android.os.Handler; 21 import android.os.UserHandle; 22 import android.service.notification.StatusBarNotification; 23 import android.support.annotation.Nullable; 24 import android.support.annotation.VisibleForTesting; 25 26 import com.android.launcher3.ItemInfo; 27 import com.android.launcher3.Launcher; 28 import com.android.launcher3.ShortcutInfo; 29 import com.android.launcher3.graphics.LauncherIcons; 30 import com.android.launcher3.notification.NotificationInfo; 31 import com.android.launcher3.notification.NotificationKeyData; 32 import com.android.launcher3.shortcuts.DeepShortcutManager; 33 import com.android.launcher3.shortcuts.DeepShortcutView; 34 import com.android.launcher3.shortcuts.ShortcutInfoCompat; 35 import com.android.launcher3.util.PackageUserKey; 36 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.Comparator; 40 import java.util.Iterator; 41 import java.util.List; 42 43 /** 44 * Contains logic relevant to populating a {@link PopupContainerWithArrow}. In particular, 45 * this class determines which items appear in the container, and in what order. 46 */ 47 public class PopupPopulator { 48 49 public static final int MAX_SHORTCUTS = 4; 50 @VisibleForTesting static final int NUM_DYNAMIC = 2; 51 public static final int MAX_SHORTCUTS_IF_NOTIFICATIONS = 2; 52 53 /** 54 * Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts. 55 */ 56 private static final Comparator<ShortcutInfoCompat> SHORTCUT_RANK_COMPARATOR 57 = new Comparator<ShortcutInfoCompat>() { 58 @Override 59 public int compare(ShortcutInfoCompat a, ShortcutInfoCompat b) { 60 if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) { 61 return -1; 62 } 63 if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) { 64 return 1; 65 } 66 return Integer.compare(a.getRank(), b.getRank()); 67 } 68 }; 69 70 /** 71 * Filters the shortcuts so that only MAX_SHORTCUTS or fewer shortcuts are retained. 72 * We want the filter to include both static and dynamic shortcuts, so we always 73 * include NUM_DYNAMIC dynamic shortcuts, if at least that many are present. 74 * 75 * @param shortcutIdToRemoveFirst An id that should be filtered out first, if any. 76 * @return a subset of shortcuts, in sorted order, with size <= MAX_SHORTCUTS. 77 */ sortAndFilterShortcuts( List<ShortcutInfoCompat> shortcuts, @Nullable String shortcutIdToRemoveFirst)78 public static List<ShortcutInfoCompat> sortAndFilterShortcuts( 79 List<ShortcutInfoCompat> shortcuts, @Nullable String shortcutIdToRemoveFirst) { 80 // Remove up to one specific shortcut before sorting and doing somewhat fancy filtering. 81 if (shortcutIdToRemoveFirst != null) { 82 Iterator<ShortcutInfoCompat> shortcutIterator = shortcuts.iterator(); 83 while (shortcutIterator.hasNext()) { 84 if (shortcutIterator.next().getId().equals(shortcutIdToRemoveFirst)) { 85 shortcutIterator.remove(); 86 break; 87 } 88 } 89 } 90 91 Collections.sort(shortcuts, SHORTCUT_RANK_COMPARATOR); 92 if (shortcuts.size() <= MAX_SHORTCUTS) { 93 return shortcuts; 94 } 95 96 // The list of shortcuts is now sorted with static shortcuts followed by dynamic 97 // shortcuts. We want to preserve this order, but only keep MAX_SHORTCUTS. 98 List<ShortcutInfoCompat> filteredShortcuts = new ArrayList<>(MAX_SHORTCUTS); 99 int numDynamic = 0; 100 int size = shortcuts.size(); 101 for (int i = 0; i < size; i++) { 102 ShortcutInfoCompat shortcut = shortcuts.get(i); 103 int filteredSize = filteredShortcuts.size(); 104 if (filteredSize < MAX_SHORTCUTS) { 105 // Always add the first MAX_SHORTCUTS to the filtered list. 106 filteredShortcuts.add(shortcut); 107 if (shortcut.isDynamic()) { 108 numDynamic++; 109 } 110 continue; 111 } 112 // At this point, we have MAX_SHORTCUTS already, but they may all be static. 113 // If there are dynamic shortcuts, remove static shortcuts to add them. 114 if (shortcut.isDynamic() && numDynamic < NUM_DYNAMIC) { 115 numDynamic++; 116 int lastStaticIndex = filteredSize - numDynamic; 117 filteredShortcuts.remove(lastStaticIndex); 118 filteredShortcuts.add(shortcut); 119 } 120 } 121 return filteredShortcuts; 122 } 123 createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo, final Handler uiHandler, final PopupContainerWithArrow container, final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews, final List<NotificationKeyData> notificationKeys)124 public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo, 125 final Handler uiHandler, final PopupContainerWithArrow container, 126 final List<String> shortcutIds, final List<DeepShortcutView> shortcutViews, 127 final List<NotificationKeyData> notificationKeys) { 128 final ComponentName activity = originalInfo.getTargetComponent(); 129 final UserHandle user = originalInfo.user; 130 return () -> { 131 if (!notificationKeys.isEmpty()) { 132 List<StatusBarNotification> notifications = launcher.getPopupDataProvider() 133 .getStatusBarNotificationsForKeys(notificationKeys); 134 List<NotificationInfo> infos = new ArrayList<>(notifications.size()); 135 for (int i = 0; i < notifications.size(); i++) { 136 StatusBarNotification notification = notifications.get(i); 137 infos.add(new NotificationInfo(launcher, notification)); 138 } 139 uiHandler.post(() -> container.applyNotificationInfos(infos)); 140 } 141 142 List<ShortcutInfoCompat> shortcuts = DeepShortcutManager.getInstance(launcher) 143 .queryForShortcutsContainer(activity, shortcutIds, user); 144 String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null 145 : notificationKeys.get(0).shortcutId; 146 shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe); 147 for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) { 148 final ShortcutInfoCompat shortcut = shortcuts.get(i); 149 final ShortcutInfo si = new ShortcutInfo(shortcut, launcher); 150 // Use unbadged icon for the menu. 151 LauncherIcons li = LauncherIcons.obtain(launcher); 152 li.createShortcutIcon(shortcut, false /* badged */).applyTo(si); 153 li.recycle(); 154 si.rank = i; 155 156 final DeepShortcutView view = shortcutViews.get(i); 157 uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container)); 158 } 159 160 // This ensures that mLauncher.getWidgetsForPackageUser() 161 // doesn't return null (it puts all the widgets in memory). 162 uiHandler.post(() -> launcher.refreshAndBindWidgetsForPackageUser( 163 PackageUserKey.fromItemInfo(originalInfo))); 164 }; 165 } 166 } 167