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