blob: d8558f0ca6a6efd9884372d02f99c47465aef1fb [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;
Samuel Fufaca37b8a2019-08-19 17:04:36 -070023
24import org.xmlpull.v1.XmlPullParser;
25import org.xmlpull.v1.XmlPullParserException;
26
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070027import java.io.IOException;
28import java.net.URISyntaxException;
My Name2f5a1b62022-04-01 11:11:57 +000029import java.util.Collections;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070030import java.util.List;
31
32/**
33 * Implements the layout parser with rules for internal layouts and partner layouts.
34 */
35public class DefaultLayoutParser extends AutoInstallsLayout {
36 private static final String TAG = "DefaultLayoutParser";
37
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080038 protected static final String TAG_RESOLVE = "resolve";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070039 private static final String TAG_FAVORITES = "favorites";
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080040 protected static final String TAG_FAVORITE = "favorite";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070041 private static final String TAG_APPWIDGET = "appwidget";
Sunny Goyala5c8a9e2016-07-08 08:32:44 -070042 protected static final String TAG_SHORTCUT = "shortcut";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070043 private static final String TAG_FOLDER = "folder";
44 private static final String TAG_PARTNER_FOLDER = "partner-folder";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070045
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080046 protected static final String ATTR_URI = "uri";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070047 private static final String ATTR_CONTAINER = "container";
48 private static final String ATTR_SCREEN = "screen";
49 private static final String ATTR_FOLDER_ITEMS = "folderItems";
My Name2f5a1b62022-04-01 11:11:57 +000050 private static final String ATTR_SHORTCUT_ID = "shortcutId";
51 private static final String ATTR_PACKAGE_NAME = "packageName";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070052
Sunny Goyal86df1382016-08-10 15:03:22 -070053 // TODO: Remove support for this broadcast, instead use widget options to send bind time options
54 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
55 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
56
Sihua Maaa2b8722022-10-25 15:17:58 -070057 public DefaultLayoutParser(Context context, LauncherWidgetHolder appWidgetHolder,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070058 LayoutParserCallback callback, Resources sourceRes, int layoutId) {
Sihua Maaa2b8722022-10-25 15:17:58 -070059 super(context, appWidgetHolder, callback, sourceRes, layoutId, TAG_FAVORITES);
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080060 }
61
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070062 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070063 protected ArrayMap<String, TagParser> getFolderElementsMap() {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070064 return getFolderElementsMap(mSourceRes);
65 }
66
Samuel Fufaca37b8a2019-08-19 17:04:36 -070067 @Thunk
68 ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
Rajeev Kumar26453a22017-06-09 16:02:25 -070069 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070070 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
71 parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
72 return parsers;
73 }
74
75 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070076 protected ArrayMap<String, TagParser> getLayoutElementsMap() {
77 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070078 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
79 parsers.put(TAG_APPWIDGET, new AppWidgetParser());
Samuel Fufaca37b8a2019-08-19 17:04:36 -070080 parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070081 parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
82 parsers.put(TAG_RESOLVE, new ResolveParser());
83 parsers.put(TAG_FOLDER, new MyFolderParser());
84 parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser());
85 return parsers;
86 }
87
88 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -080089 protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070090 out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
91 String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
92 if (strContainer != null) {
Sunny Goyalefb7e842018-10-04 15:11:00 -070093 out[0] = Integer.parseInt(strContainer);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070094 }
Sunny Goyalefb7e842018-10-04 15:11:00 -070095 out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070096 }
97
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070098 /**
99 * AppShortcutParser which also supports adding URI based intents
100 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700101 public class AppShortcutWithUriParser extends AppShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700102
103 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800104 protected int invalidPackageOrClass(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700105 final String uri = getAttributeValue(parser, ATTR_URI);
106 if (TextUtils.isEmpty(uri)) {
107 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
108 return -1;
109 }
110
111 final Intent metaIntent;
112 try {
113 metaIntent = Intent.parseUri(uri, 0);
114 } catch (URISyntaxException e) {
115 Log.e(TAG, "Unable to add meta-favorite: " + uri, e);
116 return -1;
117 }
118
119 ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
120 PackageManager.MATCH_DEFAULT_ONLY);
121 final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
122 metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
123
124 // Verify that the result is an app and not just the resolver dialog asking which
125 // app to use.
126 if (wouldLaunchResolverActivity(resolved, appList)) {
127 // If only one of the results is a system app then choose that as the default.
128 final ResolveInfo systemApp = getSingleSystemActivity(appList);
129 if (systemApp == null) {
130 // There is no logical choice for this meta-favorite, so rather than making
131 // a bad choice just add nothing.
132 Log.w(TAG, "No preference or single system activity found for "
133 + metaIntent.toString());
134 return -1;
135 }
136 resolved = systemApp;
137 }
138 final ActivityInfo info = resolved.activityInfo;
139 final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
140 if (intent == null) {
141 return -1;
142 }
143 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
144 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
145
146 return addShortcut(info.loadLabel(mPackageManager).toString(), intent,
147 Favorites.ITEM_TYPE_APPLICATION);
148 }
149
150 private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
151 ResolveInfo systemResolve = null;
152 final int N = appList.size();
153 for (int i = 0; i < N; ++i) {
154 try {
155 ApplicationInfo info = mPackageManager.getApplicationInfo(
156 appList.get(i).activityInfo.packageName, 0);
157 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
158 if (systemResolve != null) {
159 return null;
160 } else {
161 systemResolve = appList.get(i);
162 }
163 }
164 } catch (PackageManager.NameNotFoundException e) {
165 Log.w(TAG, "Unable to get info about resolve results", e);
166 return null;
167 }
168 }
169 return systemResolve;
170 }
171
172 private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
173 List<ResolveInfo> appList) {
174 // If the list contains the above resolved activity, then it can't be
175 // ResolverActivity itself.
176 for (int i = 0; i < appList.size(); ++i) {
177 ResolveInfo tmp = appList.get(i);
178 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
179 && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
180 return false;
181 }
182 }
183 return true;
184 }
185 }
186
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700187 /**
188 * Shortcut parser which allows any uri and not just web urls.
189 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700190 public class UriShortcutParser extends ShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700191
192 public UriShortcutParser(Resources iconRes) {
193 super(iconRes);
194 }
195
196 @Override
My Name2f5a1b62022-04-01 11:11:57 +0000197 public int parseAndAdd(XmlPullParser parser) {
198 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
199 final String shortcutId = getAttributeValue(parser, ATTR_SHORTCUT_ID);
200 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(shortcutId)) {
201 return parseAndAddDeepShortcut(shortcutId, packageName);
202 }
203 return super.parseAndAdd(parser);
204 }
205
206 /**
207 * This method parses and adds a deep shortcut.
208 * @return item id if the shortcut is successfully added else -1
209 */
My Name9a6dbac2022-04-28 05:11:01 +0000210 private int parseAndAddDeepShortcut(String shortcutId, String packageName) {
My Name2f5a1b62022-04-01 11:11:57 +0000211 try {
212 LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
213 launcherApps.pinShortcuts(packageName, Collections.singletonList(shortcutId),
214 Process.myUserHandle());
215 Intent intent = ShortcutKey.makeIntent(shortcutId, packageName);
216 mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_RESTORED_ICON);
217 return addShortcut(null, intent, Favorites.ITEM_TYPE_DEEP_SHORTCUT);
218 } catch (Exception e) {
219 Log.e(TAG, "Unable to pin the shortcut for shortcut id = " + shortcutId
220 + " and package name = " + packageName);
221 }
222 return -1;
223 }
224
225 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800226 protected Intent parseIntent(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700227 String uri = null;
228 try {
229 uri = getAttributeValue(parser, ATTR_URI);
230 return Intent.parseUri(uri, 0);
231 } catch (URISyntaxException e) {
232 Log.w(TAG, "Shortcut has malformed uri: " + uri);
233 return null; // Oh well
234 }
235 }
236 }
237
238 /**
239 * Contains a list of <favorite> nodes, and accepts the first successfully parsed node.
240 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700241 public class ResolveParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700242
243 private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
244
245 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800246 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700247 IOException {
248 final int groupDepth = parser.getDepth();
249 int type;
Sunny Goyalefb7e842018-10-04 15:11:00 -0700250 int addedId = -1;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700251 while ((type = parser.next()) != XmlPullParser.END_TAG ||
252 parser.getDepth() > groupDepth) {
253 if (type != XmlPullParser.START_TAG || addedId > -1) {
254 continue;
255 }
256 final String fallback_item_name = parser.getName();
257 if (TAG_FAVORITE.equals(fallback_item_name)) {
258 addedId = mChildParser.parseAndAdd(parser);
259 } else {
260 Log.e(TAG, "Fallback groups can contain only favorites, found "
261 + fallback_item_name);
262 }
263 }
264 return addedId;
265 }
266 }
267
268 /**
269 * A parser which adds a folder whose contents come from partner apk.
270 */
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700271 @Thunk
272 class PartnerFolderParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700273
274 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800275 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700276 IOException {
277 // Folder contents come from an external XML resource
278 final Partner partner = Partner.get(mPackageManager);
279 if (partner != null) {
280 final Resources partnerRes = partner.getResources();
281 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
282 "xml", partner.getPackageName());
283 if (resId != 0) {
Sunny Goyal0d742312019-03-04 20:22:26 -0800284 final XmlPullParser partnerParser = partnerRes.getXml(resId);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700285 beginDocument(partnerParser, TAG_FOLDER);
286
287 FolderParser folderParser = new FolderParser(getFolderElementsMap(partnerRes));
288 return folderParser.parseAndAdd(partnerParser);
289 }
290 }
291 return -1;
292 }
293 }
294
295 /**
296 * An extension of FolderParser which allows adding items from a different xml.
297 */
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700298 @Thunk
299 class MyFolderParser extends FolderParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700300
301 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800302 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700303 IOException {
304 final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
305 if (resId != 0) {
306 parser = mSourceRes.getXml(resId);
307 beginDocument(parser, TAG_FOLDER);
308 }
309 return super.parseAndAdd(parser);
310 }
311 }
Sunny Goyal86df1382016-08-10 15:03:22 -0700312
313
314 /**
315 * AppWidget parser which enforces that the app is already installed when the layout is parsed.
316 */
317 protected class AppWidgetParser extends PendingWidgetParser {
318
319 @Override
Sunny Goyalefb7e842018-10-04 15:11:00 -0700320 protected int verifyAndInsert(ComponentName cn, Bundle extras) {
Sunny Goyal86df1382016-08-10 15:03:22 -0700321 try {
322 mPackageManager.getReceiverInfo(cn, 0);
323 } catch (Exception e) {
324 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700325 new String[]{cn.getPackageName()});
Sunny Goyal86df1382016-08-10 15:03:22 -0700326 cn = new ComponentName(packages[0], cn.getClassName());
327 try {
328 mPackageManager.getReceiverInfo(cn, 0);
329 } catch (Exception e1) {
330 Log.d(TAG, "Can't find widget provider: " + cn.getClassName());
331 return -1;
332 }
333 }
334
335 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
Sunny Goyalefb7e842018-10-04 15:11:00 -0700336 int insertedId = -1;
Sunny Goyal86df1382016-08-10 15:03:22 -0700337 try {
Sihua Maaa2b8722022-10-25 15:17:58 -0700338 int appWidgetId = mAppWidgetHolder.allocateAppWidgetId();
Sunny Goyal86df1382016-08-10 15:03:22 -0700339
340 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
341 Log.e(TAG, "Unable to bind app widget id " + cn);
Sihua Maaa2b8722022-10-25 15:17:58 -0700342 mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
Sunny Goyal86df1382016-08-10 15:03:22 -0700343 return -1;
344 }
345
346 mValues.put(Favorites.APPWIDGET_ID, appWidgetId);
347 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
348 mValues.put(Favorites._ID, mCallback.generateNewItemId());
349 insertedId = mCallback.insertAndCheck(mDb, mValues);
350 if (insertedId < 0) {
Sihua Maaa2b8722022-10-25 15:17:58 -0700351 mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
Sunny Goyal86df1382016-08-10 15:03:22 -0700352 return insertedId;
353 }
354
355 // Send a broadcast to configure the widget
356 if (!extras.isEmpty()) {
357 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
358 intent.setComponent(cn);
359 intent.putExtras(extras);
360 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
361 mContext.sendBroadcast(intent);
362 }
363 } catch (RuntimeException ex) {
364 Log.e(TAG, "Problem allocating appWidgetId", ex);
365 }
366 return insertedId;
367 }
368 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700369}