blob: c69ae4dac885eb28723252d3ae987968a253418a [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;
Sunny Goyal3e58eea2022-11-14 14:30:07 -080022import com.android.launcher3.util.Partner;
Adam Cohen091440a2015-03-18 14:16:05 -070023import com.android.launcher3.util.Thunk;
Sihua Ma8bbfcb62022-11-08 16:46:07 -080024import com.android.launcher3.widget.LauncherWidgetHolder;
Samuel Fufaca37b8a2019-08-19 17:04:36 -070025
26import org.xmlpull.v1.XmlPullParser;
27import org.xmlpull.v1.XmlPullParserException;
28
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070029import java.io.IOException;
30import java.net.URISyntaxException;
My Name2f5a1b62022-04-01 11:11:57 +000031import java.util.Collections;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070032import java.util.List;
33
34/**
35 * Implements the layout parser with rules for internal layouts and partner layouts.
36 */
37public class DefaultLayoutParser extends AutoInstallsLayout {
38 private static final String TAG = "DefaultLayoutParser";
39
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080040 protected static final String TAG_RESOLVE = "resolve";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070041 private static final String TAG_FAVORITES = "favorites";
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080042 protected static final String TAG_FAVORITE = "favorite";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070043 private static final String TAG_APPWIDGET = "appwidget";
Sunny Goyala5c8a9e2016-07-08 08:32:44 -070044 protected static final String TAG_SHORTCUT = "shortcut";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070045 private static final String TAG_FOLDER = "folder";
46 private static final String TAG_PARTNER_FOLDER = "partner-folder";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070047
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080048 protected static final String ATTR_URI = "uri";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070049 private static final String ATTR_CONTAINER = "container";
50 private static final String ATTR_SCREEN = "screen";
51 private static final String ATTR_FOLDER_ITEMS = "folderItems";
My Name2f5a1b62022-04-01 11:11:57 +000052 private static final String ATTR_SHORTCUT_ID = "shortcutId";
53 private static final String ATTR_PACKAGE_NAME = "packageName";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070054
Sunny Goyal3e58eea2022-11-14 14:30:07 -080055 public static final String RES_PARTNER_FOLDER = "partner_folder";
56 public static final String RES_PARTNER_DEFAULT_LAYOUT = "partner_default_layout";
57
Sunny Goyal86df1382016-08-10 15:03:22 -070058 // TODO: Remove support for this broadcast, instead use widget options to send bind time options
59 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
60 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
61
Sihua Maaa2b8722022-10-25 15:17:58 -070062 public DefaultLayoutParser(Context context, LauncherWidgetHolder appWidgetHolder,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070063 LayoutParserCallback callback, Resources sourceRes, int layoutId) {
Sihua Maaa2b8722022-10-25 15:17:58 -070064 super(context, appWidgetHolder, callback, sourceRes, layoutId, TAG_FAVORITES);
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080065 }
66
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070067 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070068 protected ArrayMap<String, TagParser> getFolderElementsMap() {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070069 return getFolderElementsMap(mSourceRes);
70 }
71
Samuel Fufaca37b8a2019-08-19 17:04:36 -070072 @Thunk
73 ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
Rajeev Kumar26453a22017-06-09 16:02:25 -070074 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070075 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
76 parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
77 return parsers;
78 }
79
80 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070081 protected ArrayMap<String, TagParser> getLayoutElementsMap() {
82 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070083 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
84 parsers.put(TAG_APPWIDGET, new AppWidgetParser());
Samuel Fufaca37b8a2019-08-19 17:04:36 -070085 parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070086 parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
87 parsers.put(TAG_RESOLVE, new ResolveParser());
88 parsers.put(TAG_FOLDER, new MyFolderParser());
89 parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser());
90 return parsers;
91 }
92
93 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -080094 protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070095 out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
96 String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
97 if (strContainer != null) {
Sunny Goyalefb7e842018-10-04 15:11:00 -070098 out[0] = Integer.parseInt(strContainer);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070099 }
Sunny Goyalefb7e842018-10-04 15:11:00 -0700100 out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700101 }
102
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700103 /**
104 * AppShortcutParser which also supports adding URI based intents
105 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700106 public class AppShortcutWithUriParser extends AppShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700107
108 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800109 protected int invalidPackageOrClass(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700110 final String uri = getAttributeValue(parser, ATTR_URI);
111 if (TextUtils.isEmpty(uri)) {
112 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
113 return -1;
114 }
115
116 final Intent metaIntent;
117 try {
118 metaIntent = Intent.parseUri(uri, 0);
119 } catch (URISyntaxException e) {
120 Log.e(TAG, "Unable to add meta-favorite: " + uri, e);
121 return -1;
122 }
123
124 ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
125 PackageManager.MATCH_DEFAULT_ONLY);
126 final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
127 metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
128
129 // Verify that the result is an app and not just the resolver dialog asking which
130 // app to use.
131 if (wouldLaunchResolverActivity(resolved, appList)) {
132 // If only one of the results is a system app then choose that as the default.
133 final ResolveInfo systemApp = getSingleSystemActivity(appList);
134 if (systemApp == null) {
135 // There is no logical choice for this meta-favorite, so rather than making
136 // a bad choice just add nothing.
137 Log.w(TAG, "No preference or single system activity found for "
138 + metaIntent.toString());
139 return -1;
140 }
141 resolved = systemApp;
142 }
143 final ActivityInfo info = resolved.activityInfo;
144 final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
145 if (intent == null) {
146 return -1;
147 }
148 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
149 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
150
151 return addShortcut(info.loadLabel(mPackageManager).toString(), intent,
152 Favorites.ITEM_TYPE_APPLICATION);
153 }
154
155 private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
156 ResolveInfo systemResolve = null;
157 final int N = appList.size();
158 for (int i = 0; i < N; ++i) {
159 try {
160 ApplicationInfo info = mPackageManager.getApplicationInfo(
161 appList.get(i).activityInfo.packageName, 0);
162 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
163 if (systemResolve != null) {
164 return null;
165 } else {
166 systemResolve = appList.get(i);
167 }
168 }
169 } catch (PackageManager.NameNotFoundException e) {
170 Log.w(TAG, "Unable to get info about resolve results", e);
171 return null;
172 }
173 }
174 return systemResolve;
175 }
176
177 private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
178 List<ResolveInfo> appList) {
179 // If the list contains the above resolved activity, then it can't be
180 // ResolverActivity itself.
181 for (int i = 0; i < appList.size(); ++i) {
182 ResolveInfo tmp = appList.get(i);
183 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
184 && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
185 return false;
186 }
187 }
188 return true;
189 }
190 }
191
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700192 /**
193 * Shortcut parser which allows any uri and not just web urls.
194 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700195 public class UriShortcutParser extends ShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700196
197 public UriShortcutParser(Resources iconRes) {
198 super(iconRes);
199 }
200
201 @Override
My Name2f5a1b62022-04-01 11:11:57 +0000202 public int parseAndAdd(XmlPullParser parser) {
203 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
204 final String shortcutId = getAttributeValue(parser, ATTR_SHORTCUT_ID);
205 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(shortcutId)) {
206 return parseAndAddDeepShortcut(shortcutId, packageName);
207 }
208 return super.parseAndAdd(parser);
209 }
210
211 /**
212 * This method parses and adds a deep shortcut.
213 * @return item id if the shortcut is successfully added else -1
214 */
My Name9a6dbac2022-04-28 05:11:01 +0000215 private int parseAndAddDeepShortcut(String shortcutId, String packageName) {
My Name2f5a1b62022-04-01 11:11:57 +0000216 try {
217 LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
218 launcherApps.pinShortcuts(packageName, Collections.singletonList(shortcutId),
219 Process.myUserHandle());
220 Intent intent = ShortcutKey.makeIntent(shortcutId, packageName);
221 mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_RESTORED_ICON);
222 return addShortcut(null, intent, Favorites.ITEM_TYPE_DEEP_SHORTCUT);
223 } catch (Exception e) {
224 Log.e(TAG, "Unable to pin the shortcut for shortcut id = " + shortcutId
225 + " and package name = " + packageName);
226 }
227 return -1;
228 }
229
230 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800231 protected Intent parseIntent(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700232 String uri = null;
233 try {
234 uri = getAttributeValue(parser, ATTR_URI);
235 return Intent.parseUri(uri, 0);
236 } catch (URISyntaxException e) {
237 Log.w(TAG, "Shortcut has malformed uri: " + uri);
238 return null; // Oh well
239 }
240 }
241 }
242
243 /**
244 * Contains a list of <favorite> nodes, and accepts the first successfully parsed node.
245 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700246 public class ResolveParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700247
248 private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
249
250 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800251 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700252 IOException {
253 final int groupDepth = parser.getDepth();
254 int type;
Sunny Goyalefb7e842018-10-04 15:11:00 -0700255 int addedId = -1;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700256 while ((type = parser.next()) != XmlPullParser.END_TAG ||
257 parser.getDepth() > groupDepth) {
258 if (type != XmlPullParser.START_TAG || addedId > -1) {
259 continue;
260 }
261 final String fallback_item_name = parser.getName();
262 if (TAG_FAVORITE.equals(fallback_item_name)) {
263 addedId = mChildParser.parseAndAdd(parser);
264 } else {
265 Log.e(TAG, "Fallback groups can contain only favorites, found "
266 + fallback_item_name);
267 }
268 }
269 return addedId;
270 }
271 }
272
273 /**
274 * A parser which adds a folder whose contents come from partner apk.
275 */
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700276 @Thunk
277 class PartnerFolderParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700278
279 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800280 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700281 IOException {
282 // Folder contents come from an external XML resource
283 final Partner partner = Partner.get(mPackageManager);
284 if (partner != null) {
Sunny Goyal3e58eea2022-11-14 14:30:07 -0800285 final int resId = partner.getXmlResId(RES_PARTNER_FOLDER);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700286 if (resId != 0) {
Sunny Goyal3e58eea2022-11-14 14:30:07 -0800287 final Resources partnerRes = partner.getResources();
Sunny Goyal0d742312019-03-04 20:22:26 -0800288 final XmlPullParser partnerParser = partnerRes.getXml(resId);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700289 beginDocument(partnerParser, TAG_FOLDER);
290
291 FolderParser folderParser = new FolderParser(getFolderElementsMap(partnerRes));
292 return folderParser.parseAndAdd(partnerParser);
293 }
294 }
295 return -1;
296 }
297 }
298
299 /**
300 * An extension of FolderParser which allows adding items from a different xml.
301 */
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700302 @Thunk
303 class MyFolderParser extends FolderParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700304
305 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800306 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700307 IOException {
308 final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
309 if (resId != 0) {
310 parser = mSourceRes.getXml(resId);
311 beginDocument(parser, TAG_FOLDER);
312 }
313 return super.parseAndAdd(parser);
314 }
315 }
Sunny Goyal86df1382016-08-10 15:03:22 -0700316
317
318 /**
319 * AppWidget parser which enforces that the app is already installed when the layout is parsed.
320 */
321 protected class AppWidgetParser extends PendingWidgetParser {
322
323 @Override
Sunny Goyalefb7e842018-10-04 15:11:00 -0700324 protected int verifyAndInsert(ComponentName cn, Bundle extras) {
Sunny Goyal86df1382016-08-10 15:03:22 -0700325 try {
326 mPackageManager.getReceiverInfo(cn, 0);
327 } catch (Exception e) {
328 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700329 new String[]{cn.getPackageName()});
Sunny Goyal86df1382016-08-10 15:03:22 -0700330 cn = new ComponentName(packages[0], cn.getClassName());
331 try {
332 mPackageManager.getReceiverInfo(cn, 0);
333 } catch (Exception e1) {
334 Log.d(TAG, "Can't find widget provider: " + cn.getClassName());
335 return -1;
336 }
337 }
338
339 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
Sunny Goyalefb7e842018-10-04 15:11:00 -0700340 int insertedId = -1;
Sunny Goyal86df1382016-08-10 15:03:22 -0700341 try {
Sihua Maaa2b8722022-10-25 15:17:58 -0700342 int appWidgetId = mAppWidgetHolder.allocateAppWidgetId();
Sunny Goyal86df1382016-08-10 15:03:22 -0700343
344 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
345 Log.e(TAG, "Unable to bind app widget id " + cn);
Sihua Maaa2b8722022-10-25 15:17:58 -0700346 mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
Sunny Goyal86df1382016-08-10 15:03:22 -0700347 return -1;
348 }
349
350 mValues.put(Favorites.APPWIDGET_ID, appWidgetId);
351 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
352 mValues.put(Favorites._ID, mCallback.generateNewItemId());
353 insertedId = mCallback.insertAndCheck(mDb, mValues);
354 if (insertedId < 0) {
Sihua Maaa2b8722022-10-25 15:17:58 -0700355 mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
Sunny Goyal86df1382016-08-10 15:03:22 -0700356 return insertedId;
357 }
358
359 // Send a broadcast to configure the widget
360 if (!extras.isEmpty()) {
361 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
362 intent.setComponent(cn);
363 intent.putExtras(extras);
364 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
365 mContext.sendBroadcast(intent);
366 }
367 } catch (RuntimeException ex) {
368 Log.e(TAG, "Problem allocating appWidgetId", ex);
369 }
370 return insertedId;
371 }
372 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700373}