1 2 package com.android.launcher3.model; 3 4 import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER; 5 6 import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList; 7 import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY; 8 9 import static java.util.stream.Collectors.groupingBy; 10 import static java.util.stream.Collectors.mapping; 11 import static java.util.stream.Collectors.toList; 12 13 import android.appwidget.AppWidgetProviderInfo; 14 import android.content.ComponentName; 15 import android.content.Context; 16 import android.content.pm.PackageManager; 17 import android.os.UserHandle; 18 import android.util.Log; 19 import android.util.Pair; 20 21 import androidx.annotation.Nullable; 22 import androidx.collection.ArrayMap; 23 24 import com.android.launcher3.AppFilter; 25 import com.android.launcher3.InvariantDeviceProfile; 26 import com.android.launcher3.LauncherAppState; 27 import com.android.launcher3.Utilities; 28 import com.android.launcher3.compat.AlphabeticIndexCompat; 29 import com.android.launcher3.config.FeatureFlags; 30 import com.android.launcher3.icons.ComponentWithLabelAndIcon; 31 import com.android.launcher3.icons.IconCache; 32 import com.android.launcher3.model.data.PackageItemInfo; 33 import com.android.launcher3.pm.ShortcutConfigActivityInfo; 34 import com.android.launcher3.util.IntSet; 35 import com.android.launcher3.util.PackageUserKey; 36 import com.android.launcher3.util.Preconditions; 37 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; 38 import com.android.launcher3.widget.WidgetManagerHelper; 39 import com.android.launcher3.widget.WidgetSections; 40 import com.android.launcher3.widget.model.WidgetsListBaseEntry; 41 import com.android.launcher3.widget.model.WidgetsListContentEntry; 42 import com.android.launcher3.widget.model.WidgetsListHeaderEntry; 43 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collection; 47 import java.util.HashMap; 48 import java.util.Iterator; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Map.Entry; 52 import java.util.Set; 53 import java.util.function.Predicate; 54 55 /** 56 * Widgets data model that is used by the adapters of the widget views and controllers. 57 * 58 * <p> The widgets and shortcuts are organized using package name as its index. 59 */ 60 public class WidgetsModel { 61 62 // True is the widget support is disabled. 63 public static final boolean GO_DISABLE_WIDGETS = false; 64 public static final boolean GO_DISABLE_NOTIFICATION_DOTS = false; 65 66 private static final String TAG = "WidgetsModel"; 67 private static final boolean DEBUG = false; 68 69 /* Map of widgets and shortcuts that are tracked per package. */ 70 private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsList = new HashMap<>(); 71 72 /** 73 * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row 74 * are sorted (based on label and user), but the overall list of 75 * {@link WidgetsListBaseEntry}s is not sorted. 76 * 77 * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List) 78 */ getWidgetsListForPicker(Context context)79 public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) { 80 ArrayList<WidgetsListBaseEntry> result = new ArrayList<>(); 81 AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context); 82 83 for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) { 84 PackageItemInfo pkgItem = entry.getKey(); 85 List<WidgetItem> widgetItems = entry.getValue(); 86 String sectionName = (pkgItem.title == null) ? "" : 87 indexer.computeSectionName(pkgItem.title); 88 result.add(WidgetsListHeaderEntry.create(pkgItem, sectionName, widgetItems)); 89 result.add(new WidgetsListContentEntry(pkgItem, sectionName, widgetItems)); 90 } 91 return result; 92 } 93 94 /** Returns a mapping of packages to their widgets without static shortcuts. */ getAllWidgetsWithoutShortcuts()95 public synchronized Map<PackageUserKey, List<WidgetItem>> getAllWidgetsWithoutShortcuts() { 96 Map<PackageUserKey, List<WidgetItem>> packagesToWidgets = new HashMap<>(); 97 mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) -> { 98 List<WidgetItem> widgets = widgetsAndShortcuts.stream() 99 .filter(item -> item.widgetInfo != null) 100 .collect(toList()); 101 if (widgets.size() > 0) { 102 packagesToWidgets.put( 103 new PackageUserKey(packageItemInfo.packageName, packageItemInfo.user), 104 widgets); 105 } 106 }); 107 return packagesToWidgets; 108 } 109 110 /** 111 * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise 112 * only widgets and shortcuts associated with the package/user are. 113 */ update( LauncherAppState app, @Nullable PackageUserKey packageUser)114 public List<ComponentWithLabelAndIcon> update( 115 LauncherAppState app, @Nullable PackageUserKey packageUser) { 116 Preconditions.assertWorkerThread(); 117 118 Context context = app.getContext(); 119 final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>(); 120 List<ComponentWithLabelAndIcon> updatedItems = new ArrayList<>(); 121 try { 122 InvariantDeviceProfile idp = app.getInvariantDeviceProfile(); 123 PackageManager pm = app.getContext().getPackageManager(); 124 125 // Widgets 126 WidgetManagerHelper widgetManager = new WidgetManagerHelper(context); 127 for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) { 128 LauncherAppWidgetProviderInfo launcherWidgetInfo = 129 LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo); 130 131 widgetsAndShortcuts.add(new WidgetItem( 132 launcherWidgetInfo, idp, app.getIconCache())); 133 updatedItems.add(launcherWidgetInfo); 134 } 135 136 // Shortcuts 137 for (ShortcutConfigActivityInfo info : 138 queryList(context, packageUser)) { 139 widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm)); 140 updatedItems.add(info); 141 } 142 setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser); 143 } catch (Exception e) { 144 if (!FeatureFlags.IS_STUDIO_BUILD && Utilities.isBinderSizeError(e)) { 145 // the returned value may be incomplete and will not be refreshed until the next 146 // time Launcher starts. 147 // TODO: after figuring out a repro step, introduce a dirty bit to check when 148 // onResume is called to refresh the widget provider list. 149 } else { 150 throw e; 151 } 152 } 153 154 return updatedItems; 155 } 156 setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts, LauncherAppState app, @Nullable PackageUserKey packageUser)157 private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts, 158 LauncherAppState app, @Nullable PackageUserKey packageUser) { 159 if (DEBUG) { 160 Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size()); 161 } 162 163 // Temporary cache for {@link PackageItemInfos} to avoid having to go through 164 // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList} 165 PackageItemInfoCache packageItemInfoCache = new PackageItemInfoCache(); 166 167 if (packageUser == null) { 168 // Clear the list if this is an update on all widgets and shortcuts. 169 mWidgetsList.clear(); 170 } else { 171 // Otherwise, only clear the widgets and shortcuts for the changed package. 172 mWidgetsList.remove(packageItemInfoCache.getOrCreate(packageUser)); 173 } 174 175 // add and update. 176 mWidgetsList.putAll(rawWidgetsShortcuts.stream() 177 .filter(new WidgetValidityCheck(app)) 178 .flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream() 179 .map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem))) 180 .collect(groupingBy(pair -> pair.first, mapping(pair -> pair.second, toList())))); 181 182 // Update each package entry 183 IconCache iconCache = app.getIconCache(); 184 for (PackageItemInfo p : packageItemInfoCache.values()) { 185 iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */); 186 } 187 } 188 onPackageIconsUpdated(Set<String> packageNames, UserHandle user, LauncherAppState app)189 public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user, 190 LauncherAppState app) { 191 for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) { 192 if (packageNames.contains(entry.getKey().packageName)) { 193 List<WidgetItem> items = entry.getValue(); 194 int count = items.size(); 195 for (int i = 0; i < count; i++) { 196 WidgetItem item = items.get(i); 197 if (item.user.equals(user)) { 198 if (item.activityInfo != null) { 199 items.set(i, new WidgetItem(item.activityInfo, app.getIconCache(), 200 app.getContext().getPackageManager())); 201 } else { 202 items.set(i, new WidgetItem(item.widgetInfo, 203 app.getInvariantDeviceProfile(), app.getIconCache())); 204 } 205 } 206 } 207 } 208 } 209 } 210 getWidgetProviderInfoByProviderName( ComponentName providerName, UserHandle user)211 public WidgetItem getWidgetProviderInfoByProviderName( 212 ComponentName providerName, UserHandle user) { 213 List<WidgetItem> widgetsList = mWidgetsList.get( 214 new PackageItemInfo(providerName.getPackageName(), user)); 215 if (widgetsList == null) { 216 return null; 217 } 218 219 for (WidgetItem item : widgetsList) { 220 if (item.componentName.equals(providerName)) { 221 return item; 222 } 223 } 224 return null; 225 } 226 227 /** Returns {@link PackageItemInfo} of a pending widget. */ newPendingItemInfo(Context context, ComponentName provider, UserHandle user)228 public static PackageItemInfo newPendingItemInfo(Context context, ComponentName provider, 229 UserHandle user) { 230 Map<ComponentName, IntSet> widgetsToCategories = 231 WidgetSections.getWidgetsToCategory(context); 232 if (widgetsToCategories.containsKey(provider)) { 233 Iterator<Integer> categoriesIterator = widgetsToCategories.get(provider).iterator(); 234 int firstCategory = NO_CATEGORY; 235 while (categoriesIterator.hasNext() && firstCategory == NO_CATEGORY) { 236 firstCategory = categoriesIterator.next(); 237 } 238 return new PackageItemInfo(provider.getPackageName(), firstCategory, user); 239 } 240 return new PackageItemInfo(provider.getPackageName(), user); 241 } 242 getPackageUserKeys(Context context, WidgetItem item)243 private List<PackageUserKey> getPackageUserKeys(Context context, WidgetItem item) { 244 Map<ComponentName, IntSet> widgetsToCategories = 245 WidgetSections.getWidgetsToCategory(context); 246 IntSet categories = widgetsToCategories.get(item.componentName); 247 if (categories == null || categories.isEmpty()) { 248 return Arrays.asList( 249 new PackageUserKey(item.componentName.getPackageName(), item.user)); 250 } 251 List<PackageUserKey> packageUserKeys = new ArrayList<>(); 252 categories.forEach(category -> { 253 if (category == NO_CATEGORY) { 254 packageUserKeys.add( 255 new PackageUserKey(item.componentName.getPackageName(), 256 item.user)); 257 } else { 258 packageUserKeys.add(new PackageUserKey(category, item.user)); 259 } 260 }); 261 return packageUserKeys; 262 } 263 264 private static class WidgetValidityCheck implements Predicate<WidgetItem> { 265 266 private final InvariantDeviceProfile mIdp; 267 private final AppFilter mAppFilter; 268 WidgetValidityCheck(LauncherAppState app)269 WidgetValidityCheck(LauncherAppState app) { 270 mIdp = app.getInvariantDeviceProfile(); 271 mAppFilter = new AppFilter(app.getContext()); 272 } 273 274 @Override test(WidgetItem item)275 public boolean test(WidgetItem item) { 276 if (item.widgetInfo != null) { 277 if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) { 278 // Widget is hidden from picker 279 return false; 280 } 281 282 // Ensure that all widgets we show can be added on a workspace of this size 283 if (!item.widgetInfo.isMinSizeFulfilled()) { 284 if (DEBUG) { 285 Log.d(TAG, String.format( 286 "Widget %s : can't fit on this device with a grid size: %dx%d", 287 item.componentName, mIdp.numColumns, mIdp.numRows)); 288 } 289 return false; 290 } 291 } 292 if (!mAppFilter.shouldShowApp(item.componentName)) { 293 if (DEBUG) { 294 Log.d(TAG, String.format("%s is filtered and not added to the widget tray.", 295 item.componentName)); 296 } 297 return false; 298 } 299 300 return true; 301 } 302 } 303 304 private static final class PackageItemInfoCache { 305 private final Map<PackageUserKey, PackageItemInfo> mMap = new ArrayMap<>(); 306 getOrCreate(PackageUserKey key)307 PackageItemInfo getOrCreate(PackageUserKey key) { 308 PackageItemInfo pInfo = mMap.get(key); 309 if (pInfo == null) { 310 pInfo = new PackageItemInfo(key.mPackageName, key.mWidgetCategory, key.mUser); 311 pInfo.user = key.mUser; 312 mMap.put(key, pInfo); 313 } 314 return pInfo; 315 } 316 values()317 Collection<PackageItemInfo> values() { 318 return mMap.values(); 319 } 320 } 321 }