• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }