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