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 static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; 20 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.pm.ShortcutInfo; 24 import android.os.Handler; 25 import android.os.UserHandle; 26 27 import androidx.annotation.VisibleForTesting; 28 29 import com.android.launcher3.LauncherAppState; 30 import com.android.launcher3.icons.CacheableShortcutInfo; 31 import com.android.launcher3.icons.IconCache; 32 import com.android.launcher3.model.data.ItemInfo; 33 import com.android.launcher3.model.data.WorkspaceItemInfo; 34 import com.android.launcher3.shortcuts.DeepShortcutView; 35 import com.android.launcher3.shortcuts.ShortcutRequest; 36 import com.android.launcher3.util.ApplicationInfoWrapper; 37 import com.android.launcher3.views.ActivityContext; 38 39 import java.util.ArrayList; 40 import java.util.Comparator; 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 51 static final int NUM_DYNAMIC = 2; 52 53 /** 54 * Sorts shortcuts in rank order, with manifest shortcuts coming before dynamic shortcuts. 55 */ 56 private static final Comparator<ShortcutInfo> SHORTCUT_RANK_COMPARATOR = (a, b) -> { 57 if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) { 58 return -1; 59 } 60 if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) { 61 return 1; 62 } 63 return Integer.compare(a.getRank(), b.getRank()); 64 }; 65 66 /** 67 * Filters the shortcuts so that only MAX_SHORTCUTS or fewer shortcuts are retained. 68 * We want the filter to include both static and dynamic shortcuts, so we always 69 * include NUM_DYNAMIC dynamic shortcuts, if at least that many are present. 70 * 71 * @return a subset of shortcuts, in sorted order, with size <= MAX_SHORTCUTS. 72 */ sortAndFilterShortcuts(List<ShortcutInfo> shortcuts)73 public static List<ShortcutInfo> sortAndFilterShortcuts(List<ShortcutInfo> shortcuts) { 74 shortcuts.sort(SHORTCUT_RANK_COMPARATOR); 75 if (shortcuts.size() <= MAX_SHORTCUTS) { 76 return shortcuts; 77 } 78 79 // The list of shortcuts is now sorted with static shortcuts followed by dynamic 80 // shortcuts. We want to preserve this order, but only keep MAX_SHORTCUTS. 81 List<ShortcutInfo> filteredShortcuts = new ArrayList<>(MAX_SHORTCUTS); 82 int numDynamic = 0; 83 int size = shortcuts.size(); 84 for (int i = 0; i < size; i++) { 85 ShortcutInfo shortcut = shortcuts.get(i); 86 int filteredSize = filteredShortcuts.size(); 87 if (filteredSize < MAX_SHORTCUTS) { 88 // Always add the first MAX_SHORTCUTS to the filtered list. 89 filteredShortcuts.add(shortcut); 90 if (shortcut.isDynamic()) { 91 numDynamic++; 92 } 93 continue; 94 } 95 // At this point, we have MAX_SHORTCUTS already, but they may all be static. 96 // If there are dynamic shortcuts, remove static shortcuts to add them. 97 if (shortcut.isDynamic() && numDynamic < NUM_DYNAMIC) { 98 numDynamic++; 99 int lastStaticIndex = filteredSize - numDynamic; 100 filteredShortcuts.remove(lastStaticIndex); 101 filteredShortcuts.add(shortcut); 102 } 103 } 104 return filteredShortcuts; 105 } 106 107 /** 108 * Returns a runnable to update the provided shortcuts 109 */ createUpdateRunnable( final T context, final ItemInfo originalInfo, final Handler uiHandler, final PopupContainerWithArrow container, final List<DeepShortcutView> shortcutViews )110 public static <T extends Context & ActivityContext> Runnable createUpdateRunnable( 111 final T context, 112 final ItemInfo originalInfo, 113 final Handler uiHandler, 114 final PopupContainerWithArrow container, 115 final List<DeepShortcutView> shortcutViews 116 ) { 117 final ComponentName activity = originalInfo.getTargetComponent(); 118 final UserHandle user = originalInfo.user; 119 final String targetPackage = originalInfo.getTargetPackage(); 120 return () -> { 121 ApplicationInfoWrapper infoWrapper = 122 new ApplicationInfoWrapper(context, targetPackage, user); 123 List<ShortcutInfo> shortcuts = new ShortcutRequest(context, user) 124 .withContainer(activity) 125 .query(ShortcutRequest.PUBLISHED); 126 shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts); 127 IconCache cache = LauncherAppState.getInstance(context).getIconCache(); 128 for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) { 129 final ShortcutInfo shortcut = shortcuts.get(i); 130 final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, context); 131 cache.getShortcutIcon(si, new CacheableShortcutInfo(shortcut, infoWrapper)); 132 si.rank = i; 133 si.container = CONTAINER_SHORTCUTS; 134 135 final DeepShortcutView view = shortcutViews.get(i); 136 uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container)); 137 } 138 }; 139 } 140 } 141