1 2 package com.android.launcher3.model; 3 4 import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER; 5 6 import android.appwidget.AppWidgetProviderInfo; 7 import android.content.Context; 8 import android.content.pm.PackageManager; 9 import android.os.Process; 10 import android.os.UserHandle; 11 import android.support.annotation.Nullable; 12 import android.util.Log; 13 14 import com.android.launcher3.AppFilter; 15 import com.android.launcher3.IconCache; 16 import com.android.launcher3.InvariantDeviceProfile; 17 import com.android.launcher3.LauncherAppState; 18 import com.android.launcher3.LauncherAppWidgetProviderInfo; 19 import com.android.launcher3.Utilities; 20 import com.android.launcher3.compat.AlphabeticIndexCompat; 21 import com.android.launcher3.compat.AppWidgetManagerCompat; 22 import com.android.launcher3.compat.LauncherAppsCompat; 23 import com.android.launcher3.compat.ShortcutConfigActivityInfo; 24 import com.android.launcher3.config.FeatureFlags; 25 import com.android.launcher3.util.MultiHashMap; 26 import com.android.launcher3.util.PackageUserKey; 27 import com.android.launcher3.util.Preconditions; 28 import com.android.launcher3.widget.WidgetItemComparator; 29 import com.android.launcher3.widget.WidgetListRowEntry; 30 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.HashMap; 34 import java.util.Iterator; 35 import java.util.Map; 36 37 /** 38 * Widgets data model that is used by the adapters of the widget views and controllers. 39 * 40 * <p> The widgets and shortcuts are organized using package name as its index. 41 */ 42 public class WidgetsModel { 43 44 private static final String TAG = "WidgetsModel"; 45 private static final boolean DEBUG = false; 46 47 /* Map of widgets and shortcuts that are tracked per package. */ 48 private final MultiHashMap<PackageItemInfo, WidgetItem> mWidgetsList = new MultiHashMap<>(); 49 50 private AppFilter mAppFilter; 51 52 /** 53 * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row 54 * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s 55 * is not sorted. This list is sorted at the UI when using 56 * {@link com.android.launcher3.widget.WidgetsDiffReporter} 57 * 58 * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList) 59 */ 60 public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) { 61 ArrayList<WidgetListRowEntry> result = new ArrayList<>(); 62 AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context); 63 64 WidgetItemComparator widgetComparator = new WidgetItemComparator(); 65 for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) { 66 WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue()); 67 row.titleSectionName = indexer.computeSectionName(row.pkgItem.title); 68 Collections.sort(row.widgets, widgetComparator); 69 result.add(row); 70 } 71 return result; 72 } 73 74 /** 75 * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise 76 * only widgets and shortcuts associated with the package/user are. 77 */ 78 public void update(LauncherAppState app, @Nullable PackageUserKey packageUser) { 79 Preconditions.assertWorkerThread(); 80 81 Context context = app.getContext(); 82 final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>(); 83 try { 84 PackageManager pm = context.getPackageManager(); 85 InvariantDeviceProfile idp = app.getInvariantDeviceProfile(); 86 87 // Widgets 88 AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context); 89 for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) { 90 widgetsAndShortcuts.add(new WidgetItem(LauncherAppWidgetProviderInfo 91 .fromProviderInfo(context, widgetInfo), pm, idp)); 92 } 93 94 // Shortcuts 95 for (ShortcutConfigActivityInfo info : LauncherAppsCompat.getInstance(context) 96 .getCustomShortcutActivityList(packageUser)) { 97 widgetsAndShortcuts.add(new WidgetItem(info)); 98 } 99 setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser); 100 } catch (Exception e) { 101 if (!FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) { 102 // the returned value may be incomplete and will not be refreshed until the next 103 // time Launcher starts. 104 // TODO: after figuring out a repro step, introduce a dirty bit to check when 105 // onResume is called to refresh the widget provider list. 106 } else { 107 throw e; 108 } 109 } 110 111 app.getWidgetCache().removeObsoletePreviews(widgetsAndShortcuts, packageUser); 112 } 113 114 private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts, 115 LauncherAppState app, @Nullable PackageUserKey packageUser) { 116 if (DEBUG) { 117 Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size()); 118 } 119 120 // Temporary list for {@link PackageItemInfos} to avoid having to go through 121 // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList} 122 HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>(); 123 124 // clear the lists. 125 if (packageUser == null) { 126 mWidgetsList.clear(); 127 } else { 128 // Only clear the widgets for the given package/user. 129 PackageItemInfo packageItem = null; 130 for (PackageItemInfo item : mWidgetsList.keySet()) { 131 if (item.packageName.equals(packageUser.mPackageName)) { 132 packageItem = item; 133 break; 134 } 135 } 136 if (packageItem != null) { 137 // We want to preserve the user that was on the packageItem previously, 138 // so add it to tmpPackageItemInfos here to avoid creating a new entry. 139 tmpPackageItemInfos.put(packageItem.packageName, packageItem); 140 141 Iterator<WidgetItem> widgetItemIterator = mWidgetsList.get(packageItem).iterator(); 142 while (widgetItemIterator.hasNext()) { 143 WidgetItem nextWidget = widgetItemIterator.next(); 144 if (nextWidget.componentName.getPackageName().equals(packageUser.mPackageName) 145 && nextWidget.user.equals(packageUser.mUser)) { 146 widgetItemIterator.remove(); 147 } 148 } 149 } 150 } 151 152 InvariantDeviceProfile idp = app.getInvariantDeviceProfile(); 153 UserHandle myUser = Process.myUserHandle(); 154 155 // add and update. 156 for (WidgetItem item : rawWidgetsShortcuts) { 157 if (item.widgetInfo != null) { 158 if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) { 159 // Widget is hidden from picker 160 continue; 161 } 162 163 // Ensure that all widgets we show can be added on a workspace of this size 164 int minSpanX = Math.min(item.widgetInfo.spanX, item.widgetInfo.minSpanX); 165 int minSpanY = Math.min(item.widgetInfo.spanY, item.widgetInfo.minSpanY); 166 if (minSpanX > idp.numColumns || minSpanY > idp.numRows) { 167 if (DEBUG) { 168 Log.d(TAG, String.format( 169 "Widget %s : (%d X %d) can't fit on this device", 170 item.componentName, minSpanX, minSpanY)); 171 } 172 continue; 173 } 174 } 175 176 if (mAppFilter == null) { 177 mAppFilter = AppFilter.newInstance(app.getContext()); 178 } 179 if (!mAppFilter.shouldShowApp(item.componentName)) { 180 if (DEBUG) { 181 Log.d(TAG, String.format("%s is filtered and not added to the widget tray.", 182 item.componentName)); 183 } 184 continue; 185 } 186 187 String packageName = item.componentName.getPackageName(); 188 PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName); 189 if (pInfo == null) { 190 pInfo = new PackageItemInfo(packageName); 191 pInfo.user = item.user; 192 tmpPackageItemInfos.put(packageName, pInfo); 193 } else if (!myUser.equals(pInfo.user)) { 194 // Keep updating the user, until we get the primary user. 195 pInfo.user = item.user; 196 } 197 mWidgetsList.addToList(pInfo, item); 198 } 199 200 // Update each package entry 201 IconCache iconCache = app.getIconCache(); 202 for (PackageItemInfo p : tmpPackageItemInfos.values()) { 203 iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */); 204 } 205 } 206 }