Home | History | Annotate | Download | only in launcher3
      1 package com.android.launcher3;
      2 
      3 import android.appwidget.AppWidgetHost;
      4 import android.appwidget.AppWidgetManager;
      5 import android.content.ComponentName;
      6 import android.content.Context;
      7 import android.content.Intent;
      8 import android.content.pm.ActivityInfo;
      9 import android.content.pm.ApplicationInfo;
     10 import android.content.pm.PackageManager;
     11 import android.content.pm.ResolveInfo;
     12 import android.content.res.Resources;
     13 import android.content.res.XmlResourceParser;
     14 import android.os.Bundle;
     15 import android.text.TextUtils;
     16 import android.util.ArrayMap;
     17 import android.util.Log;
     18 import com.android.launcher3.LauncherSettings.Favorites;
     19 import com.android.launcher3.util.Thunk;
     20 import java.io.IOException;
     21 import java.net.URISyntaxException;
     22 import java.util.List;
     23 import org.xmlpull.v1.XmlPullParser;
     24 import org.xmlpull.v1.XmlPullParserException;
     25 
     26 /**
     27  * Implements the layout parser with rules for internal layouts and partner layouts.
     28  */
     29 public class DefaultLayoutParser extends AutoInstallsLayout {
     30     private static final String TAG = "DefaultLayoutParser";
     31 
     32     protected static final String TAG_RESOLVE = "resolve";
     33     private static final String TAG_FAVORITES = "favorites";
     34     protected static final String TAG_FAVORITE = "favorite";
     35     private static final String TAG_APPWIDGET = "appwidget";
     36     protected static final String TAG_SHORTCUT = "shortcut";
     37     private static final String TAG_FOLDER = "folder";
     38     private static final String TAG_PARTNER_FOLDER = "partner-folder";
     39 
     40     protected static final String ATTR_URI = "uri";
     41     private static final String ATTR_CONTAINER = "container";
     42     private static final String ATTR_SCREEN = "screen";
     43     private static final String ATTR_FOLDER_ITEMS = "folderItems";
     44 
     45     // TODO: Remove support for this broadcast, instead use widget options to send bind time options
     46     private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
     47             "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
     48 
     49     public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
     50             LayoutParserCallback callback, Resources sourceRes, int layoutId) {
     51         super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES);
     52     }
     53 
     54     @Override
     55     protected ArrayMap<String, TagParser> getFolderElementsMap() {
     56         return getFolderElementsMap(mSourceRes);
     57     }
     58 
     59     @Thunk ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
     60         ArrayMap<String, TagParser> parsers = new ArrayMap<>();
     61         parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
     62         parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
     63         return parsers;
     64     }
     65 
     66     @Override
     67     protected ArrayMap<String, TagParser> getLayoutElementsMap() {
     68         ArrayMap<String, TagParser> parsers = new ArrayMap<>();
     69         parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
     70         parsers.put(TAG_APPWIDGET, new AppWidgetParser());
     71         parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
     72         parsers.put(TAG_RESOLVE, new ResolveParser());
     73         parsers.put(TAG_FOLDER, new MyFolderParser());
     74         parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser());
     75         return parsers;
     76     }
     77 
     78     @Override
     79     protected void parseContainerAndScreen(XmlResourceParser parser, long[] out) {
     80         out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
     81         String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
     82         if (strContainer != null) {
     83             out[0] = Long.valueOf(strContainer);
     84         }
     85         out[1] = Long.parseLong(getAttributeValue(parser, ATTR_SCREEN));
     86     }
     87 
     88     /**
     89      * AppShortcutParser which also supports adding URI based intents
     90      */
     91     public class AppShortcutWithUriParser extends AppShortcutParser {
     92 
     93         @Override
     94         protected long invalidPackageOrClass(XmlResourceParser parser) {
     95             final String uri = getAttributeValue(parser, ATTR_URI);
     96             if (TextUtils.isEmpty(uri)) {
     97                 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
     98                 return -1;
     99             }
    100 
    101             final Intent metaIntent;
    102             try {
    103                 metaIntent = Intent.parseUri(uri, 0);
    104             } catch (URISyntaxException e) {
    105                 Log.e(TAG, "Unable to add meta-favorite: " + uri, e);
    106                 return -1;
    107             }
    108 
    109             ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
    110                     PackageManager.MATCH_DEFAULT_ONLY);
    111             final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
    112                     metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
    113 
    114             // Verify that the result is an app and not just the resolver dialog asking which
    115             // app to use.
    116             if (wouldLaunchResolverActivity(resolved, appList)) {
    117                 // If only one of the results is a system app then choose that as the default.
    118                 final ResolveInfo systemApp = getSingleSystemActivity(appList);
    119                 if (systemApp == null) {
    120                     // There is no logical choice for this meta-favorite, so rather than making
    121                     // a bad choice just add nothing.
    122                     Log.w(TAG, "No preference or single system activity found for "
    123                             + metaIntent.toString());
    124                     return -1;
    125                 }
    126                 resolved = systemApp;
    127             }
    128             final ActivityInfo info = resolved.activityInfo;
    129             final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
    130             if (intent == null) {
    131                 return -1;
    132             }
    133             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    134                     Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    135 
    136             return addShortcut(info.loadLabel(mPackageManager).toString(), intent,
    137                     Favorites.ITEM_TYPE_APPLICATION);
    138         }
    139 
    140         private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
    141             ResolveInfo systemResolve = null;
    142             final int N = appList.size();
    143             for (int i = 0; i < N; ++i) {
    144                 try {
    145                     ApplicationInfo info = mPackageManager.getApplicationInfo(
    146                             appList.get(i).activityInfo.packageName, 0);
    147                     if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
    148                         if (systemResolve != null) {
    149                             return null;
    150                         } else {
    151                             systemResolve = appList.get(i);
    152                         }
    153                     }
    154                 } catch (PackageManager.NameNotFoundException e) {
    155                     Log.w(TAG, "Unable to get info about resolve results", e);
    156                     return null;
    157                 }
    158             }
    159             return systemResolve;
    160         }
    161 
    162         private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
    163                 List<ResolveInfo> appList) {
    164             // If the list contains the above resolved activity, then it can't be
    165             // ResolverActivity itself.
    166             for (int i = 0; i < appList.size(); ++i) {
    167                 ResolveInfo tmp = appList.get(i);
    168                 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
    169                         && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
    170                     return false;
    171                 }
    172             }
    173             return true;
    174         }
    175     }
    176 
    177 
    178     /**
    179      * Shortcut parser which allows any uri and not just web urls.
    180      */
    181     public class UriShortcutParser extends ShortcutParser {
    182 
    183         public UriShortcutParser(Resources iconRes) {
    184             super(iconRes);
    185         }
    186 
    187         @Override
    188         protected Intent parseIntent(XmlResourceParser parser) {
    189             String uri = null;
    190             try {
    191                 uri = getAttributeValue(parser, ATTR_URI);
    192                 return Intent.parseUri(uri, 0);
    193             } catch (URISyntaxException e) {
    194                 Log.w(TAG, "Shortcut has malformed uri: " + uri);
    195                 return null; // Oh well
    196             }
    197         }
    198     }
    199 
    200     /**
    201      * Contains a list of <favorite> nodes, and accepts the first successfully parsed node.
    202      */
    203     public class ResolveParser implements TagParser {
    204 
    205         private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
    206 
    207         @Override
    208         public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
    209                 IOException {
    210             final int groupDepth = parser.getDepth();
    211             int type;
    212             long addedId = -1;
    213             while ((type = parser.next()) != XmlPullParser.END_TAG ||
    214                     parser.getDepth() > groupDepth) {
    215                 if (type != XmlPullParser.START_TAG || addedId > -1) {
    216                     continue;
    217                 }
    218                 final String fallback_item_name = parser.getName();
    219                 if (TAG_FAVORITE.equals(fallback_item_name)) {
    220                     addedId = mChildParser.parseAndAdd(parser);
    221                 } else {
    222                     Log.e(TAG, "Fallback groups can contain only favorites, found "
    223                             + fallback_item_name);
    224                 }
    225             }
    226             return addedId;
    227         }
    228     }
    229 
    230     /**
    231      * A parser which adds a folder whose contents come from partner apk.
    232      */
    233     @Thunk class PartnerFolderParser implements TagParser {
    234 
    235         @Override
    236         public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
    237                 IOException {
    238             // Folder contents come from an external XML resource
    239             final Partner partner = Partner.get(mPackageManager);
    240             if (partner != null) {
    241                 final Resources partnerRes = partner.getResources();
    242                 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
    243                         "xml", partner.getPackageName());
    244                 if (resId != 0) {
    245                     final XmlResourceParser partnerParser = partnerRes.getXml(resId);
    246                     beginDocument(partnerParser, TAG_FOLDER);
    247 
    248                     FolderParser folderParser = new FolderParser(getFolderElementsMap(partnerRes));
    249                     return folderParser.parseAndAdd(partnerParser);
    250                 }
    251             }
    252             return -1;
    253         }
    254     }
    255 
    256     /**
    257      * An extension of FolderParser which allows adding items from a different xml.
    258      */
    259     @Thunk class MyFolderParser extends FolderParser {
    260 
    261         @Override
    262         public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
    263                 IOException {
    264             final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
    265             if (resId != 0) {
    266                 parser = mSourceRes.getXml(resId);
    267                 beginDocument(parser, TAG_FOLDER);
    268             }
    269             return super.parseAndAdd(parser);
    270         }
    271     }
    272 
    273 
    274     /**
    275      * AppWidget parser which enforces that the app is already installed when the layout is parsed.
    276      */
    277     protected class AppWidgetParser extends PendingWidgetParser {
    278 
    279         @Override
    280         protected long verifyAndInsert(ComponentName cn, Bundle extras) {
    281             try {
    282                 mPackageManager.getReceiverInfo(cn, 0);
    283             } catch (Exception e) {
    284                 String[] packages = mPackageManager.currentToCanonicalPackageNames(
    285                         new String[] { cn.getPackageName() });
    286                 cn = new ComponentName(packages[0], cn.getClassName());
    287                 try {
    288                     mPackageManager.getReceiverInfo(cn, 0);
    289                 } catch (Exception e1) {
    290                     Log.d(TAG, "Can't find widget provider: " + cn.getClassName());
    291                     return -1;
    292                 }
    293             }
    294 
    295             final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
    296             long insertedId = -1;
    297             try {
    298                 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
    299 
    300                 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
    301                     Log.e(TAG, "Unable to bind app widget id " + cn);
    302                     mAppWidgetHost.deleteAppWidgetId(appWidgetId);
    303                     return -1;
    304                 }
    305 
    306                 mValues.put(Favorites.APPWIDGET_ID, appWidgetId);
    307                 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
    308                 mValues.put(Favorites._ID, mCallback.generateNewItemId());
    309                 insertedId = mCallback.insertAndCheck(mDb, mValues);
    310                 if (insertedId < 0) {
    311                     mAppWidgetHost.deleteAppWidgetId(appWidgetId);
    312                     return insertedId;
    313                 }
    314 
    315                 // Send a broadcast to configure the widget
    316                 if (!extras.isEmpty()) {
    317                     Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
    318                     intent.setComponent(cn);
    319                     intent.putExtras(extras);
    320                     intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
    321                     mContext.sendBroadcast(intent);
    322                 }
    323             } catch (RuntimeException ex) {
    324                 Log.e(TAG, "Problem allocating appWidgetId", ex);
    325             }
    326             return insertedId;
    327         }
    328     }
    329 }
    330