Home | History | Annotate | Download | only in model
      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      */
     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      */
     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 
    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 }