blob: af855947794b9db4a154a5ebf298fe1a5a85d7f0 [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;
Samuel Fufaca37b8a2019-08-19 17:04:36 -070017
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070018import com.android.launcher3.LauncherSettings.Favorites;
Adam Cohen091440a2015-03-18 14:16:05 -070019import com.android.launcher3.util.Thunk;
Samuel Fufaca37b8a2019-08-19 17:04:36 -070020
21import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070024import java.io.IOException;
25import java.net.URISyntaxException;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070026import java.util.List;
27
28/**
29 * Implements the layout parser with rules for internal layouts and partner layouts.
30 */
31public class DefaultLayoutParser extends AutoInstallsLayout {
32 private static final String TAG = "DefaultLayoutParser";
33
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080034 protected static final String TAG_RESOLVE = "resolve";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070035 private static final String TAG_FAVORITES = "favorites";
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080036 protected static final String TAG_FAVORITE = "favorite";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070037 private static final String TAG_APPWIDGET = "appwidget";
Sunny Goyala5c8a9e2016-07-08 08:32:44 -070038 protected static final String TAG_SHORTCUT = "shortcut";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070039 private static final String TAG_FOLDER = "folder";
40 private static final String TAG_PARTNER_FOLDER = "partner-folder";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070041
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080042 protected static final String ATTR_URI = "uri";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070043 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
Sunny Goyal86df1382016-08-10 15:03:22 -070047 // TODO: Remove support for this broadcast, instead use widget options to send bind time options
48 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
49 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
50
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070051 public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
52 LayoutParserCallback callback, Resources sourceRes, int layoutId) {
53 super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES);
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080054 }
55
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070056 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070057 protected ArrayMap<String, TagParser> getFolderElementsMap() {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070058 return getFolderElementsMap(mSourceRes);
59 }
60
Samuel Fufaca37b8a2019-08-19 17:04:36 -070061 @Thunk
62 ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
Rajeev Kumar26453a22017-06-09 16:02:25 -070063 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070064 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
65 parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
66 return parsers;
67 }
68
69 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070070 protected ArrayMap<String, TagParser> getLayoutElementsMap() {
71 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070072 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
73 parsers.put(TAG_APPWIDGET, new AppWidgetParser());
Samuel Fufaca37b8a2019-08-19 17:04:36 -070074 parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070075 parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
76 parsers.put(TAG_RESOLVE, new ResolveParser());
77 parsers.put(TAG_FOLDER, new MyFolderParser());
78 parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser());
79 return parsers;
80 }
81
82 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -080083 protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070084 out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
85 String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
86 if (strContainer != null) {
Sunny Goyalefb7e842018-10-04 15:11:00 -070087 out[0] = Integer.parseInt(strContainer);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070088 }
Sunny Goyalefb7e842018-10-04 15:11:00 -070089 out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070090 }
91
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070092 /**
93 * AppShortcutParser which also supports adding URI based intents
94 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -070095 public class AppShortcutWithUriParser extends AppShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070096
97 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -080098 protected int invalidPackageOrClass(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070099 final String uri = getAttributeValue(parser, ATTR_URI);
100 if (TextUtils.isEmpty(uri)) {
101 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
102 return -1;
103 }
104
105 final Intent metaIntent;
106 try {
107 metaIntent = Intent.parseUri(uri, 0);
108 } catch (URISyntaxException e) {
109 Log.e(TAG, "Unable to add meta-favorite: " + uri, e);
110 return -1;
111 }
112
113 ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
114 PackageManager.MATCH_DEFAULT_ONLY);
115 final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
116 metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
117
118 // Verify that the result is an app and not just the resolver dialog asking which
119 // app to use.
120 if (wouldLaunchResolverActivity(resolved, appList)) {
121 // If only one of the results is a system app then choose that as the default.
122 final ResolveInfo systemApp = getSingleSystemActivity(appList);
123 if (systemApp == null) {
124 // There is no logical choice for this meta-favorite, so rather than making
125 // a bad choice just add nothing.
126 Log.w(TAG, "No preference or single system activity found for "
127 + metaIntent.toString());
128 return -1;
129 }
130 resolved = systemApp;
131 }
132 final ActivityInfo info = resolved.activityInfo;
133 final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
134 if (intent == null) {
135 return -1;
136 }
137 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
138 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
139
140 return addShortcut(info.loadLabel(mPackageManager).toString(), intent,
141 Favorites.ITEM_TYPE_APPLICATION);
142 }
143
144 private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
145 ResolveInfo systemResolve = null;
146 final int N = appList.size();
147 for (int i = 0; i < N; ++i) {
148 try {
149 ApplicationInfo info = mPackageManager.getApplicationInfo(
150 appList.get(i).activityInfo.packageName, 0);
151 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
152 if (systemResolve != null) {
153 return null;
154 } else {
155 systemResolve = appList.get(i);
156 }
157 }
158 } catch (PackageManager.NameNotFoundException e) {
159 Log.w(TAG, "Unable to get info about resolve results", e);
160 return null;
161 }
162 }
163 return systemResolve;
164 }
165
166 private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
167 List<ResolveInfo> appList) {
168 // If the list contains the above resolved activity, then it can't be
169 // ResolverActivity itself.
170 for (int i = 0; i < appList.size(); ++i) {
171 ResolveInfo tmp = appList.get(i);
172 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
173 && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
174 return false;
175 }
176 }
177 return true;
178 }
179 }
180
181
182 /**
183 * Shortcut parser which allows any uri and not just web urls.
184 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700185 public class UriShortcutParser extends ShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700186
187 public UriShortcutParser(Resources iconRes) {
188 super(iconRes);
189 }
190
191 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800192 protected Intent parseIntent(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700193 String uri = null;
194 try {
195 uri = getAttributeValue(parser, ATTR_URI);
196 return Intent.parseUri(uri, 0);
197 } catch (URISyntaxException e) {
198 Log.w(TAG, "Shortcut has malformed uri: " + uri);
199 return null; // Oh well
200 }
201 }
202 }
203
204 /**
205 * Contains a list of <favorite> nodes, and accepts the first successfully parsed node.
206 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700207 public class ResolveParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700208
209 private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
210
211 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800212 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700213 IOException {
214 final int groupDepth = parser.getDepth();
215 int type;
Sunny Goyalefb7e842018-10-04 15:11:00 -0700216 int addedId = -1;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700217 while ((type = parser.next()) != XmlPullParser.END_TAG ||
218 parser.getDepth() > groupDepth) {
219 if (type != XmlPullParser.START_TAG || addedId > -1) {
220 continue;
221 }
222 final String fallback_item_name = parser.getName();
223 if (TAG_FAVORITE.equals(fallback_item_name)) {
224 addedId = mChildParser.parseAndAdd(parser);
225 } else {
226 Log.e(TAG, "Fallback groups can contain only favorites, found "
227 + fallback_item_name);
228 }
229 }
230 return addedId;
231 }
232 }
233
234 /**
235 * A parser which adds a folder whose contents come from partner apk.
236 */
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700237 @Thunk
238 class PartnerFolderParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700239
240 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800241 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700242 IOException {
243 // Folder contents come from an external XML resource
244 final Partner partner = Partner.get(mPackageManager);
245 if (partner != null) {
246 final Resources partnerRes = partner.getResources();
247 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
248 "xml", partner.getPackageName());
249 if (resId != 0) {
Sunny Goyal0d742312019-03-04 20:22:26 -0800250 final XmlPullParser partnerParser = partnerRes.getXml(resId);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700251 beginDocument(partnerParser, TAG_FOLDER);
252
253 FolderParser folderParser = new FolderParser(getFolderElementsMap(partnerRes));
254 return folderParser.parseAndAdd(partnerParser);
255 }
256 }
257 return -1;
258 }
259 }
260
261 /**
262 * An extension of FolderParser which allows adding items from a different xml.
263 */
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700264 @Thunk
265 class MyFolderParser extends FolderParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700266
267 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800268 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700269 IOException {
270 final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
271 if (resId != 0) {
272 parser = mSourceRes.getXml(resId);
273 beginDocument(parser, TAG_FOLDER);
274 }
275 return super.parseAndAdd(parser);
276 }
277 }
Sunny Goyal86df1382016-08-10 15:03:22 -0700278
279
280 /**
281 * AppWidget parser which enforces that the app is already installed when the layout is parsed.
282 */
283 protected class AppWidgetParser extends PendingWidgetParser {
284
285 @Override
Sunny Goyalefb7e842018-10-04 15:11:00 -0700286 protected int verifyAndInsert(ComponentName cn, Bundle extras) {
Sunny Goyal86df1382016-08-10 15:03:22 -0700287 try {
288 mPackageManager.getReceiverInfo(cn, 0);
289 } catch (Exception e) {
290 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700291 new String[]{cn.getPackageName()});
Sunny Goyal86df1382016-08-10 15:03:22 -0700292 cn = new ComponentName(packages[0], cn.getClassName());
293 try {
294 mPackageManager.getReceiverInfo(cn, 0);
295 } catch (Exception e1) {
296 Log.d(TAG, "Can't find widget provider: " + cn.getClassName());
297 return -1;
298 }
299 }
300
301 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
Sunny Goyalefb7e842018-10-04 15:11:00 -0700302 int insertedId = -1;
Sunny Goyal86df1382016-08-10 15:03:22 -0700303 try {
304 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
305
306 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
307 Log.e(TAG, "Unable to bind app widget id " + cn);
308 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
309 return -1;
310 }
311
312 mValues.put(Favorites.APPWIDGET_ID, appWidgetId);
313 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
314 mValues.put(Favorites._ID, mCallback.generateNewItemId());
315 insertedId = mCallback.insertAndCheck(mDb, mValues);
316 if (insertedId < 0) {
317 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
318 return insertedId;
319 }
320
321 // Send a broadcast to configure the widget
322 if (!extras.isEmpty()) {
323 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
324 intent.setComponent(cn);
325 intent.putExtras(extras);
326 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
327 mContext.sendBroadcast(intent);
328 }
329 } catch (RuntimeException ex) {
330 Log.e(TAG, "Problem allocating appWidgetId", ex);
331 }
332 return insertedId;
333 }
334 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700335}