Home | History | Annotate | Download | only in model
      1 
      2 package com.android.launcher3.model;
      3 
      4 import android.appwidget.AppWidgetProviderInfo;
      5 import android.content.ComponentName;
      6 import android.content.Context;
      7 import android.content.Intent;
      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.Utilities;
     20 import com.android.launcher3.compat.AlphabeticIndexCompat;
     21 import com.android.launcher3.compat.AppWidgetManagerCompat;
     22 import com.android.launcher3.compat.UserHandleCompat;
     23 import com.android.launcher3.config.ProviderConfig;
     24 
     25 import java.util.ArrayList;
     26 import java.util.Collections;
     27 import java.util.Comparator;
     28 import java.util.HashMap;
     29 import java.util.List;
     30 
     31 /**
     32  * Widgets data model that is used by the adapters of the widget views and controllers.
     33  *
     34  * <p> The widgets and shortcuts are organized using package name as its index.
     35  */
     36 public class WidgetsModel {
     37 
     38     private static final String TAG = "WidgetsModel";
     39     private static final boolean DEBUG = false;
     40 
     41     /* List of packages that is tracked by this model. */
     42     private final ArrayList<PackageItemInfo> mPackageItemInfos;
     43 
     44     /* Map of widgets and shortcuts that are tracked per package. */
     45     private final HashMap<PackageItemInfo, ArrayList<Object>> mWidgetsList;
     46 
     47     private final AppWidgetManagerCompat mAppWidgetMgr;
     48     private final WidgetsAndShortcutNameComparator mWidgetAndShortcutNameComparator;
     49     private final Comparator<ItemInfo> mAppNameComparator;
     50     private final IconCache mIconCache;
     51     private final AppFilter mAppFilter;
     52     private final AlphabeticIndexCompat mIndexer;
     53 
     54     private ArrayList<Object> mRawList;
     55 
     56     public WidgetsModel(Context context,  IconCache iconCache, AppFilter appFilter) {
     57         mAppWidgetMgr = AppWidgetManagerCompat.getInstance(context);
     58         mWidgetAndShortcutNameComparator = new WidgetsAndShortcutNameComparator(context);
     59         mAppNameComparator = (new AppNameComparator(context)).getAppInfoComparator();
     60         mIconCache = iconCache;
     61         mAppFilter = appFilter;
     62         mIndexer = new AlphabeticIndexCompat(context);
     63         mPackageItemInfos = new ArrayList<>();
     64         mWidgetsList = new HashMap<>();
     65 
     66         mRawList = new ArrayList<>();
     67     }
     68 
     69     @SuppressWarnings("unchecked")
     70     private WidgetsModel(WidgetsModel model) {
     71         mAppWidgetMgr = model.mAppWidgetMgr;
     72         mPackageItemInfos = (ArrayList<PackageItemInfo>) model.mPackageItemInfos.clone();
     73         mWidgetsList = (HashMap<PackageItemInfo, ArrayList<Object>>) model.mWidgetsList.clone();
     74         mWidgetAndShortcutNameComparator = model.mWidgetAndShortcutNameComparator;
     75         mAppNameComparator = model.mAppNameComparator;
     76         mIconCache = model.mIconCache;
     77         mAppFilter = model.mAppFilter;
     78         mIndexer = model.mIndexer;
     79         mRawList = (ArrayList<Object>) model.mRawList.clone();
     80     }
     81 
     82     // Access methods that may be deleted if the private fields are made package-private.
     83     public int getPackageSize() {
     84         return mPackageItemInfos.size();
     85     }
     86 
     87     // Access methods that may be deleted if the private fields are made package-private.
     88     public PackageItemInfo getPackageItemInfo(int pos) {
     89         if (pos >= mPackageItemInfos.size() || pos < 0) {
     90             return null;
     91         }
     92         return mPackageItemInfos.get(pos);
     93     }
     94 
     95     public List<Object> getSortedWidgets(int pos) {
     96         return mWidgetsList.get(mPackageItemInfos.get(pos));
     97     }
     98 
     99     public ArrayList<Object> getRawList() {
    100         return mRawList;
    101     }
    102 
    103     public boolean isEmpty() {
    104         return mRawList.isEmpty();
    105     }
    106 
    107     public WidgetsModel updateAndClone(Context context) {
    108         Utilities.assertWorkerThread();
    109 
    110         try {
    111             final ArrayList<Object> widgetsAndShortcuts = new ArrayList<>();
    112             // Widgets
    113             for (AppWidgetProviderInfo widgetInfo :
    114                     AppWidgetManagerCompat.getInstance(context).getAllProviders()) {
    115                 widgetsAndShortcuts.add(LauncherAppWidgetProviderInfo
    116                         .fromProviderInfo(context, widgetInfo));
    117             }
    118             // Shortcuts
    119             widgetsAndShortcuts.addAll(context.getPackageManager().queryIntentActivities(
    120                     new Intent(Intent.ACTION_CREATE_SHORTCUT), 0));
    121             setWidgetsAndShortcuts(widgetsAndShortcuts);
    122         } catch (Exception e) {
    123             if (!LauncherAppState.isDogfoodBuild() &&
    124                     (e.getCause() instanceof TransactionTooLargeException ||
    125                             e.getCause() instanceof DeadObjectException)) {
    126                 // the returned value may be incomplete and will not be refreshed until the next
    127                 // time Launcher starts.
    128                 // TODO: after figuring out a repro step, introduce a dirty bit to check when
    129                 // onResume is called to refresh the widget provider list.
    130             } else {
    131                 throw e;
    132             }
    133         }
    134         return clone();
    135     }
    136 
    137     private void setWidgetsAndShortcuts(ArrayList<Object> rawWidgetsShortcuts) {
    138         mRawList = rawWidgetsShortcuts;
    139         if (DEBUG) {
    140             Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
    141         }
    142 
    143         // Temporary list for {@link PackageItemInfos} to avoid having to go through
    144         // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
    145         HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
    146 
    147         // clear the lists.
    148         mWidgetsList.clear();
    149         mPackageItemInfos.clear();
    150         mWidgetAndShortcutNameComparator.reset();
    151 
    152         InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
    153 
    154         // add and update.
    155         for (Object o: rawWidgetsShortcuts) {
    156             String packageName = "";
    157             UserHandleCompat userHandle = null;
    158             ComponentName componentName = null;
    159             if (o instanceof LauncherAppWidgetProviderInfo) {
    160                 LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
    161 
    162                 // Ensure that all widgets we show can be added on a workspace of this size
    163                 int minSpanX = Math.min(widgetInfo.spanX, widgetInfo.minSpanX);
    164                 int minSpanY = Math.min(widgetInfo.spanY, widgetInfo.minSpanY);
    165                 if (minSpanX <= (int) idp.numColumns &&
    166                     minSpanY <= (int) idp.numRows) {
    167                     componentName = widgetInfo.provider;
    168                     packageName = widgetInfo.provider.getPackageName();
    169                     userHandle = mAppWidgetMgr.getUser(widgetInfo);
    170                 } else {
    171                     if (DEBUG) {
    172                         Log.d(TAG, String.format(
    173                                 "Widget %s : (%d X %d) can't fit on this device",
    174                                 widgetInfo.provider, minSpanX, minSpanY));
    175                     }
    176                     continue;
    177                 }
    178             } else if (o instanceof ResolveInfo) {
    179                 ResolveInfo resolveInfo = (ResolveInfo) o;
    180                 componentName = new ComponentName(resolveInfo.activityInfo.packageName,
    181                         resolveInfo.activityInfo.name);
    182                 packageName = resolveInfo.activityInfo.packageName;
    183                 userHandle = UserHandleCompat.myUserHandle();
    184             }
    185 
    186             if (componentName == null || userHandle == null) {
    187                 Log.e(TAG, String.format("Widget cannot be set for %s.", o.getClass().toString()));
    188                 continue;
    189             }
    190             if (mAppFilter != null && !mAppFilter.shouldShowApp(componentName)) {
    191                 if (DEBUG) {
    192                     Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
    193                         packageName));
    194                 }
    195                 continue;
    196             }
    197 
    198             PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
    199             ArrayList<Object> widgetsShortcutsList = mWidgetsList.get(pInfo);
    200             if (widgetsShortcutsList != null) {
    201                 widgetsShortcutsList.add(o);
    202             } else {
    203                 widgetsShortcutsList = new ArrayList<>();
    204                 widgetsShortcutsList.add(o);
    205                 pInfo = new PackageItemInfo(packageName);
    206                 mIconCache.getTitleAndIconForApp(packageName, userHandle,
    207                         true /* userLowResIcon */, pInfo);
    208                 pInfo.titleSectionName = mIndexer.computeSectionName(pInfo.title);
    209                 mWidgetsList.put(pInfo, widgetsShortcutsList);
    210                 tmpPackageItemInfos.put(packageName,  pInfo);
    211                 mPackageItemInfos.add(pInfo);
    212             }
    213         }
    214 
    215         // sort.
    216         Collections.sort(mPackageItemInfos, mAppNameComparator);
    217         for (PackageItemInfo p: mPackageItemInfos) {
    218             Collections.sort(mWidgetsList.get(p), mWidgetAndShortcutNameComparator);
    219         }
    220     }
    221 
    222     /**
    223      * Create a snapshot of the widgets model.
    224      * <p>
    225      * Usage case: view binding without being modified from package updates.
    226      */
    227     @Override
    228     public WidgetsModel clone(){
    229         return new WidgetsModel(this);
    230     }
    231 }