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