1 2 package com.android.launcher3.model; 3 4 import android.appwidget.AppWidgetProviderInfo; 5 import android.content.ComponentName; 6 import android.content.Context; 7 import android.content.Intent; 8 import android.content.pm.ResolveInfo; 9 import android.os.DeadObjectException; 10 import android.os.TransactionTooLargeException; 11 import android.util.Log; 12 13 import com.android.launcher3.AppFilter; 14 import com.android.launcher3.IconCache; 15 import com.android.launcher3.InvariantDeviceProfile; 16 import com.android.launcher3.ItemInfo; 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.UserHandleCompat; 23 import com.android.launcher3.config.ProviderConfig; 24 25 import java.util.ArrayList; 26 import java.util.Collections; 27 import java.util.Comparator; 28 import java.util.HashMap; 29 import java.util.List; 30 31 /** 32 * Widgets data model that is used by the adapters of the widget views and controllers. 33 * 34 * <p> The widgets and shortcuts are organized using package name as its index. 35 */ 36 public class WidgetsModel { 37 38 private static final String TAG = "WidgetsModel"; 39 private static final boolean DEBUG = false; 40 41 /* List of packages that is tracked by this model. */ 42 private final ArrayList<PackageItemInfo> mPackageItemInfos; 43 44 /* Map of widgets and shortcuts that are tracked per package. */ 45 private final HashMap<PackageItemInfo, ArrayList<Object>> mWidgetsList; 46 47 private final AppWidgetManagerCompat mAppWidgetMgr; 48 private final WidgetsAndShortcutNameComparator mWidgetAndShortcutNameComparator; 49 private final Comparator<ItemInfo> mAppNameComparator; 50 private final IconCache mIconCache; 51 private final AppFilter mAppFilter; 52 private final AlphabeticIndexCompat mIndexer; 53 54 private ArrayList<Object> mRawList; 55 56 public WidgetsModel(Context context, IconCache iconCache, AppFilter appFilter) { 57 mAppWidgetMgr = AppWidgetManagerCompat.getInstance(context); 58 mWidgetAndShortcutNameComparator = new WidgetsAndShortcutNameComparator(context); 59 mAppNameComparator = (new AppNameComparator(context)).getAppInfoComparator(); 60 mIconCache = iconCache; 61 mAppFilter = appFilter; 62 mIndexer = new AlphabeticIndexCompat(context); 63 mPackageItemInfos = new ArrayList<>(); 64 mWidgetsList = new HashMap<>(); 65 66 mRawList = new ArrayList<>(); 67 } 68 69 @SuppressWarnings("unchecked") 70 private WidgetsModel(WidgetsModel model) { 71 mAppWidgetMgr = model.mAppWidgetMgr; 72 mPackageItemInfos = (ArrayList<PackageItemInfo>) model.mPackageItemInfos.clone(); 73 mWidgetsList = (HashMap<PackageItemInfo, ArrayList<Object>>) model.mWidgetsList.clone(); 74 mWidgetAndShortcutNameComparator = model.mWidgetAndShortcutNameComparator; 75 mAppNameComparator = model.mAppNameComparator; 76 mIconCache = model.mIconCache; 77 mAppFilter = model.mAppFilter; 78 mIndexer = model.mIndexer; 79 mRawList = (ArrayList<Object>) model.mRawList.clone(); 80 } 81 82 // Access methods that may be deleted if the private fields are made package-private. 83 public int getPackageSize() { 84 return mPackageItemInfos.size(); 85 } 86 87 // Access methods that may be deleted if the private fields are made package-private. 88 public PackageItemInfo getPackageItemInfo(int pos) { 89 if (pos >= mPackageItemInfos.size() || pos < 0) { 90 return null; 91 } 92 return mPackageItemInfos.get(pos); 93 } 94 95 public List<Object> getSortedWidgets(int pos) { 96 return mWidgetsList.get(mPackageItemInfos.get(pos)); 97 } 98 99 public ArrayList<Object> getRawList() { 100 return mRawList; 101 } 102 103 public boolean isEmpty() { 104 return mRawList.isEmpty(); 105 } 106 107 public WidgetsModel updateAndClone(Context context) { 108 Utilities.assertWorkerThread(); 109 110 try { 111 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<>(); 112 // Widgets 113 for (AppWidgetProviderInfo widgetInfo : 114 AppWidgetManagerCompat.getInstance(context).getAllProviders()) { 115 widgetsAndShortcuts.add(LauncherAppWidgetProviderInfo 116 .fromProviderInfo(context, widgetInfo)); 117 } 118 // Shortcuts 119 widgetsAndShortcuts.addAll(context.getPackageManager().queryIntentActivities( 120 new Intent(Intent.ACTION_CREATE_SHORTCUT), 0)); 121 setWidgetsAndShortcuts(widgetsAndShortcuts); 122 } catch (Exception e) { 123 if (!LauncherAppState.isDogfoodBuild() && 124 (e.getCause() instanceof TransactionTooLargeException || 125 e.getCause() instanceof DeadObjectException)) { 126 // the returned value may be incomplete and will not be refreshed until the next 127 // time Launcher starts. 128 // TODO: after figuring out a repro step, introduce a dirty bit to check when 129 // onResume is called to refresh the widget provider list. 130 } else { 131 throw e; 132 } 133 } 134 return clone(); 135 } 136 137 private void setWidgetsAndShortcuts(ArrayList<Object> rawWidgetsShortcuts) { 138 mRawList = rawWidgetsShortcuts; 139 if (DEBUG) { 140 Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size()); 141 } 142 143 // Temporary list for {@link PackageItemInfos} to avoid having to go through 144 // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList} 145 HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>(); 146 147 // clear the lists. 148 mWidgetsList.clear(); 149 mPackageItemInfos.clear(); 150 mWidgetAndShortcutNameComparator.reset(); 151 152 InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile(); 153 154 // add and update. 155 for (Object o: rawWidgetsShortcuts) { 156 String packageName = ""; 157 UserHandleCompat userHandle = null; 158 ComponentName componentName = null; 159 if (o instanceof LauncherAppWidgetProviderInfo) { 160 LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o; 161 162 // Ensure that all widgets we show can be added on a workspace of this size 163 int minSpanX = Math.min(widgetInfo.spanX, widgetInfo.minSpanX); 164 int minSpanY = Math.min(widgetInfo.spanY, widgetInfo.minSpanY); 165 if (minSpanX <= (int) idp.numColumns && 166 minSpanY <= (int) idp.numRows) { 167 componentName = widgetInfo.provider; 168 packageName = widgetInfo.provider.getPackageName(); 169 userHandle = mAppWidgetMgr.getUser(widgetInfo); 170 } else { 171 if (DEBUG) { 172 Log.d(TAG, String.format( 173 "Widget %s : (%d X %d) can't fit on this device", 174 widgetInfo.provider, minSpanX, minSpanY)); 175 } 176 continue; 177 } 178 } else if (o instanceof ResolveInfo) { 179 ResolveInfo resolveInfo = (ResolveInfo) o; 180 componentName = new ComponentName(resolveInfo.activityInfo.packageName, 181 resolveInfo.activityInfo.name); 182 packageName = resolveInfo.activityInfo.packageName; 183 userHandle = UserHandleCompat.myUserHandle(); 184 } 185 186 if (componentName == null || userHandle == null) { 187 Log.e(TAG, String.format("Widget cannot be set for %s.", o.getClass().toString())); 188 continue; 189 } 190 if (mAppFilter != null && !mAppFilter.shouldShowApp(componentName)) { 191 if (DEBUG) { 192 Log.d(TAG, String.format("%s is filtered and not added to the widget tray.", 193 packageName)); 194 } 195 continue; 196 } 197 198 PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName); 199 ArrayList<Object> widgetsShortcutsList = mWidgetsList.get(pInfo); 200 if (widgetsShortcutsList != null) { 201 widgetsShortcutsList.add(o); 202 } else { 203 widgetsShortcutsList = new ArrayList<>(); 204 widgetsShortcutsList.add(o); 205 pInfo = new PackageItemInfo(packageName); 206 mIconCache.getTitleAndIconForApp(packageName, userHandle, 207 true /* userLowResIcon */, pInfo); 208 pInfo.titleSectionName = mIndexer.computeSectionName(pInfo.title); 209 mWidgetsList.put(pInfo, widgetsShortcutsList); 210 tmpPackageItemInfos.put(packageName, pInfo); 211 mPackageItemInfos.add(pInfo); 212 } 213 } 214 215 // sort. 216 Collections.sort(mPackageItemInfos, mAppNameComparator); 217 for (PackageItemInfo p: mPackageItemInfos) { 218 Collections.sort(mWidgetsList.get(p), mWidgetAndShortcutNameComparator); 219 } 220 } 221 222 /** 223 * Create a snapshot of the widgets model. 224 * <p> 225 * Usage case: view binding without being modified from package updates. 226 */ 227 @Override 228 public WidgetsModel clone(){ 229 return new WidgetsModel(this); 230 } 231 }