package com.android.launcher3.model; import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.pm.PackageManager; import android.os.Process; import android.os.UserHandle; import android.util.Log; import com.android.launcher3.AppFilter; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; import com.android.launcher3.widget.WidgetItemComparator; import com.android.launcher3.widget.WidgetListRowEntry; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import androidx.annotation.Nullable; /** * Widgets data model that is used by the adapters of the widget views and controllers. * *

The widgets and shortcuts are organized using package name as its index. */ public class WidgetsModel { private static final String TAG = "WidgetsModel"; private static final boolean DEBUG = false; /* Map of widgets and shortcuts that are tracked per package. */ private final MultiHashMap mWidgetsList = new MultiHashMap<>(); private AppFilter mAppFilter; /** * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s * is not sorted. This list is sorted at the UI when using * {@link com.android.launcher3.widget.WidgetsDiffReporter} * * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList) */ public synchronized ArrayList getWidgetsList(Context context) { ArrayList result = new ArrayList<>(); AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context); WidgetItemComparator widgetComparator = new WidgetItemComparator(); for (Map.Entry> entry : mWidgetsList.entrySet()) { WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue()); row.titleSectionName = (row.pkgItem.title == null) ? "" : indexer.computeSectionName(row.pkgItem.title); Collections.sort(row.widgets, widgetComparator); result.add(row); } return result; } /** * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise * only widgets and shortcuts associated with the package/user are. */ public List update(LauncherAppState app, @Nullable PackageUserKey packageUser) { Preconditions.assertWorkerThread(); Context context = app.getContext(); final ArrayList widgetsAndShortcuts = new ArrayList<>(); List updatedItems = new ArrayList<>(); try { InvariantDeviceProfile idp = app.getInvariantDeviceProfile(); PackageManager pm = app.getContext().getPackageManager(); // Widgets AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context); for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) { LauncherAppWidgetProviderInfo launcherWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo); widgetsAndShortcuts.add(new WidgetItem( launcherWidgetInfo, idp, app.getIconCache())); updatedItems.add(launcherWidgetInfo); } // Shortcuts for (ShortcutConfigActivityInfo info : LauncherAppsCompat.getInstance(context) .getCustomShortcutActivityList(packageUser)) { widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm)); updatedItems.add(info); } setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser); } catch (Exception e) { if (!FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) { // the returned value may be incomplete and will not be refreshed until the next // time Launcher starts. // TODO: after figuring out a repro step, introduce a dirty bit to check when // onResume is called to refresh the widget provider list. } else { throw e; } } app.getWidgetCache().removeObsoletePreviews(widgetsAndShortcuts, packageUser); return updatedItems; } private synchronized void setWidgetsAndShortcuts(ArrayList rawWidgetsShortcuts, LauncherAppState app, @Nullable PackageUserKey packageUser) { if (DEBUG) { Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size()); } // Temporary list for {@link PackageItemInfos} to avoid having to go through // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList} HashMap tmpPackageItemInfos = new HashMap<>(); // clear the lists. if (packageUser == null) { mWidgetsList.clear(); } else { // Only clear the widgets for the given package/user. PackageItemInfo packageItem = null; for (PackageItemInfo item : mWidgetsList.keySet()) { if (item.packageName.equals(packageUser.mPackageName)) { packageItem = item; break; } } if (packageItem != null) { // We want to preserve the user that was on the packageItem previously, // so add it to tmpPackageItemInfos here to avoid creating a new entry. tmpPackageItemInfos.put(packageItem.packageName, packageItem); Iterator widgetItemIterator = mWidgetsList.get(packageItem).iterator(); while (widgetItemIterator.hasNext()) { WidgetItem nextWidget = widgetItemIterator.next(); if (nextWidget.componentName.getPackageName().equals(packageUser.mPackageName) && nextWidget.user.equals(packageUser.mUser)) { widgetItemIterator.remove(); } } } } InvariantDeviceProfile idp = app.getInvariantDeviceProfile(); UserHandle myUser = Process.myUserHandle(); // add and update. for (WidgetItem item : rawWidgetsShortcuts) { if (item.widgetInfo != null) { if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) { // Widget is hidden from picker continue; } // Ensure that all widgets we show can be added on a workspace of this size int minSpanX = Math.min(item.widgetInfo.spanX, item.widgetInfo.minSpanX); int minSpanY = Math.min(item.widgetInfo.spanY, item.widgetInfo.minSpanY); if (minSpanX > idp.numColumns || minSpanY > idp.numRows) { if (DEBUG) { Log.d(TAG, String.format( "Widget %s : (%d X %d) can't fit on this device", item.componentName, minSpanX, minSpanY)); } continue; } } if (mAppFilter == null) { mAppFilter = AppFilter.newInstance(app.getContext()); } if (!mAppFilter.shouldShowApp(item.componentName)) { if (DEBUG) { Log.d(TAG, String.format("%s is filtered and not added to the widget tray.", item.componentName)); } continue; } String packageName = item.componentName.getPackageName(); PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName); if (pInfo == null) { pInfo = new PackageItemInfo(packageName); pInfo.user = item.user; tmpPackageItemInfos.put(packageName, pInfo); } else if (!myUser.equals(pInfo.user)) { // Keep updating the user, until we get the primary user. pInfo.user = item.user; } mWidgetsList.addToList(pInfo, item); } // Update each package entry IconCache iconCache = app.getIconCache(); for (PackageItemInfo p : tmpPackageItemInfos.values()) { iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */); } } public void onPackageIconsUpdated(Set packageNames, UserHandle user, LauncherAppState app) { for (Entry> entry : mWidgetsList.entrySet()) { if (packageNames.contains(entry.getKey().packageName)) { ArrayList items = entry.getValue(); int count = items.size(); for (int i = 0; i < count; i++) { WidgetItem item = items.get(i); if (item.user.equals(user)) { if (item.activityInfo != null) { items.set(i, new WidgetItem(item.activityInfo, app.getIconCache(), app.getContext().getPackageManager())); } else { items.set(i, new WidgetItem(item.widgetInfo, app.getInvariantDeviceProfile(), app.getIconCache())); } } } } } } }