1 package com.android.launcher3;
2 
3 import android.appwidget.AppWidgetHost;
4 import android.content.Context;
5 import android.content.Intent;
6 import android.content.pm.ActivityInfo;
7 import android.content.pm.ApplicationInfo;
8 import android.content.pm.PackageManager;
9 import android.content.pm.ResolveInfo;
10 import android.content.res.Resources;
11 import android.content.res.XmlResourceParser;
12 import android.text.TextUtils;
13 import android.util.Log;
14 
15 import com.android.launcher3.LauncherSettings.Favorites;
16 import com.android.launcher3.util.Thunk;
17 
18 import org.xmlpull.v1.XmlPullParser;
19 import org.xmlpull.v1.XmlPullParserException;
20 
21 import java.io.IOException;
22 import java.net.URISyntaxException;
23 import java.util.HashMap;
24 import java.util.List;
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     private 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 
DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost, LayoutParserCallback callback, Resources sourceRes, int layoutId)45     public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
46             LayoutParserCallback callback, Resources sourceRes, int layoutId) {
47         super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES);
48     }
49 
DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost, LayoutParserCallback callback, Resources sourceRes, int layoutId, String rootTag)50     public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
51             LayoutParserCallback callback, Resources sourceRes, int layoutId, String rootTag) {
52         super(context, appWidgetHost, callback, sourceRes, layoutId, rootTag);
53     }
54 
55     @Override
getFolderElementsMap()56     protected HashMap<String, TagParser> getFolderElementsMap() {
57         return getFolderElementsMap(mSourceRes);
58     }
59 
getFolderElementsMap(Resources res)60     @Thunk HashMap<String, TagParser> getFolderElementsMap(Resources res) {
61         HashMap<String, TagParser> parsers = new HashMap<String, TagParser>();
62         parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
63         parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
64         return parsers;
65     }
66 
67     @Override
getLayoutElementsMap()68     protected HashMap<String, TagParser> getLayoutElementsMap() {
69         HashMap<String, TagParser> parsers = new HashMap<String, TagParser>();
70         parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
71         parsers.put(TAG_APPWIDGET, new AppWidgetParser());
72         parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
73         parsers.put(TAG_RESOLVE, new ResolveParser());
74         parsers.put(TAG_FOLDER, new MyFolderParser());
75         parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser());
76         return parsers;
77     }
78 
79     @Override
parseContainerAndScreen(XmlResourceParser parser, long[] out)80     protected void parseContainerAndScreen(XmlResourceParser parser, long[] out) {
81         out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
82         String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
83         if (strContainer != null) {
84             out[0] = Long.valueOf(strContainer);
85         }
86         out[1] = Long.parseLong(getAttributeValue(parser, ATTR_SCREEN));
87     }
88 
89     /**
90      * AppShortcutParser which also supports adding URI based intents
91      */
92     @Thunk class AppShortcutWithUriParser extends AppShortcutParser {
93 
94         @Override
invalidPackageOrClass(XmlResourceParser parser)95         protected long invalidPackageOrClass(XmlResourceParser parser) {
96             final String uri = getAttributeValue(parser, ATTR_URI);
97             if (TextUtils.isEmpty(uri)) {
98                 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
99                 return -1;
100             }
101 
102             final Intent metaIntent;
103             try {
104                 metaIntent = Intent.parseUri(uri, 0);
105             } catch (URISyntaxException e) {
106                 Log.e(TAG, "Unable to add meta-favorite: " + uri, e);
107                 return -1;
108             }
109 
110             ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
111                     PackageManager.MATCH_DEFAULT_ONLY);
112             final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
113                     metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
114 
115             // Verify that the result is an app and not just the resolver dialog asking which
116             // app to use.
117             if (wouldLaunchResolverActivity(resolved, appList)) {
118                 // If only one of the results is a system app then choose that as the default.
119                 final ResolveInfo systemApp = getSingleSystemActivity(appList);
120                 if (systemApp == null) {
121                     // There is no logical choice for this meta-favorite, so rather than making
122                     // a bad choice just add nothing.
123                     Log.w(TAG, "No preference or single system activity found for "
124                             + metaIntent.toString());
125                     return -1;
126                 }
127                 resolved = systemApp;
128             }
129             final ActivityInfo info = resolved.activityInfo;
130             final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
131             if (intent == null) {
132                 return -1;
133             }
134             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
135                     Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
136 
137             return addShortcut(info.loadLabel(mPackageManager).toString(), intent,
138                     Favorites.ITEM_TYPE_APPLICATION);
139         }
140 
getSingleSystemActivity(List<ResolveInfo> appList)141         private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
142             ResolveInfo systemResolve = null;
143             final int N = appList.size();
144             for (int i = 0; i < N; ++i) {
145                 try {
146                     ApplicationInfo info = mPackageManager.getApplicationInfo(
147                             appList.get(i).activityInfo.packageName, 0);
148                     if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
149                         if (systemResolve != null) {
150                             return null;
151                         } else {
152                             systemResolve = appList.get(i);
153                         }
154                     }
155                 } catch (PackageManager.NameNotFoundException e) {
156                     Log.w(TAG, "Unable to get info about resolve results", e);
157                     return null;
158                 }
159             }
160             return systemResolve;
161         }
162 
wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList)163         private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
164                 List<ResolveInfo> appList) {
165             // If the list contains the above resolved activity, then it can't be
166             // ResolverActivity itself.
167             for (int i = 0; i < appList.size(); ++i) {
168                 ResolveInfo tmp = appList.get(i);
169                 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
170                         && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
171                     return false;
172                 }
173             }
174             return true;
175         }
176     }
177 
178 
179     /**
180      * Shortcut parser which allows any uri and not just web urls.
181      */
182     private class UriShortcutParser extends ShortcutParser {
183 
UriShortcutParser(Resources iconRes)184         public UriShortcutParser(Resources iconRes) {
185             super(iconRes);
186         }
187 
188         @Override
parseIntent(XmlResourceParser parser)189         protected Intent parseIntent(XmlResourceParser parser) {
190             String uri = null;
191             try {
192                 uri = getAttributeValue(parser, ATTR_URI);
193                 return Intent.parseUri(uri, 0);
194             } catch (URISyntaxException e) {
195                 Log.w(TAG, "Shortcut has malformed uri: " + uri);
196                 return null; // Oh well
197             }
198         }
199     }
200 
201     /**
202      * Contains a list of <favorite> nodes, and accepts the first successfully parsed node.
203      */
204     protected class ResolveParser implements TagParser {
205 
206         private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
207 
208         @Override
parseAndAdd(XmlResourceParser parser)209         public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
210                 IOException {
211             final int groupDepth = parser.getDepth();
212             int type;
213             long addedId = -1;
214             while ((type = parser.next()) != XmlPullParser.END_TAG ||
215                     parser.getDepth() > groupDepth) {
216                 if (type != XmlPullParser.START_TAG || addedId > -1) {
217                     continue;
218                 }
219                 final String fallback_item_name = parser.getName();
220                 if (TAG_FAVORITE.equals(fallback_item_name)) {
221                     addedId = mChildParser.parseAndAdd(parser);
222                 } else {
223                     Log.e(TAG, "Fallback groups can contain only favorites, found "
224                             + fallback_item_name);
225                 }
226             }
227             return addedId;
228         }
229     }
230 
231     /**
232      * A parser which adds a folder whose contents come from partner apk.
233      */
234     @Thunk class PartnerFolderParser implements TagParser {
235 
236         @Override
parseAndAdd(XmlResourceParser parser)237         public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
238                 IOException {
239             // Folder contents come from an external XML resource
240             final Partner partner = Partner.get(mPackageManager);
241             if (partner != null) {
242                 final Resources partnerRes = partner.getResources();
243                 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
244                         "xml", partner.getPackageName());
245                 if (resId != 0) {
246                     final XmlResourceParser partnerParser = partnerRes.getXml(resId);
247                     beginDocument(partnerParser, TAG_FOLDER);
248 
249                     FolderParser folderParser = new FolderParser(getFolderElementsMap(partnerRes));
250                     return folderParser.parseAndAdd(partnerParser);
251                 }
252             }
253             return -1;
254         }
255     }
256 
257     /**
258      * An extension of FolderParser which allows adding items from a different xml.
259      */
260     @Thunk class MyFolderParser extends FolderParser {
261 
262         @Override
parseAndAdd(XmlResourceParser parser)263         public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
264                 IOException {
265             final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
266             if (resId != 0) {
267                 parser = mSourceRes.getXml(resId);
268                 beginDocument(parser, TAG_FOLDER);
269             }
270             return super.parseAndAdd(parser);
271         }
272     }
273 }
274