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