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