blob: 75297f63b5e1e7b6828a03b92a581b389179b678 [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;
Sunny Goyal86df1382016-08-10 15:03:22 -070013import android.os.Bundle;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070014import android.text.TextUtils;
Rajeev Kumar26453a22017-06-09 16:02:25 -070015import android.util.ArrayMap;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070016import android.util.Log;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070017import com.android.launcher3.LauncherSettings.Favorites;
Adam Cohen091440a2015-03-18 14:16:05 -070018import com.android.launcher3.util.Thunk;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070019import java.io.IOException;
20import java.net.URISyntaxException;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070021import java.util.List;
Rajeev Kumar26453a22017-06-09 16:02:25 -070022import org.xmlpull.v1.XmlPullParser;
23import org.xmlpull.v1.XmlPullParserException;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070024
25/**
26 * Implements the layout parser with rules for internal layouts and partner layouts.
27 */
28public class DefaultLayoutParser extends AutoInstallsLayout {
29 private static final String TAG = "DefaultLayoutParser";
30
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080031 protected static final String TAG_RESOLVE = "resolve";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070032 private static final String TAG_FAVORITES = "favorites";
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080033 protected static final String TAG_FAVORITE = "favorite";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070034 private static final String TAG_APPWIDGET = "appwidget";
Sunny Goyala5c8a9e2016-07-08 08:32:44 -070035 protected static final String TAG_SHORTCUT = "shortcut";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070036 private static final String TAG_FOLDER = "folder";
37 private static final String TAG_PARTNER_FOLDER = "partner-folder";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070038
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080039 protected static final String ATTR_URI = "uri";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070040 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
Sunny Goyal86df1382016-08-10 15:03:22 -070044 // 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
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070048 public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
49 LayoutParserCallback callback, Resources sourceRes, int layoutId) {
50 super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES);
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080051 }
52
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070053 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070054 protected ArrayMap<String, TagParser> getFolderElementsMap() {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070055 return getFolderElementsMap(mSourceRes);
56 }
57
Rajeev Kumar26453a22017-06-09 16:02:25 -070058 @Thunk ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
59 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070060 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
61 parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
62 return parsers;
63 }
64
65 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070066 protected ArrayMap<String, TagParser> getLayoutElementsMap() {
67 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070068 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
Sunny Goyal0d742312019-03-04 20:22:26 -080078 protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070079 out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
80 String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
81 if (strContainer != null) {
Sunny Goyalefb7e842018-10-04 15:11:00 -070082 out[0] = Integer.parseInt(strContainer);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070083 }
Sunny Goyalefb7e842018-10-04 15:11:00 -070084 out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070085 }
86
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070087 /**
88 * AppShortcutParser which also supports adding URI based intents
89 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -070090 public class AppShortcutWithUriParser extends AppShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070091
92 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -080093 protected int invalidPackageOrClass(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070094 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
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
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 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700180 public class UriShortcutParser extends ShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700181
182 public UriShortcutParser(Resources iconRes) {
183 super(iconRes);
184 }
185
186 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800187 protected Intent parseIntent(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700188 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 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700202 public class ResolveParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700203
204 private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
205
206 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800207 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700208 IOException {
209 final int groupDepth = parser.getDepth();
210 int type;
Sunny Goyalefb7e842018-10-04 15:11:00 -0700211 int addedId = -1;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700212 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 */
Adam Cohen091440a2015-03-18 14:16:05 -0700232 @Thunk class PartnerFolderParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700233
234 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800235 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700236 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) {
Sunny Goyal0d742312019-03-04 20:22:26 -0800244 final XmlPullParser partnerParser = partnerRes.getXml(resId);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700245 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 */
Adam Cohen091440a2015-03-18 14:16:05 -0700258 @Thunk class MyFolderParser extends FolderParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700259
260 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800261 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700262 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 }
Sunny Goyal86df1382016-08-10 15:03:22 -0700271
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
Sunny Goyalefb7e842018-10-04 15:11:00 -0700279 protected int verifyAndInsert(ComponentName cn, Bundle extras) {
Sunny Goyal86df1382016-08-10 15:03:22 -0700280 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);
Sunny Goyalefb7e842018-10-04 15:11:00 -0700295 int insertedId = -1;
Sunny Goyal86df1382016-08-10 15:03:22 -0700296 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 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700328}