• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
getWidgetsList(Context context)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      */
update(LauncherAppState app, @Nullable PackageUserKey packageUser)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 
setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts, LauncherAppState app, @Nullable PackageUserKey packageUser)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 }