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