blob: 55ede6cecbfd36144948750f6b9d5fc6f4d9f77a [file] [log] [blame]
Sunny Goyal0fe505b2014-08-06 09:55:36 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.launcher3;
18
Sunny Goyal0fe505b2014-08-06 09:55:36 -070019import android.content.ComponentName;
20import android.content.ContentValues;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.ActivityInfo;
24import android.content.pm.PackageManager;
25import android.content.res.Resources;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070026import android.database.sqlite.SQLiteDatabase;
27import android.graphics.drawable.Drawable;
28import android.net.Uri;
29import android.os.Bundle;
30import android.text.TextUtils;
Rajeev Kumar26453a22017-06-09 16:02:25 -070031import android.util.ArrayMap;
Sunny Goyal0d742312019-03-04 20:22:26 -080032import android.util.AttributeSet;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070033import android.util.Log;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070034import android.util.Patterns;
Sunny Goyal0d742312019-03-04 20:22:26 -080035import android.util.Xml;
Sunny Goyal36b54222018-07-10 13:50:50 -070036
Samuel Fufaca37b8a2019-08-19 17:04:36 -070037import androidx.annotation.Nullable;
38
Sunny Goyal0fe505b2014-08-06 09:55:36 -070039import com.android.launcher3.LauncherProvider.SqlArguments;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070040import com.android.launcher3.LauncherSettings.Favorites;
Sunny Goyale62d2bb2018-11-06 10:28:37 -080041import com.android.launcher3.icons.GraphicsUtils;
Hyunyoung Song48cb7bc2018-09-25 17:03:34 -070042import com.android.launcher3.icons.LauncherIcons;
Sunny Goyale396abf2020-04-06 15:11:17 -070043import com.android.launcher3.model.data.LauncherAppWidgetInfo;
44import com.android.launcher3.model.data.WorkspaceItemInfo;
Samuel Fufaca37b8a2019-08-19 17:04:36 -070045import com.android.launcher3.qsb.QsbContainerView;
Sunny Goyalefb7e842018-10-04 15:11:00 -070046import com.android.launcher3.util.IntArray;
Sunny Goyal3e58eea2022-11-14 14:30:07 -080047import com.android.launcher3.util.Partner;
Adam Cohen091440a2015-03-18 14:16:05 -070048import com.android.launcher3.util.Thunk;
Sihua Ma8bbfcb62022-11-08 16:46:07 -080049import com.android.launcher3.widget.LauncherWidgetHolder;
Sunny Goyal36b54222018-07-10 13:50:50 -070050
51import org.xmlpull.v1.XmlPullParser;
52import org.xmlpull.v1.XmlPullParserException;
53
Sunny Goyal0fe505b2014-08-06 09:55:36 -070054import java.io.IOException;
Sunny Goyalb2d46ce2015-03-26 11:32:11 -070055import java.util.Locale;
Sunny Goyalc0f03d92019-03-22 14:13:36 -070056import java.util.function.Supplier;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070057
58/**
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070059 * Layout parsing code for auto installs layout
Sunny Goyal0fe505b2014-08-06 09:55:36 -070060 */
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070061public class AutoInstallsLayout {
Sunny Goyal0fe505b2014-08-06 09:55:36 -070062 private static final String TAG = "AutoInstalls";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070063 private static final boolean LOGD = false;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070064
65 /** Marker action used to discover a package which defines launcher customization */
66 static final String ACTION_LAUNCHER_CUSTOMIZATION =
Sunny Goyal2233c882014-09-18 14:36:48 -070067 "android.autoinstalls.config.action.PLAY_AUTO_INSTALL";
Sunny Goyal0fe505b2014-08-06 09:55:36 -070068
Sunny Goyalb2d46ce2015-03-26 11:32:11 -070069 /**
70 * Layout resource which also includes grid size and hotseat count, e.g., default_layout_6x6_h5
71 */
72 private static final String FORMATTED_LAYOUT_RES_WITH_HOSTEAT = "default_layout_%dx%d_h%s";
73 private static final String FORMATTED_LAYOUT_RES = "default_layout_%dx%d";
Sunny Goyal0fe505b2014-08-06 09:55:36 -070074 private static final String LAYOUT_RES = "default_layout";
75
Sihua Maaa2b8722022-10-25 15:17:58 -070076 static AutoInstallsLayout get(Context context, LauncherWidgetHolder appWidgetHolder,
Sunny Goyal0fe505b2014-08-06 09:55:36 -070077 LayoutParserCallback callback) {
Sunny Goyal3e58eea2022-11-14 14:30:07 -080078 Partner partner = Partner.get(context.getPackageManager(), ACTION_LAUNCHER_CUSTOMIZATION);
79 if (partner == null) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -070080 return null;
81 }
Sunny Goyal87f784c2017-01-11 10:48:34 -080082 InvariantDeviceProfile grid = LauncherAppState.getIDP(context);
Sunny Goyalb2d46ce2015-03-26 11:32:11 -070083
84 // Try with grid size and hotseat count
85 String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
Tony Wickhamb87f3cd2021-04-07 15:02:37 -070086 grid.numColumns, grid.numRows, grid.numDatabaseHotseatIcons);
Sunny Goyal3e58eea2022-11-14 14:30:07 -080087 int layoutId = partner.getXmlResId(layoutName);
Sunny Goyalb2d46ce2015-03-26 11:32:11 -070088
89 // Try with only grid size
90 if (layoutId == 0) {
91 Log.d(TAG, "Formatted layout: " + layoutName
92 + " not found. Trying layout without hosteat");
93 layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,
Samuel Fufaca37b8a2019-08-19 17:04:36 -070094 grid.numColumns, grid.numRows);
Sunny Goyal3e58eea2022-11-14 14:30:07 -080095 layoutId = partner.getXmlResId(layoutName);
Sunny Goyalb2d46ce2015-03-26 11:32:11 -070096 }
97
98 // Try the default layout
99 if (layoutId == 0) {
100 Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying the default layout");
Sunny Goyal3e58eea2022-11-14 14:30:07 -0800101 layoutId = partner.getXmlResId(LAYOUT_RES);
Sunny Goyalb2d46ce2015-03-26 11:32:11 -0700102 }
103
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700104 if (layoutId == 0) {
Sunny Goyal3e58eea2022-11-14 14:30:07 -0800105 Log.e(TAG, "Layout definition not found in package: " + partner.getPackageName());
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700106 return null;
107 }
Sunny Goyal3e58eea2022-11-14 14:30:07 -0800108 return new AutoInstallsLayout(context, appWidgetHolder, callback, partner.getResources(),
109 layoutId, TAG_WORKSPACE);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700110 }
111
112 // Object Tags
Sunny Goyalb564efb2015-01-23 13:45:20 -0800113 private static final String TAG_INCLUDE = "include";
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700114 public static final String TAG_WORKSPACE = "workspace";
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700115 private static final String TAG_APP_ICON = "appicon";
116 private static final String TAG_AUTO_INSTALL = "autoinstall";
117 private static final String TAG_FOLDER = "folder";
118 private static final String TAG_APPWIDGET = "appwidget";
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700119 protected static final String TAG_SEARCH_WIDGET = "searchwidget";
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700120 private static final String TAG_SHORTCUT = "shortcut";
121 private static final String TAG_EXTRA = "extra";
122
123 private static final String ATTR_CONTAINER = "container";
124 private static final String ATTR_RANK = "rank";
125
126 private static final String ATTR_PACKAGE_NAME = "packageName";
127 private static final String ATTR_CLASS_NAME = "className";
128 private static final String ATTR_TITLE = "title";
Sunny Goyal55e2b162020-06-09 15:44:48 -0700129 private static final String ATTR_TITLE_TEXT = "titleText";
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700130 private static final String ATTR_SCREEN = "screen";
Sunny Goyal96a09632015-12-16 11:32:54 -0800131
132 // x and y can be specified as negative integers, in which case -1 represents the
133 // last row / column, -2 represents the second last, and so on.
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700134 private static final String ATTR_X = "x";
135 private static final String ATTR_Y = "y";
Sunny Goyal96a09632015-12-16 11:32:54 -0800136
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700137 private static final String ATTR_SPAN_X = "spanX";
138 private static final String ATTR_SPAN_Y = "spanY";
139 private static final String ATTR_ICON = "icon";
140 private static final String ATTR_URL = "url";
141
Sunny Goyalb564efb2015-01-23 13:45:20 -0800142 // Attrs for "Include"
143 private static final String ATTR_WORKSPACE = "workspace";
144
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700145 // Style attrs -- "Extra"
146 private static final String ATTR_KEY = "key";
147 private static final String ATTR_VALUE = "value";
148
149 private static final String HOTSEAT_CONTAINER_NAME =
150 Favorites.containerToString(Favorites.CONTAINER_HOTSEAT);
151
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700152 @Thunk
153 final Context mContext;
154 @Thunk
Sihua Maaa2b8722022-10-25 15:17:58 -0700155 final LauncherWidgetHolder mAppWidgetHolder;
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800156 protected final LayoutParserCallback mCallback;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700157
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700158 protected final PackageManager mPackageManager;
159 protected final Resources mSourceRes;
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700160 protected final Supplier<XmlPullParser> mInitialLayoutSupplier;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700161
Sunny Goyalbb011da2016-06-15 15:42:29 -0700162 private final InvariantDeviceProfile mIdp;
Sunny Goyal96a09632015-12-16 11:32:54 -0800163 private final int mRowCount;
164 private final int mColumnCount;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700165
Sunny Goyalefb7e842018-10-04 15:11:00 -0700166 private final int[] mTemp = new int[2];
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700167 @Thunk
168 final ContentValues mValues;
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800169 protected final String mRootTag;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700170
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700171 protected SQLiteDatabase mDb;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700172
Sihua Maaa2b8722022-10-25 15:17:58 -0700173 public AutoInstallsLayout(Context context, LauncherWidgetHolder appWidgetHolder,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700174 LayoutParserCallback callback, Resources res,
175 int layoutId, String rootTag) {
Sihua Maaa2b8722022-10-25 15:17:58 -0700176 this(context, appWidgetHolder, callback, res, () -> res.getXml(layoutId), rootTag);
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700177 }
178
Sihua Maaa2b8722022-10-25 15:17:58 -0700179 public AutoInstallsLayout(Context context, LauncherWidgetHolder appWidgetHolder,
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700180 LayoutParserCallback callback, Resources res,
181 Supplier<XmlPullParser> initialLayoutSupplier, String rootTag) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700182 mContext = context;
Sihua Maaa2b8722022-10-25 15:17:58 -0700183 mAppWidgetHolder = appWidgetHolder;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700184 mCallback = callback;
185
186 mPackageManager = context.getPackageManager();
187 mValues = new ContentValues();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700188 mRootTag = rootTag;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700189
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700190 mSourceRes = res;
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700191 mInitialLayoutSupplier = initialLayoutSupplier;
Sunny Goyal96a09632015-12-16 11:32:54 -0800192
Sunny Goyal87f784c2017-01-11 10:48:34 -0800193 mIdp = LauncherAppState.getIDP(context);
Sunny Goyalbb011da2016-06-15 15:42:29 -0700194 mRowCount = mIdp.numRows;
195 mColumnCount = mIdp.numColumns;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700196 }
197
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700198 /**
199 * Loads the layout in the db and returns the number of entries added on the desktop.
200 */
Sunny Goyalefb7e842018-10-04 15:11:00 -0700201 public int loadLayout(SQLiteDatabase db, IntArray screenIds) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700202 mDb = db;
203 try {
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700204 return parseLayout(mInitialLayoutSupplier.get(), screenIds);
Sameer Padala8fd74832014-09-08 16:00:29 -0700205 } catch (Exception e) {
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700206 Log.e(TAG, "Error parsing layout: ", e);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700207 return -1;
208 }
209 }
210
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700211 /**
212 * Parses the layout and returns the number of elements added on the homescreen.
213 */
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700214 protected int parseLayout(XmlPullParser parser, IntArray screenIds)
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700215 throws XmlPullParserException, IOException {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700216 beginDocument(parser, mRootTag);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700217 final int depth = parser.getDepth();
218 int type;
Rajeev Kumar26453a22017-06-09 16:02:25 -0700219 ArrayMap<String, TagParser> tagParserMap = getLayoutElementsMap();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700220 int count = 0;
221
222 while (((type = parser.next()) != XmlPullParser.END_TAG ||
223 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
224 if (type != XmlPullParser.START_TAG) {
225 continue;
226 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700227 count += parseAndAddNode(parser, tagParserMap, screenIds);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700228 }
229 return count;
230 }
231
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700232 /**
233 * Parses container and screenId attribute from the current tag, and puts it in the out.
234 * @param out array of size 2.
235 */
Sunny Goyal0d742312019-03-04 20:22:26 -0800236 protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700237 if (HOTSEAT_CONTAINER_NAME.equals(getAttributeValue(parser, ATTR_CONTAINER))) {
238 out[0] = Favorites.CONTAINER_HOTSEAT;
239 // Hack: hotseat items are stored using screen ids
Sunny Goyalefb7e842018-10-04 15:11:00 -0700240 out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_RANK));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700241 } else {
242 out[0] = Favorites.CONTAINER_DESKTOP;
Sunny Goyalefb7e842018-10-04 15:11:00 -0700243 out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700244 }
245 }
246
247 /**
248 * Parses the current node and returns the number of elements added.
249 */
250 protected int parseAndAddNode(
Sunny Goyal0d742312019-03-04 20:22:26 -0800251 XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700252 throws XmlPullParserException, IOException {
Sunny Goyalb564efb2015-01-23 13:45:20 -0800253
254 if (TAG_INCLUDE.equals(parser.getName())) {
255 final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
256 if (resId != 0) {
257 // recursively load some more favorites, why not?
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700258 return parseLayout(mSourceRes.getXml(resId), screenIds);
Sunny Goyalb564efb2015-01-23 13:45:20 -0800259 } else {
260 return 0;
261 }
262 }
263
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700264 mValues.clear();
265 parseContainerAndScreen(parser, mTemp);
Sunny Goyalefb7e842018-10-04 15:11:00 -0700266 final int container = mTemp[0];
267 final int screenId = mTemp[1];
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700268
269 mValues.put(Favorites.CONTAINER, container);
270 mValues.put(Favorites.SCREEN, screenId);
Sunny Goyal96a09632015-12-16 11:32:54 -0800271
272 mValues.put(Favorites.CELLX,
Sunny Goyalf82e5472016-01-06 15:09:22 -0800273 convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount));
Sunny Goyal96a09632015-12-16 11:32:54 -0800274 mValues.put(Favorites.CELLY,
Sunny Goyalf82e5472016-01-06 15:09:22 -0800275 convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700276
277 TagParser tagParser = tagParserMap.get(parser.getName());
278 if (tagParser == null) {
279 if (LOGD) Log.d(TAG, "Ignoring unknown element tag: " + parser.getName());
280 return 0;
281 }
Sunny Goyalefb7e842018-10-04 15:11:00 -0700282 int newElementId = tagParser.parseAndAdd(parser);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700283 if (newElementId >= 0) {
284 // Keep track of the set of screens which need to be added to the db.
285 if (!screenIds.contains(screenId) &&
286 container == Favorites.CONTAINER_DESKTOP) {
287 screenIds.add(screenId);
288 }
289 return 1;
290 }
291 return 0;
292 }
293
Sunny Goyalefb7e842018-10-04 15:11:00 -0700294 protected int addShortcut(String title, Intent intent, int type) {
295 int id = mCallback.generateNewItemId();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700296 mValues.put(Favorites.INTENT, intent.toUri(0));
297 mValues.put(Favorites.TITLE, title);
298 mValues.put(Favorites.ITEM_TYPE, type);
299 mValues.put(Favorites.SPANX, 1);
300 mValues.put(Favorites.SPANY, 1);
301 mValues.put(Favorites._ID, id);
302 if (mCallback.insertAndCheck(mDb, mValues) < 0) {
303 return -1;
304 } else {
305 return id;
306 }
307 }
308
Rajeev Kumar26453a22017-06-09 16:02:25 -0700309 protected ArrayMap<String, TagParser> getFolderElementsMap() {
310 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700311 parsers.put(TAG_APP_ICON, new AppShortcutParser());
312 parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700313 parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes));
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700314 return parsers;
315 }
316
Rajeev Kumar26453a22017-06-09 16:02:25 -0700317 protected ArrayMap<String, TagParser> getLayoutElementsMap() {
318 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700319 parsers.put(TAG_APP_ICON, new AppShortcutParser());
320 parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
321 parsers.put(TAG_FOLDER, new FolderParser());
Sunny Goyal86df1382016-08-10 15:03:22 -0700322 parsers.put(TAG_APPWIDGET, new PendingWidgetParser());
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700323 parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700324 parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes));
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700325 return parsers;
326 }
327
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700328 protected interface TagParser {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700329 /**
330 * Parses the tag and adds to the db
331 * @return the id of the row added or -1;
332 */
Sunny Goyal0d742312019-03-04 20:22:26 -0800333 int parseAndAdd(XmlPullParser parser)
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700334 throws XmlPullParserException, IOException;
335 }
336
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700337 /**
338 * App shortcuts: required attributes packageName and className
339 */
340 protected class AppShortcutParser implements TagParser {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700341
342 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800343 public int parseAndAdd(XmlPullParser parser) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700344 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
345 final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
346
347 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
348 ActivityInfo info;
349 try {
350 ComponentName cn;
351 try {
352 cn = new ComponentName(packageName, className);
353 info = mPackageManager.getActivityInfo(cn, 0);
354 } catch (PackageManager.NameNotFoundException nnfe) {
355 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700356 new String[]{packageName});
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700357 cn = new ComponentName(packages[0], className);
358 info = mPackageManager.getActivityInfo(cn, 0);
359 }
360 final Intent intent = new Intent(Intent.ACTION_MAIN, null)
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700361 .addCategory(Intent.CATEGORY_LAUNCHER)
362 .setComponent(cn)
363 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
364 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700365
366 return addShortcut(info.loadLabel(mPackageManager).toString(),
367 intent, Favorites.ITEM_TYPE_APPLICATION);
368 } catch (PackageManager.NameNotFoundException e) {
Sunny Goyaleb3ba0f2017-03-27 11:22:36 -0700369 Log.e(TAG, "Favorite not found: " + packageName + "/" + className);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700370 }
371 return -1;
372 } else {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700373 return invalidPackageOrClass(parser);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700374 }
375 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700376
377 /**
378 * Helper method to allow extending the parser capabilities
379 */
Sunny Goyal0d742312019-03-04 20:22:26 -0800380 protected int invalidPackageOrClass(XmlPullParser parser) {
Adam Cohencf0c7462015-08-06 14:02:23 -0700381 Log.w(TAG, "Skipping invalid <favorite> with no component");
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700382 return -1;
383 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700384 }
385
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700386 /**
387 * AutoInstall: required attributes packageName and className
388 */
389 protected class AutoInstallParser implements TagParser {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700390
391 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800392 public int parseAndAdd(XmlPullParser parser) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700393 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
394 final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
395 if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
396 if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
397 return -1;
398 }
399
Sunny Goyal95899162019-03-27 16:03:06 -0700400 mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700401 final Intent intent = new Intent(Intent.ACTION_MAIN, null)
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700402 .addCategory(Intent.CATEGORY_LAUNCHER)
403 .setComponent(new ComponentName(packageName, className))
404 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
405 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700406 return addShortcut(mContext.getString(R.string.package_state_unknown), intent,
407 Favorites.ITEM_TYPE_APPLICATION);
408 }
409 }
410
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700411 /**
412 * Parses a web shortcut. Required attributes url, icon, title
413 */
414 protected class ShortcutParser implements TagParser {
415
416 private final Resources mIconRes;
417
418 public ShortcutParser(Resources iconRes) {
419 mIconRes = iconRes;
420 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700421
422 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800423 public int parseAndAdd(XmlPullParser parser) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700424 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
425 final int iconId = getAttributeResourceValue(parser, ATTR_ICON, 0);
426
427 if (titleResId == 0 || iconId == 0) {
428 if (LOGD) Log.d(TAG, "Ignoring shortcut");
429 return -1;
430 }
431
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700432 final Intent intent = parseIntent(parser);
433 if (intent == null) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700434 return -1;
435 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700436
437 Drawable icon = mIconRes.getDrawable(iconId);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700438 if (icon == null) {
439 if (LOGD) Log.d(TAG, "Ignoring shortcut, can't load icon");
440 return -1;
441 }
442
Sunny Goyalad7ff442017-09-12 12:42:26 -0700443 // Auto installs should always support the current platform version.
Sunny Goyal18a4e5a2018-01-09 15:34:38 -0800444 LauncherIcons li = LauncherIcons.obtain(mContext);
Sunny Goyale62d2bb2018-11-06 10:28:37 -0800445 mValues.put(LauncherSettings.Favorites.ICON, GraphicsUtils.flattenBitmap(
Sunny Goyald872a972021-11-24 18:07:04 -0800446 li.createBadgedIconBitmap(icon).icon));
Sunny Goyal18a4e5a2018-01-09 15:34:38 -0800447 li.recycle();
448
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700449 mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
450 mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
451
452 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700453 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700454 return addShortcut(mSourceRes.getString(titleResId),
455 intent, Favorites.ITEM_TYPE_SHORTCUT);
456 }
457
Sunny Goyal0d742312019-03-04 20:22:26 -0800458 protected Intent parseIntent(XmlPullParser parser) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700459 final String url = getAttributeValue(parser, ATTR_URL);
460 if (TextUtils.isEmpty(url) || !Patterns.WEB_URL.matcher(url).matches()) {
461 if (LOGD) Log.d(TAG, "Ignoring shortcut, invalid url: " + url);
462 return null;
463 }
464 return new Intent(Intent.ACTION_VIEW, null).setData(Uri.parse(url));
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700465 }
466 }
467
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700468 /**
469 * AppWidget parser: Required attributes packageName, className, spanX and spanY.
470 * Options child nodes: <extra key=... value=... />
Sunny Goyal86df1382016-08-10 15:03:22 -0700471 * It adds a pending widget which allows the widget to come later. If there are extras, those
472 * are passed to widget options during bind.
473 * The config activity for the widget (if present) is not shown, so any optional configurations
474 * should be passed as extras and the widget should support reading these widget options.
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700475 */
Sunny Goyal86df1382016-08-10 15:03:22 -0700476 protected class PendingWidgetParser implements TagParser {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700477
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700478 @Nullable
479 public ComponentName getComponentName(XmlPullParser parser) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700480 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
481 final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
482 if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700483 return null;
484 }
485 return new ComponentName(packageName, className);
486 }
487
488
489 @Override
490 public int parseAndAdd(XmlPullParser parser)
491 throws XmlPullParserException, IOException {
492 ComponentName cn = getComponentName(parser);
493 if (cn == null) {
Sunny Goyal86df1382016-08-10 15:03:22 -0700494 if (LOGD) Log.d(TAG, "Skipping invalid <appwidget> with no component");
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700495 return -1;
496 }
497
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700498 mValues.put(Favorites.SPANX, getAttributeValue(parser, ATTR_SPAN_X));
499 mValues.put(Favorites.SPANY, getAttributeValue(parser, ATTR_SPAN_Y));
Sunny Goyal86df1382016-08-10 15:03:22 -0700500 mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700501
502 // Read the extras
503 Bundle extras = new Bundle();
504 int widgetDepth = parser.getDepth();
505 int type;
506 while ((type = parser.next()) != XmlPullParser.END_TAG ||
507 parser.getDepth() > widgetDepth) {
508 if (type != XmlPullParser.START_TAG) {
509 continue;
510 }
511
512 if (TAG_EXTRA.equals(parser.getName())) {
513 String key = getAttributeValue(parser, ATTR_KEY);
514 String value = getAttributeValue(parser, ATTR_VALUE);
515 if (key != null && value != null) {
516 extras.putString(key, value);
517 } else {
518 throw new RuntimeException("Widget extras must have a key and value");
519 }
520 } else {
521 throw new RuntimeException("Widgets can contain only extras");
522 }
523 }
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700524 return verifyAndInsert(cn, extras);
Sunny Goyal86df1382016-08-10 15:03:22 -0700525 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700526
Sunny Goyalefb7e842018-10-04 15:11:00 -0700527 protected int verifyAndInsert(ComponentName cn, Bundle extras) {
Sunny Goyal86df1382016-08-10 15:03:22 -0700528 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
529 mValues.put(Favorites.RESTORED,
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700530 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
531 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
532 | LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
Sunny Goyal86df1382016-08-10 15:03:22 -0700533 mValues.put(Favorites._ID, mCallback.generateNewItemId());
534 if (!extras.isEmpty()) {
535 mValues.put(Favorites.INTENT, new Intent().putExtras(extras).toUri(0));
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700536 }
Sunny Goyal86df1382016-08-10 15:03:22 -0700537
Sunny Goyalefb7e842018-10-04 15:11:00 -0700538 int insertedId = mCallback.insertAndCheck(mDb, mValues);
Sunny Goyal86df1382016-08-10 15:03:22 -0700539 if (insertedId < 0) {
540 return -1;
541 } else {
542 return insertedId;
543 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700544 }
545 }
546
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700547 protected class SearchWidgetParser extends PendingWidgetParser {
548 @Override
549 @Nullable
550 public ComponentName getComponentName(XmlPullParser parser) {
551 return QsbContainerView.getSearchComponentName(mContext);
552 }
553
554 @Override
555 protected int verifyAndInsert(ComponentName cn, Bundle extras) {
556 mValues.put(Favorites.OPTIONS, LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET);
557 int flags = mValues.getAsInteger(Favorites.RESTORED)
558 | WorkspaceItemInfo.FLAG_RESTORE_STARTED;
559 mValues.put(Favorites.RESTORED, flags);
560 return super.verifyAndInsert(cn, extras);
561 }
562 }
563
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700564 protected class FolderParser implements TagParser {
Rajeev Kumar26453a22017-06-09 16:02:25 -0700565 private final ArrayMap<String, TagParser> mFolderElements;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700566
567 public FolderParser() {
568 this(getFolderElementsMap());
569 }
570
Rajeev Kumar26453a22017-06-09 16:02:25 -0700571 public FolderParser(ArrayMap<String, TagParser> elements) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700572 mFolderElements = elements;
573 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700574
575 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800576 public int parseAndAdd(XmlPullParser parser)
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700577 throws XmlPullParserException, IOException {
578 final String title;
579 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
580 if (titleResId != 0) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700581 title = mSourceRes.getString(titleResId);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700582 } else {
Sunny Goyal55e2b162020-06-09 15:44:48 -0700583 String titleText = getAttributeValue(parser, ATTR_TITLE_TEXT);
584 title = TextUtils.isEmpty(titleText) ? "" : titleText;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700585 }
586
587 mValues.put(Favorites.TITLE, title);
588 mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
589 mValues.put(Favorites.SPANX, 1);
590 mValues.put(Favorites.SPANY, 1);
591 mValues.put(Favorites._ID, mCallback.generateNewItemId());
Sunny Goyalefb7e842018-10-04 15:11:00 -0700592 int folderId = mCallback.insertAndCheck(mDb, mValues);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700593 if (folderId < 0) {
594 if (LOGD) Log.e(TAG, "Unable to add folder");
595 return -1;
596 }
597
598 final ContentValues myValues = new ContentValues(mValues);
Sunny Goyalefb7e842018-10-04 15:11:00 -0700599 IntArray folderItems = new IntArray();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700600
601 int type;
602 int folderDepth = parser.getDepth();
Sunny Goyal56a57bb2015-07-06 11:15:45 -0700603 int rank = 0;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700604 while ((type = parser.next()) != XmlPullParser.END_TAG ||
605 parser.getDepth() > folderDepth) {
606 if (type != XmlPullParser.START_TAG) {
607 continue;
608 }
609 mValues.clear();
610 mValues.put(Favorites.CONTAINER, folderId);
Sunny Goyal56a57bb2015-07-06 11:15:45 -0700611 mValues.put(Favorites.RANK, rank);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700612
613 TagParser tagParser = mFolderElements.get(parser.getName());
614 if (tagParser != null) {
Sunny Goyalefb7e842018-10-04 15:11:00 -0700615 final int id = tagParser.parseAndAdd(parser);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700616 if (id >= 0) {
617 folderItems.add(id);
Sunny Goyal56a57bb2015-07-06 11:15:45 -0700618 rank++;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700619 }
620 } else {
621 throw new RuntimeException("Invalid folder item " + parser.getName());
622 }
623 }
624
Sunny Goyalefb7e842018-10-04 15:11:00 -0700625 int addedId = folderId;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700626
627 // We can only have folders with >= 2 items, so we need to remove the
628 // folder and clean up if less than 2 items were included, or some
629 // failed to add, and less than 2 were actually added
630 if (folderItems.size() < 2) {
631 // Delete the folder
Sunny Goyal1d4a2df2015-03-30 11:11:46 -0700632 Uri uri = Favorites.getContentUri(folderId);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700633 SqlArguments args = new SqlArguments(uri, null, null);
634 mDb.delete(args.table, args.where, args.args);
635 addedId = -1;
636
637 // If we have a single item, promote it to where the folder
638 // would have been.
639 if (folderItems.size() == 1) {
640 final ContentValues childValues = new ContentValues();
641 copyInteger(myValues, childValues, Favorites.CONTAINER);
642 copyInteger(myValues, childValues, Favorites.SCREEN);
643 copyInteger(myValues, childValues, Favorites.CELLX);
644 copyInteger(myValues, childValues, Favorites.CELLY);
645
646 addedId = folderItems.get(0);
Sunny Goyalb5b55c82016-05-10 12:28:59 -0700647 mDb.update(Favorites.TABLE_NAME, childValues,
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700648 Favorites._ID + "=" + addedId, null);
649 }
650 }
651 return addedId;
652 }
653 }
654
Sunny Goyal762d0612020-07-29 15:03:46 -0700655 public static void beginDocument(XmlPullParser parser, String firstElementName)
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700656 throws XmlPullParserException, IOException {
657 int type;
658 while ((type = parser.next()) != XmlPullParser.START_TAG
659 && type != XmlPullParser.END_DOCUMENT);
660
661 if (type != XmlPullParser.START_TAG) {
662 throw new XmlPullParserException("No start tag found");
663 }
664
665 if (!parser.getName().equals(firstElementName)) {
666 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
667 ", expected " + firstElementName);
668 }
669 }
670
Sunny Goyal96a09632015-12-16 11:32:54 -0800671 private static String convertToDistanceFromEnd(String value, int endValue) {
672 if (!TextUtils.isEmpty(value)) {
673 int x = Integer.parseInt(value);
674 if (x < 0) {
675 return Integer.toString(endValue + x);
676 }
677 }
678 return value;
679 }
680
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700681 /**
682 * Return attribute value, attempting launcher-specific namespace first
683 * before falling back to anonymous attribute.
684 */
Sunny Goyal0d742312019-03-04 20:22:26 -0800685 protected static String getAttributeValue(XmlPullParser parser, String attribute) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700686 String value = parser.getAttributeValue(
687 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
688 if (value == null) {
689 value = parser.getAttributeValue(null, attribute);
690 }
691 return value;
692 }
693
694 /**
695 * Return attribute resource value, attempting launcher-specific namespace
696 * first before falling back to anonymous attribute.
697 */
Sunny Goyal0d742312019-03-04 20:22:26 -0800698 protected static int getAttributeResourceValue(XmlPullParser parser, String attribute,
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700699 int defaultValue) {
Sunny Goyal0d742312019-03-04 20:22:26 -0800700 AttributeSet attrs = Xml.asAttributeSet(parser);
701 int value = attrs.getAttributeResourceValue(
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700702 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
703 defaultValue);
704 if (value == defaultValue) {
Sunny Goyal0d742312019-03-04 20:22:26 -0800705 value = attrs.getAttributeResourceValue(null, attribute, defaultValue);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700706 }
707 return value;
708 }
709
Rajeev Kumar26453a22017-06-09 16:02:25 -0700710 public interface LayoutParserCallback {
Sunny Goyalefb7e842018-10-04 15:11:00 -0700711 int generateNewItemId();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700712
Sunny Goyalefb7e842018-10-04 15:11:00 -0700713 int insertAndCheck(SQLiteDatabase db, ContentValues values);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700714 }
715
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700716 @Thunk
717 static void copyInteger(ContentValues from, ContentValues to, String key) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700718 to.put(key, from.getAsInteger(key));
719 }
720}