package com.android.launcher3.model; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.DeadObjectException; import android.os.TransactionTooLargeException; import android.util.Log; import com.android.launcher3.AppFilter; import com.android.launcher3.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.util.Preconditions; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; /** * Widgets data model that is used by the adapters of the widget views and controllers. * * <p> 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; /* List of packages that is tracked by this model. */ private final ArrayList<PackageItemInfo> mPackageItemInfos; /* Map of widgets and shortcuts that are tracked per package. */ private final HashMap<PackageItemInfo, ArrayList<WidgetItem>> mWidgetsList; private final AppWidgetManagerCompat mAppWidgetMgr; private final Comparator<ItemInfo> mAppNameComparator; private final IconCache mIconCache; private final AppFilter mAppFilter; private final AlphabeticIndexCompat mIndexer; private ArrayList<WidgetItem> mRawList; public WidgetsModel(Context context, IconCache iconCache, AppFilter appFilter) { mAppWidgetMgr = AppWidgetManagerCompat.getInstance(context); mAppNameComparator = (new AppNameComparator(context)).getAppInfoComparator(); mIconCache = iconCache; mAppFilter = appFilter; mIndexer = new AlphabeticIndexCompat(context); mPackageItemInfos = new ArrayList<>(); mWidgetsList = new HashMap<>(); mRawList = new ArrayList<>(); } @SuppressWarnings("unchecked") private WidgetsModel(WidgetsModel model) { mAppWidgetMgr = model.mAppWidgetMgr; mPackageItemInfos = (ArrayList<PackageItemInfo>) model.mPackageItemInfos.clone(); mWidgetsList = (HashMap<PackageItemInfo, ArrayList<WidgetItem>>) model.mWidgetsList.clone(); mAppNameComparator = model.mAppNameComparator; mIconCache = model.mIconCache; mAppFilter = model.mAppFilter; mIndexer = model.mIndexer; mRawList = (ArrayList<WidgetItem>) model.mRawList.clone(); } // Access methods that may be deleted if the private fields are made package-private. public int getPackageSize() { return mPackageItemInfos.size(); } // Access methods that may be deleted if the private fields are made package-private. public PackageItemInfo getPackageItemInfo(int pos) { if (pos >= mPackageItemInfos.size() || pos < 0) { return null; } return mPackageItemInfos.get(pos); } public List<WidgetItem> getSortedWidgets(int pos) { return mWidgetsList.get(mPackageItemInfos.get(pos)); } public ArrayList<WidgetItem> getRawList() { return mRawList; } public boolean isEmpty() { return mRawList.isEmpty(); } public WidgetsModel updateAndClone(Context context) { Preconditions.assertWorkerThread(); try { final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>(); // Widgets AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context); for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders()) { widgetsAndShortcuts.add(new WidgetItem( LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo), widgetManager)); } // Shortcuts PackageManager pm = context.getPackageManager(); for (ResolveInfo info : pm.queryIntentActivities(new Intent(Intent.ACTION_CREATE_SHORTCUT), 0)) { widgetsAndShortcuts.add(new WidgetItem(info, pm)); } setWidgetsAndShortcuts(widgetsAndShortcuts); } catch (Exception e) { if (!ProviderConfig.IS_DOGFOOD_BUILD && (e.getCause() instanceof TransactionTooLargeException || e.getCause() instanceof DeadObjectException)) { // 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; } } return clone(); } private void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts) { mRawList = rawWidgetsShortcuts; 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<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>(); // clear the lists. mWidgetsList.clear(); mPackageItemInfos.clear(); InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile(); // add and update. for (WidgetItem item: rawWidgetsShortcuts) { if (item.widgetInfo != null) { // 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.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); ArrayList<WidgetItem> widgetsShortcutsList = mWidgetsList.get(pInfo); if (widgetsShortcutsList == null) { widgetsShortcutsList = new ArrayList<>(); pInfo = new PackageItemInfo(packageName); tmpPackageItemInfos.put(packageName, pInfo); mPackageItemInfos.add(pInfo); mWidgetsList.put(pInfo, widgetsShortcutsList); } widgetsShortcutsList.add(item); } // Update each package entry for (PackageItemInfo p : mPackageItemInfos) { ArrayList<WidgetItem> widgetsShortcutsList = mWidgetsList.get(p); Collections.sort(widgetsShortcutsList); // Update the package entry based on the first item. p.user = widgetsShortcutsList.get(0).user; mIconCache.getTitleAndIconForApp(p, true /* userLowResIcon */); p.titleSectionName = mIndexer.computeSectionName(p.title); } // sort the package entries. Collections.sort(mPackageItemInfos, mAppNameComparator); } /** * Create a snapshot of the widgets model. * <p> * Usage case: view binding without being modified from package updates. */ @Override public WidgetsModel clone(){ return new WidgetsModel(this); } }