blob: af13beac1d89e3e0ebb844b31d1997ba4dff473d [file] [log] [blame]
Sunny Goyal3a5a9d12014-10-01 15:33:41 -07001package com.android.launcher3;
2
Sunny Goyal86df1382016-08-10 15:03:22 -07003import android.appwidget.AppWidgetManager;
4import android.content.ComponentName;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -07005import android.content.Context;
6import android.content.Intent;
7import android.content.pm.ActivityInfo;
8import android.content.pm.ApplicationInfo;
My Name2f5a1b62022-04-01 11:11:57 +00009import android.content.pm.LauncherApps;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070010import 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;
My Name2f5a1b62022-04-01 11:11:57 +000014import android.os.Process;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070015import android.text.TextUtils;
Rajeev Kumar26453a22017-06-09 16:02:25 -070016import android.util.ArrayMap;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070017import android.util.Log;
Samuel Fufaca37b8a2019-08-19 17:04:36 -070018
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070019import com.android.launcher3.LauncherSettings.Favorites;
My Name2f5a1b62022-04-01 11:11:57 +000020import com.android.launcher3.model.data.WorkspaceItemInfo;
21import com.android.launcher3.shortcuts.ShortcutKey;
Adam Cohen091440a2015-03-18 14:16:05 -070022import com.android.launcher3.util.Thunk;
Sihua Ma8bbfcb62022-11-08 16:46:07 -080023import com.android.launcher3.widget.LauncherWidgetHolder;
Samuel Fufaca37b8a2019-08-19 17:04:36 -070024
25import org.xmlpull.v1.XmlPullParser;
26import org.xmlpull.v1.XmlPullParserException;
27
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070028import java.io.IOException;
29import java.net.URISyntaxException;
My Name2f5a1b62022-04-01 11:11:57 +000030import java.util.Collections;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070031import java.util.List;
32
33/**
34 * Implements the layout parser with rules for internal layouts and partner layouts.
35 */
36public class DefaultLayoutParser extends AutoInstallsLayout {
37 private static final String TAG = "DefaultLayoutParser";
38
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080039 protected static final String TAG_RESOLVE = "resolve";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070040 private static final String TAG_FAVORITES = "favorites";
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080041 protected static final String TAG_FAVORITE = "favorite";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070042 private static final String TAG_APPWIDGET = "appwidget";
Sunny Goyala5c8a9e2016-07-08 08:32:44 -070043 protected static final String TAG_SHORTCUT = "shortcut";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070044 private static final String TAG_FOLDER = "folder";
45 private static final String TAG_PARTNER_FOLDER = "partner-folder";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070046
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080047 protected static final String ATTR_URI = "uri";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070048 private static final String ATTR_CONTAINER = "container";
49 private static final String ATTR_SCREEN = "screen";
50 private static final String ATTR_FOLDER_ITEMS = "folderItems";
My Name2f5a1b62022-04-01 11:11:57 +000051 private static final String ATTR_SHORTCUT_ID = "shortcutId";
52 private static final String ATTR_PACKAGE_NAME = "packageName";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070053
Sunny Goyal86df1382016-08-10 15:03:22 -070054 // TODO: Remove support for this broadcast, instead use widget options to send bind time options
55 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
56 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
57
Sihua Maaa2b8722022-10-25 15:17:58 -070058 public DefaultLayoutParser(Context context, LauncherWidgetHolder appWidgetHolder,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070059 LayoutParserCallback callback, Resources sourceRes, int layoutId) {
Sihua Maaa2b8722022-10-25 15:17:58 -070060 super(context, appWidgetHolder, callback, sourceRes, layoutId, TAG_FAVORITES);
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080061 }
62
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070063 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070064 protected ArrayMap<String, TagParser> getFolderElementsMap() {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070065 return getFolderElementsMap(mSourceRes);
66 }
67
Samuel Fufaca37b8a2019-08-19 17:04:36 -070068 @Thunk
69 ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
Rajeev Kumar26453a22017-06-09 16:02:25 -070070 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070071 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
72 parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
73 return parsers;
74 }
75
76 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070077 protected ArrayMap<String, TagParser> getLayoutElementsMap() {
78 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070079 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
80 parsers.put(TAG_APPWIDGET, new AppWidgetParser());
Samuel Fufaca37b8a2019-08-19 17:04:36 -070081 parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070082 parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
83 parsers.put(TAG_RESOLVE, new ResolveParser());
84 parsers.put(TAG_FOLDER, new MyFolderParser());
85 parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser());
86 return parsers;
87 }
88
89 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -080090 protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070091 out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
92 String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
93 if (strContainer != null) {
Sunny Goyalefb7e842018-10-04 15:11:00 -070094 out[0] = Integer.parseInt(strContainer);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070095 }
Sunny Goyalefb7e842018-10-04 15:11:00 -070096 out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070097 }
98
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070099 /**
100 * AppShortcutParser which also supports adding URI based intents
101 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700102 public class AppShortcutWithUriParser extends AppShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700103
104 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800105 protected int invalidPackageOrClass(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700106 final String uri = getAttributeValue(parser, ATTR_URI);
107 if (TextUtils.isEmpty(uri)) {
108 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
109 return -1;
110 }
111
112 final Intent metaIntent;
113 try {
114 metaIntent = Intent.parseUri(uri, 0);
115 } catch (URISyntaxException e) {
116 Log.e(TAG, "Unable to add meta-favorite: " + uri, e);
117 return -1;
118 }
119
120 ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
121 PackageManager.MATCH_DEFAULT_ONLY);
122 final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
123 metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
124
125 // Verify that the result is an app and not just the resolver dialog asking which
126 // app to use.
127 if (wouldLaunchResolverActivity(resolved, appList)) {
128 // If only one of the results is a system app then choose that as the default.
129 final ResolveInfo systemApp = getSingleSystemActivity(appList);
130 if (systemApp == null) {
131 // There is no logical choice for this meta-favorite, so rather than making
132 // a bad choice just add nothing.
133 Log.w(TAG, "No preference or single system activity found for "
134 + metaIntent.toString());
135 return -1;
136 }
137 resolved = systemApp;
138 }
139 final ActivityInfo info = resolved.activityInfo;
140 final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
141 if (intent == null) {
142 return -1;
143 }
144 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
145 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
146
147 return addShortcut(info.loadLabel(mPackageManager).toString(), intent,
148 Favorites.ITEM_TYPE_APPLICATION);
149 }
150
151 private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
152 ResolveInfo systemResolve = null;
153 final int N = appList.size();
154 for (int i = 0; i < N; ++i) {
155 try {
156 ApplicationInfo info = mPackageManager.getApplicationInfo(
157 appList.get(i).activityInfo.packageName, 0);
158 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
159 if (systemResolve != null) {
160 return null;
161 } else {
162 systemResolve = appList.get(i);
163 }
164 }
165 } catch (PackageManager.NameNotFoundException e) {
166 Log.w(TAG, "Unable to get info about resolve results", e);
167 return null;
168 }
169 }
170 return systemResolve;
171 }
172
173 private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
174 List<ResolveInfo> appList) {
175 // If the list contains the above resolved activity, then it can't be
176 // ResolverActivity itself.
177 for (int i = 0; i < appList.size(); ++i) {
178 ResolveInfo tmp = appList.get(i);
179 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
180 && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
181 return false;
182 }
183 }
184 return true;
185 }
186 }
187
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700188 /**
189 * Shortcut parser which allows any uri and not just web urls.
190 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700191 public class UriShortcutParser extends ShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700192
193 public UriShortcutParser(Resources iconRes) {
194 super(iconRes);
195 }
196
197 @Override
My Name2f5a1b62022-04-01 11:11:57 +0000198 public int parseAndAdd(XmlPullParser parser) {
199 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
200 final String shortcutId = getAttributeValue(parser, ATTR_SHORTCUT_ID);
201 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(shortcutId)) {
202 return parseAndAddDeepShortcut(shortcutId, packageName);
203 }
204 return super.parseAndAdd(parser);
205 }
206
207 /**
208 * This method parses and adds a deep shortcut.
209 * @return item id if the shortcut is successfully added else -1
210 */
My Name9a6dbac2022-04-28 05:11:01 +0000211 private int parseAndAddDeepShortcut(String shortcutId, String packageName) {
My Name2f5a1b62022-04-01 11:11:57 +0000212 try {
213 LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
214 launcherApps.pinShortcuts(packageName, Collections.singletonList(shortcutId),
215 Process.myUserHandle());
216 Intent intent = ShortcutKey.makeIntent(shortcutId, packageName);
217 mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_RESTORED_ICON);
218 return addShortcut(null, intent, Favorites.ITEM_TYPE_DEEP_SHORTCUT);
219 } catch (Exception e) {
220 Log.e(TAG, "Unable to pin the shortcut for shortcut id = " + shortcutId
221 + " and package name = " + packageName);
222 }
223 return -1;
224 }
225
226 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800227 protected Intent parseIntent(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700228 String uri = null;
229 try {
230 uri = getAttributeValue(parser, ATTR_URI);
231 return Intent.parseUri(uri, 0);
232 } catch (URISyntaxException e) {
233 Log.w(TAG, "Shortcut has malformed uri: " + uri);
234 return null; // Oh well
235 }
236 }
237 }
238
239 /**
240 * Contains a list of <favorite> nodes, and accepts the first successfully parsed node.
241 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700242 public class ResolveParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700243
244 private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
245
246 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800247 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700248 IOException {
249 final int groupDepth = parser.getDepth();
250 int type;
Sunny Goyalefb7e842018-10-04 15:11:00 -0700251 int addedId = -1;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700252 while ((type = parser.next()) != XmlPullParser.END_TAG ||
253 parser.getDepth() > groupDepth) {
254 if (type != XmlPullParser.START_TAG || addedId > -1) {
255 continue;
256 }
257 final String fallback_item_name = parser.getName();
258 if (TAG_FAVORITE.equals(fallback_item_name)) {
259 addedId = mChildParser.parseAndAdd(parser);
260 } else {
261 Log.e(TAG, "Fallback groups can contain only favorites, found "
262 + fallback_item_name);
263 }
264 }
265 return addedId;
266 }
267 }
268
269 /**
270 * A parser which adds a folder whose contents come from partner apk.
271 */
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700272 @Thunk
273 class PartnerFolderParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700274
275 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800276 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700277 IOException {
278 // Folder contents come from an external XML resource
279 final Partner partner = Partner.get(mPackageManager);
280 if (partner != null) {
281 final Resources partnerRes = partner.getResources();
282 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
283 "xml", partner.getPackageName());
284 if (resId != 0) {
Sunny Goyal0d742312019-03-04 20:22:26 -0800285 final XmlPullParser partnerParser = partnerRes.getXml(resId);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700286 beginDocument(partnerParser, TAG_FOLDER);
287
288 FolderParser folderParser = new FolderParser(getFolderElementsMap(partnerRes));
289 return folderParser.parseAndAdd(partnerParser);
290 }
291 }
292 return -1;
293 }
294 }
295
296 /**
297 * An extension of FolderParser which allows adding items from a different xml.
298 */
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700299 @Thunk
300 class MyFolderParser extends FolderParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700301
302 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800303 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700304 IOException {
305 final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
306 if (resId != 0) {
307 parser = mSourceRes.getXml(resId);
308 beginDocument(parser, TAG_FOLDER);
309 }
310 return super.parseAndAdd(parser);
311 }
312 }
Sunny Goyal86df1382016-08-10 15:03:22 -0700313
314
315 /**
316 * AppWidget parser which enforces that the app is already installed when the layout is parsed.
317 */
318 protected class AppWidgetParser extends PendingWidgetParser {
319
320 @Override
Sunny Goyalefb7e842018-10-04 15:11:00 -0700321 protected int verifyAndInsert(ComponentName cn, Bundle extras) {
Sunny Goyal86df1382016-08-10 15:03:22 -0700322 try {
323 mPackageManager.getReceiverInfo(cn, 0);
324 } catch (Exception e) {
325 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700326 new String[]{cn.getPackageName()});
Sunny Goyal86df1382016-08-10 15:03:22 -0700327 cn = new ComponentName(packages[0], cn.getClassName());
328 try {
329 mPackageManager.getReceiverInfo(cn, 0);
330 } catch (Exception e1) {
331 Log.d(TAG, "Can't find widget provider: " + cn.getClassName());
332 return -1;
333 }
334 }
335
336 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
Sunny Goyalefb7e842018-10-04 15:11:00 -0700337 int insertedId = -1;
Sunny Goyal86df1382016-08-10 15:03:22 -0700338 try {
Sihua Maaa2b8722022-10-25 15:17:58 -0700339 int appWidgetId = mAppWidgetHolder.allocateAppWidgetId();
Sunny Goyal86df1382016-08-10 15:03:22 -0700340
341 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
342 Log.e(TAG, "Unable to bind app widget id " + cn);
Sihua Maaa2b8722022-10-25 15:17:58 -0700343 mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
Sunny Goyal86df1382016-08-10 15:03:22 -0700344 return -1;
345 }
346
347 mValues.put(Favorites.APPWIDGET_ID, appWidgetId);
348 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
349 mValues.put(Favorites._ID, mCallback.generateNewItemId());
350 insertedId = mCallback.insertAndCheck(mDb, mValues);
351 if (insertedId < 0) {
Sihua Maaa2b8722022-10-25 15:17:58 -0700352 mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
Sunny Goyal86df1382016-08-10 15:03:22 -0700353 return insertedId;
354 }
355
356 // Send a broadcast to configure the widget
357 if (!extras.isEmpty()) {
358 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
359 intent.setComponent(cn);
360 intent.putExtras(extras);
361 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
362 mContext.sendBroadcast(intent);
363 }
364 } catch (RuntimeException ex) {
365 Log.e(TAG, "Problem allocating appWidgetId", ex);
366 }
367 return insertedId;
368 }
369 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700370}