• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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