• 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 static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
7 import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
8 
9 import static java.util.stream.Collectors.groupingBy;
10 import static java.util.stream.Collectors.mapping;
11 import static java.util.stream.Collectors.toList;
12 
13 import android.appwidget.AppWidgetProviderInfo;
14 import android.content.ComponentName;
15 import android.content.Context;
16 import android.content.pm.PackageManager;
17 import android.os.UserHandle;
18 import android.util.Log;
19 import android.util.Pair;
20 
21 import androidx.annotation.Nullable;
22 import androidx.collection.ArrayMap;
23 
24 import com.android.launcher3.AppFilter;
25 import com.android.launcher3.InvariantDeviceProfile;
26 import com.android.launcher3.LauncherAppState;
27 import com.android.launcher3.Utilities;
28 import com.android.launcher3.compat.AlphabeticIndexCompat;
29 import com.android.launcher3.config.FeatureFlags;
30 import com.android.launcher3.icons.ComponentWithLabelAndIcon;
31 import com.android.launcher3.icons.IconCache;
32 import com.android.launcher3.model.data.PackageItemInfo;
33 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
34 import com.android.launcher3.util.IntSet;
35 import com.android.launcher3.util.PackageUserKey;
36 import com.android.launcher3.util.Preconditions;
37 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
38 import com.android.launcher3.widget.WidgetManagerHelper;
39 import com.android.launcher3.widget.WidgetSections;
40 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
41 import com.android.launcher3.widget.model.WidgetsListContentEntry;
42 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
43 
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collection;
47 import java.util.HashMap;
48 import java.util.Iterator;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Map.Entry;
52 import java.util.Set;
53 import java.util.function.Predicate;
54 
55 /**
56  * Widgets data model that is used by the adapters of the widget views and controllers.
57  *
58  * <p> The widgets and shortcuts are organized using package name as its index.
59  */
60 public class WidgetsModel {
61 
62     // True is the widget support is disabled.
63     public static final boolean GO_DISABLE_WIDGETS = false;
64     public static final boolean GO_DISABLE_NOTIFICATION_DOTS = false;
65 
66     private static final String TAG = "WidgetsModel";
67     private static final boolean DEBUG = false;
68 
69     /* Map of widgets and shortcuts that are tracked per package. */
70     private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsList = new HashMap<>();
71 
72     /**
73      * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row
74      * are sorted (based on label and user), but the overall list of
75      * {@link WidgetsListBaseEntry}s is not sorted.
76      *
77      * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List)
78      */
getWidgetsListForPicker(Context context)79     public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) {
80         ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
81         AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
82 
83         for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
84             PackageItemInfo pkgItem = entry.getKey();
85             List<WidgetItem> widgetItems = entry.getValue();
86             String sectionName = (pkgItem.title == null) ? "" :
87                     indexer.computeSectionName(pkgItem.title);
88             result.add(WidgetsListHeaderEntry.create(pkgItem, sectionName, widgetItems));
89             result.add(new WidgetsListContentEntry(pkgItem, sectionName, widgetItems));
90         }
91         return result;
92     }
93 
94     /** Returns a mapping of packages to their widgets without static shortcuts. */
getAllWidgetsWithoutShortcuts()95     public synchronized Map<PackageUserKey, List<WidgetItem>> getAllWidgetsWithoutShortcuts() {
96         Map<PackageUserKey, List<WidgetItem>> packagesToWidgets = new HashMap<>();
97         mWidgetsList.forEach((packageItemInfo, widgetsAndShortcuts) -> {
98             List<WidgetItem> widgets = widgetsAndShortcuts.stream()
99                         .filter(item -> item.widgetInfo != null)
100                         .collect(toList());
101             if (widgets.size() > 0) {
102                 packagesToWidgets.put(
103                         new PackageUserKey(packageItemInfo.packageName, packageItemInfo.user),
104                         widgets);
105             }
106         });
107         return packagesToWidgets;
108     }
109 
110     /**
111      * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
112      *                    only widgets and shortcuts associated with the package/user are.
113      */
update( LauncherAppState app, @Nullable PackageUserKey packageUser)114     public List<ComponentWithLabelAndIcon> update(
115             LauncherAppState app, @Nullable PackageUserKey packageUser) {
116         Preconditions.assertWorkerThread();
117 
118         Context context = app.getContext();
119         final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
120         List<ComponentWithLabelAndIcon> updatedItems = new ArrayList<>();
121         try {
122             InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
123             PackageManager pm = app.getContext().getPackageManager();
124 
125             // Widgets
126             WidgetManagerHelper widgetManager = new WidgetManagerHelper(context);
127             for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) {
128                 LauncherAppWidgetProviderInfo launcherWidgetInfo =
129                         LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo);
130 
131                 widgetsAndShortcuts.add(new WidgetItem(
132                         launcherWidgetInfo, idp, app.getIconCache()));
133                 updatedItems.add(launcherWidgetInfo);
134             }
135 
136             // Shortcuts
137             for (ShortcutConfigActivityInfo info :
138                     queryList(context, packageUser)) {
139                 widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
140                 updatedItems.add(info);
141             }
142             setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
143         } catch (Exception e) {
144             if (!FeatureFlags.IS_STUDIO_BUILD && Utilities.isBinderSizeError(e)) {
145                 // the returned value may be incomplete and will not be refreshed until the next
146                 // time Launcher starts.
147                 // TODO: after figuring out a repro step, introduce a dirty bit to check when
148                 // onResume is called to refresh the widget provider list.
149             } else {
150                 throw e;
151             }
152         }
153 
154         return updatedItems;
155     }
156 
setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts, LauncherAppState app, @Nullable PackageUserKey packageUser)157     private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts,
158             LauncherAppState app, @Nullable PackageUserKey packageUser) {
159         if (DEBUG) {
160             Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
161         }
162 
163         // Temporary cache for {@link PackageItemInfos} to avoid having to go through
164         // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
165         PackageItemInfoCache packageItemInfoCache = new PackageItemInfoCache();
166 
167         if (packageUser == null) {
168             // Clear the list if this is an update on all widgets and shortcuts.
169             mWidgetsList.clear();
170         } else {
171             // Otherwise, only clear the widgets and shortcuts for the changed package.
172             mWidgetsList.remove(packageItemInfoCache.getOrCreate(packageUser));
173         }
174 
175         // add and update.
176         mWidgetsList.putAll(rawWidgetsShortcuts.stream()
177                 .filter(new WidgetValidityCheck(app))
178                 .flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
179                         .map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
180                 .collect(groupingBy(pair -> pair.first, mapping(pair -> pair.second, toList()))));
181 
182         // Update each package entry
183         IconCache iconCache = app.getIconCache();
184         for (PackageItemInfo p : packageItemInfoCache.values()) {
185             iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */);
186         }
187     }
188 
onPackageIconsUpdated(Set<String> packageNames, UserHandle user, LauncherAppState app)189     public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
190             LauncherAppState app) {
191         for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
192             if (packageNames.contains(entry.getKey().packageName)) {
193                 List<WidgetItem> items = entry.getValue();
194                 int count = items.size();
195                 for (int i = 0; i < count; i++) {
196                     WidgetItem item = items.get(i);
197                     if (item.user.equals(user)) {
198                         if (item.activityInfo != null) {
199                             items.set(i, new WidgetItem(item.activityInfo, app.getIconCache(),
200                                     app.getContext().getPackageManager()));
201                         } else {
202                             items.set(i, new WidgetItem(item.widgetInfo,
203                                     app.getInvariantDeviceProfile(), app.getIconCache()));
204                         }
205                     }
206                 }
207             }
208         }
209     }
210 
getWidgetProviderInfoByProviderName( ComponentName providerName, UserHandle user)211     public WidgetItem getWidgetProviderInfoByProviderName(
212             ComponentName providerName, UserHandle user) {
213         List<WidgetItem> widgetsList = mWidgetsList.get(
214                 new PackageItemInfo(providerName.getPackageName(), user));
215         if (widgetsList == null) {
216             return null;
217         }
218 
219         for (WidgetItem item : widgetsList) {
220             if (item.componentName.equals(providerName)) {
221                 return item;
222             }
223         }
224         return null;
225     }
226 
227     /** Returns {@link PackageItemInfo} of a pending widget. */
newPendingItemInfo(Context context, ComponentName provider, UserHandle user)228     public static PackageItemInfo newPendingItemInfo(Context context, ComponentName provider,
229             UserHandle user) {
230         Map<ComponentName, IntSet> widgetsToCategories =
231                 WidgetSections.getWidgetsToCategory(context);
232         if (widgetsToCategories.containsKey(provider)) {
233             Iterator<Integer> categoriesIterator = widgetsToCategories.get(provider).iterator();
234             int firstCategory = NO_CATEGORY;
235             while (categoriesIterator.hasNext() && firstCategory == NO_CATEGORY) {
236                 firstCategory = categoriesIterator.next();
237             }
238             return new PackageItemInfo(provider.getPackageName(), firstCategory, user);
239         }
240         return new PackageItemInfo(provider.getPackageName(), user);
241     }
242 
getPackageUserKeys(Context context, WidgetItem item)243     private List<PackageUserKey> getPackageUserKeys(Context context, WidgetItem item) {
244         Map<ComponentName, IntSet> widgetsToCategories =
245                 WidgetSections.getWidgetsToCategory(context);
246         IntSet categories = widgetsToCategories.get(item.componentName);
247         if (categories == null || categories.isEmpty()) {
248             return Arrays.asList(
249                     new PackageUserKey(item.componentName.getPackageName(), item.user));
250         }
251         List<PackageUserKey> packageUserKeys = new ArrayList<>();
252         categories.forEach(category -> {
253             if (category == NO_CATEGORY) {
254                 packageUserKeys.add(
255                         new PackageUserKey(item.componentName.getPackageName(),
256                                 item.user));
257             } else {
258                 packageUserKeys.add(new PackageUserKey(category, item.user));
259             }
260         });
261         return packageUserKeys;
262     }
263 
264     private static class WidgetValidityCheck implements Predicate<WidgetItem> {
265 
266         private final InvariantDeviceProfile mIdp;
267         private final AppFilter mAppFilter;
268 
WidgetValidityCheck(LauncherAppState app)269         WidgetValidityCheck(LauncherAppState app) {
270             mIdp = app.getInvariantDeviceProfile();
271             mAppFilter = new AppFilter(app.getContext());
272         }
273 
274         @Override
test(WidgetItem item)275         public boolean test(WidgetItem item) {
276             if (item.widgetInfo != null) {
277                 if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) {
278                     // Widget is hidden from picker
279                     return false;
280                 }
281 
282                 // Ensure that all widgets we show can be added on a workspace of this size
283                 if (!item.widgetInfo.isMinSizeFulfilled()) {
284                     if (DEBUG) {
285                         Log.d(TAG, String.format(
286                                 "Widget %s : can't fit on this device with a grid size: %dx%d",
287                                 item.componentName, mIdp.numColumns, mIdp.numRows));
288                     }
289                     return false;
290                 }
291             }
292             if (!mAppFilter.shouldShowApp(item.componentName)) {
293                 if (DEBUG) {
294                     Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
295                             item.componentName));
296                 }
297                 return false;
298             }
299 
300             return true;
301         }
302     }
303 
304     private static final class PackageItemInfoCache {
305         private final Map<PackageUserKey, PackageItemInfo> mMap = new ArrayMap<>();
306 
getOrCreate(PackageUserKey key)307         PackageItemInfo getOrCreate(PackageUserKey key) {
308             PackageItemInfo pInfo = mMap.get(key);
309             if (pInfo == null) {
310                 pInfo = new PackageItemInfo(key.mPackageName, key.mWidgetCategory, key.mUser);
311                 pInfo.user = key.mUser;
312                 mMap.put(key,  pInfo);
313             }
314             return pInfo;
315         }
316 
values()317         Collection<PackageItemInfo> values() {
318             return mMap.values();
319         }
320     }
321 }