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}