blob: 197aa5a178f6b1523232eadcda1b67f98fd7f45d [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
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +000019import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
20
Sunny Goyal0fe505b2014-08-06 09:55:36 -070021import android.content.ComponentName;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.ActivityInfo;
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +000026import android.content.pm.LauncherActivityInfo;
Sunny Goyalafaa8f02023-03-30 12:34:43 -070027import android.content.pm.LauncherApps;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070028import android.content.pm.PackageManager;
29import android.content.res.Resources;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070030import android.database.sqlite.SQLiteDatabase;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070031import android.net.Uri;
32import android.os.Bundle;
Sunny Goyalafaa8f02023-03-30 12:34:43 -070033import android.os.Process;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070034import android.text.TextUtils;
Rajeev Kumar26453a22017-06-09 16:02:25 -070035import android.util.ArrayMap;
Sunny Goyal0d742312019-03-04 20:22:26 -080036import android.util.AttributeSet;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070037import android.util.Log;
Sunny Goyal0d742312019-03-04 20:22:26 -080038import android.util.Xml;
Sunny Goyal36b54222018-07-10 13:50:50 -070039
Samuel Fufaca37b8a2019-08-19 17:04:36 -070040import androidx.annotation.Nullable;
Stefan Andonian7fcee912023-04-07 17:56:29 +000041import androidx.annotation.WorkerThread;
Samuel Fufaca37b8a2019-08-19 17:04:36 -070042
Sunny Goyal0fe505b2014-08-06 09:55:36 -070043import com.android.launcher3.LauncherProvider.SqlArguments;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070044import com.android.launcher3.LauncherSettings.Favorites;
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +000045import com.android.launcher3.model.data.AppInfo;
Sunny Goyale396abf2020-04-06 15:11:17 -070046import com.android.launcher3.model.data.LauncherAppWidgetInfo;
47import com.android.launcher3.model.data.WorkspaceItemInfo;
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +000048import com.android.launcher3.pm.UserCache;
Samuel Fufaca37b8a2019-08-19 17:04:36 -070049import com.android.launcher3.qsb.QsbContainerView;
Sunny Goyalafaa8f02023-03-30 12:34:43 -070050import com.android.launcher3.shortcuts.ShortcutKey;
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +000051import com.android.launcher3.uioverrides.ApiWrapper;
Sunny Goyalefb7e842018-10-04 15:11:00 -070052import com.android.launcher3.util.IntArray;
Sunny Goyal3e58eea2022-11-14 14:30:07 -080053import com.android.launcher3.util.Partner;
Adam Cohen091440a2015-03-18 14:16:05 -070054import com.android.launcher3.util.Thunk;
Sihua Ma8bbfcb62022-11-08 16:46:07 -080055import com.android.launcher3.widget.LauncherWidgetHolder;
Sunny Goyal36b54222018-07-10 13:50:50 -070056
57import org.xmlpull.v1.XmlPullParser;
58import org.xmlpull.v1.XmlPullParserException;
59
Sunny Goyal0fe505b2014-08-06 09:55:36 -070060import java.io.IOException;
Sunny Goyalafaa8f02023-03-30 12:34:43 -070061import java.util.Collections;
Sunny Goyalb2d46ce2015-03-26 11:32:11 -070062import java.util.Locale;
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +000063import java.util.Map;
Sunny Goyalc0f03d92019-03-22 14:13:36 -070064import java.util.function.Supplier;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070065
66/**
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070067 * Layout parsing code for auto installs layout
Sunny Goyal0fe505b2014-08-06 09:55:36 -070068 */
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070069public class AutoInstallsLayout {
Sunny Goyal0fe505b2014-08-06 09:55:36 -070070 private static final String TAG = "AutoInstalls";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070071 private static final boolean LOGD = false;
Sunny Goyal0fe505b2014-08-06 09:55:36 -070072
73 /** Marker action used to discover a package which defines launcher customization */
74 static final String ACTION_LAUNCHER_CUSTOMIZATION =
Sunny Goyal2233c882014-09-18 14:36:48 -070075 "android.autoinstalls.config.action.PLAY_AUTO_INSTALL";
Sunny Goyal0fe505b2014-08-06 09:55:36 -070076
Sunny Goyalb2d46ce2015-03-26 11:32:11 -070077 /**
78 * Layout resource which also includes grid size and hotseat count, e.g., default_layout_6x6_h5
79 */
80 private static final String FORMATTED_LAYOUT_RES_WITH_HOSTEAT = "default_layout_%dx%d_h%s";
81 private static final String FORMATTED_LAYOUT_RES = "default_layout_%dx%d";
Sunny Goyal0fe505b2014-08-06 09:55:36 -070082 private static final String LAYOUT_RES = "default_layout";
83
Sunny Goyal1ae46ca2023-04-10 15:28:59 -070084 public static AutoInstallsLayout get(Context context, LauncherWidgetHolder appWidgetHolder,
Sunny Goyal0fe505b2014-08-06 09:55:36 -070085 LayoutParserCallback callback) {
Sunny Goyal3e58eea2022-11-14 14:30:07 -080086 Partner partner = Partner.get(context.getPackageManager(), ACTION_LAUNCHER_CUSTOMIZATION);
87 if (partner == null) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -070088 return null;
89 }
Sunny Goyal87f784c2017-01-11 10:48:34 -080090 InvariantDeviceProfile grid = LauncherAppState.getIDP(context);
Sunny Goyalb2d46ce2015-03-26 11:32:11 -070091
92 // Try with grid size and hotseat count
93 String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
Tony Wickhamb87f3cd2021-04-07 15:02:37 -070094 grid.numColumns, grid.numRows, grid.numDatabaseHotseatIcons);
Sunny Goyal3e58eea2022-11-14 14:30:07 -080095 int layoutId = partner.getXmlResId(layoutName);
Sunny Goyalb2d46ce2015-03-26 11:32:11 -070096
97 // Try with only grid size
98 if (layoutId == 0) {
99 Log.d(TAG, "Formatted layout: " + layoutName
100 + " not found. Trying layout without hosteat");
101 layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700102 grid.numColumns, grid.numRows);
Sunny Goyal3e58eea2022-11-14 14:30:07 -0800103 layoutId = partner.getXmlResId(layoutName);
Sunny Goyalb2d46ce2015-03-26 11:32:11 -0700104 }
105
106 // Try the default layout
107 if (layoutId == 0) {
108 Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying the default layout");
Sunny Goyal3e58eea2022-11-14 14:30:07 -0800109 layoutId = partner.getXmlResId(LAYOUT_RES);
Sunny Goyalb2d46ce2015-03-26 11:32:11 -0700110 }
111
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700112 if (layoutId == 0) {
Sunny Goyal3e58eea2022-11-14 14:30:07 -0800113 Log.e(TAG, "Layout definition not found in package: " + partner.getPackageName());
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700114 return null;
115 }
Sunny Goyal3e58eea2022-11-14 14:30:07 -0800116 return new AutoInstallsLayout(context, appWidgetHolder, callback, partner.getResources(),
117 layoutId, TAG_WORKSPACE);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700118 }
119
120 // Object Tags
Sunny Goyalb564efb2015-01-23 13:45:20 -0800121 private static final String TAG_INCLUDE = "include";
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700122 public static final String TAG_WORKSPACE = "workspace";
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700123 private static final String TAG_APP_ICON = "appicon";
124 private static final String TAG_AUTO_INSTALL = "autoinstall";
125 private static final String TAG_FOLDER = "folder";
126 private static final String TAG_APPWIDGET = "appwidget";
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700127 protected static final String TAG_SEARCH_WIDGET = "searchwidget";
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700128 private static final String TAG_SHORTCUT = "shortcut";
129 private static final String TAG_EXTRA = "extra";
130
131 private static final String ATTR_CONTAINER = "container";
132 private static final String ATTR_RANK = "rank";
133
134 private static final String ATTR_PACKAGE_NAME = "packageName";
135 private static final String ATTR_CLASS_NAME = "className";
136 private static final String ATTR_TITLE = "title";
Sunny Goyal55e2b162020-06-09 15:44:48 -0700137 private static final String ATTR_TITLE_TEXT = "titleText";
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700138 private static final String ATTR_SCREEN = "screen";
Sunny Goyalafaa8f02023-03-30 12:34:43 -0700139 private static final String ATTR_SHORTCUT_ID = "shortcutId";
Sunny Goyal96a09632015-12-16 11:32:54 -0800140
141 // x and y can be specified as negative integers, in which case -1 represents the
142 // last row / column, -2 represents the second last, and so on.
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700143 private static final String ATTR_X = "x";
144 private static final String ATTR_Y = "y";
Sunny Goyal96a09632015-12-16 11:32:54 -0800145
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700146 private static final String ATTR_SPAN_X = "spanX";
147 private static final String ATTR_SPAN_Y = "spanY";
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700148
Sunny Goyalb564efb2015-01-23 13:45:20 -0800149 // Attrs for "Include"
150 private static final String ATTR_WORKSPACE = "workspace";
151
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700152 // Style attrs -- "Extra"
153 private static final String ATTR_KEY = "key";
154 private static final String ATTR_VALUE = "value";
155
156 private static final String HOTSEAT_CONTAINER_NAME =
157 Favorites.containerToString(Favorites.CONTAINER_HOTSEAT);
158
Sunny Goyalafaa8f02023-03-30 12:34:43 -0700159 protected final Context mContext;
160 protected final LauncherWidgetHolder mAppWidgetHolder;
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800161 protected final LayoutParserCallback mCallback;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700162
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700163 protected final PackageManager mPackageManager;
164 protected final Resources mSourceRes;
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700165 protected final Supplier<XmlPullParser> mInitialLayoutSupplier;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700166
Sunny Goyalbb011da2016-06-15 15:42:29 -0700167 private final InvariantDeviceProfile mIdp;
Sunny Goyal96a09632015-12-16 11:32:54 -0800168 private final int mRowCount;
169 private final int mColumnCount;
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +0000170 private final Map<String, LauncherActivityInfo> mActivityOverride;
Sunny Goyalefb7e842018-10-04 15:11:00 -0700171 private final int[] mTemp = new int[2];
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700172 @Thunk
173 final ContentValues mValues;
Sunny Goyalbb3b02f2015-01-15 12:00:14 -0800174 protected final String mRootTag;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700175
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700176 protected SQLiteDatabase mDb;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700177
Sihua Maaa2b8722022-10-25 15:17:58 -0700178 public AutoInstallsLayout(Context context, LauncherWidgetHolder appWidgetHolder,
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700179 LayoutParserCallback callback, Resources res,
180 int layoutId, String rootTag) {
Sihua Maaa2b8722022-10-25 15:17:58 -0700181 this(context, appWidgetHolder, callback, res, () -> res.getXml(layoutId), rootTag);
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700182 }
183
Sihua Maaa2b8722022-10-25 15:17:58 -0700184 public AutoInstallsLayout(Context context, LauncherWidgetHolder appWidgetHolder,
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700185 LayoutParserCallback callback, Resources res,
186 Supplier<XmlPullParser> initialLayoutSupplier, String rootTag) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700187 mContext = context;
Sihua Maaa2b8722022-10-25 15:17:58 -0700188 mAppWidgetHolder = appWidgetHolder;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700189 mCallback = callback;
190
191 mPackageManager = context.getPackageManager();
192 mValues = new ContentValues();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700193 mRootTag = rootTag;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700194
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700195 mSourceRes = res;
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700196 mInitialLayoutSupplier = initialLayoutSupplier;
Sunny Goyal96a09632015-12-16 11:32:54 -0800197
Sunny Goyal87f784c2017-01-11 10:48:34 -0800198 mIdp = LauncherAppState.getIDP(context);
Sunny Goyalbb011da2016-06-15 15:42:29 -0700199 mRowCount = mIdp.numRows;
200 mColumnCount = mIdp.numColumns;
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +0000201 mActivityOverride = ApiWrapper.getActivityOverrides(context);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700202 }
203
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700204 /**
205 * Loads the layout in the db and returns the number of entries added on the desktop.
206 */
Sunny Goyalefb7e842018-10-04 15:11:00 -0700207 public int loadLayout(SQLiteDatabase db, IntArray screenIds) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700208 mDb = db;
209 try {
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700210 return parseLayout(mInitialLayoutSupplier.get(), screenIds);
Sameer Padala8fd74832014-09-08 16:00:29 -0700211 } catch (Exception e) {
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700212 Log.e(TAG, "Error parsing layout: ", e);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700213 return -1;
214 }
215 }
216
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700217 /**
218 * Parses the layout and returns the number of elements added on the homescreen.
219 */
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700220 protected int parseLayout(XmlPullParser parser, IntArray screenIds)
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700221 throws XmlPullParserException, IOException {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700222 beginDocument(parser, mRootTag);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700223 final int depth = parser.getDepth();
224 int type;
Rajeev Kumar26453a22017-06-09 16:02:25 -0700225 ArrayMap<String, TagParser> tagParserMap = getLayoutElementsMap();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700226 int count = 0;
227
228 while (((type = parser.next()) != XmlPullParser.END_TAG ||
229 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
230 if (type != XmlPullParser.START_TAG) {
231 continue;
232 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700233 count += parseAndAddNode(parser, tagParserMap, screenIds);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700234 }
235 return count;
236 }
237
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700238 /**
239 * Parses container and screenId attribute from the current tag, and puts it in the out.
240 * @param out array of size 2.
241 */
Sunny Goyal0d742312019-03-04 20:22:26 -0800242 protected void parseContainerAndScreen(XmlPullParser parser, int[] out) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700243 if (HOTSEAT_CONTAINER_NAME.equals(getAttributeValue(parser, ATTR_CONTAINER))) {
244 out[0] = Favorites.CONTAINER_HOTSEAT;
245 // Hack: hotseat items are stored using screen ids
Sunny Goyalefb7e842018-10-04 15:11:00 -0700246 out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_RANK));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700247 } else {
248 out[0] = Favorites.CONTAINER_DESKTOP;
Sunny Goyalefb7e842018-10-04 15:11:00 -0700249 out[1] = Integer.parseInt(getAttributeValue(parser, ATTR_SCREEN));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700250 }
251 }
252
253 /**
254 * Parses the current node and returns the number of elements added.
255 */
256 protected int parseAndAddNode(
Sunny Goyal0d742312019-03-04 20:22:26 -0800257 XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700258 throws XmlPullParserException, IOException {
Sunny Goyalb564efb2015-01-23 13:45:20 -0800259
260 if (TAG_INCLUDE.equals(parser.getName())) {
261 final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
262 if (resId != 0) {
263 // recursively load some more favorites, why not?
Sunny Goyalc0f03d92019-03-22 14:13:36 -0700264 return parseLayout(mSourceRes.getXml(resId), screenIds);
Sunny Goyalb564efb2015-01-23 13:45:20 -0800265 } else {
266 return 0;
267 }
268 }
269
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700270 mValues.clear();
271 parseContainerAndScreen(parser, mTemp);
Sunny Goyalefb7e842018-10-04 15:11:00 -0700272 final int container = mTemp[0];
273 final int screenId = mTemp[1];
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700274
275 mValues.put(Favorites.CONTAINER, container);
276 mValues.put(Favorites.SCREEN, screenId);
Sunny Goyal96a09632015-12-16 11:32:54 -0800277
278 mValues.put(Favorites.CELLX,
Sunny Goyalf82e5472016-01-06 15:09:22 -0800279 convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount));
Sunny Goyal96a09632015-12-16 11:32:54 -0800280 mValues.put(Favorites.CELLY,
Sunny Goyalf82e5472016-01-06 15:09:22 -0800281 convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount));
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700282
283 TagParser tagParser = tagParserMap.get(parser.getName());
284 if (tagParser == null) {
285 if (LOGD) Log.d(TAG, "Ignoring unknown element tag: " + parser.getName());
286 return 0;
287 }
Sunny Goyalefb7e842018-10-04 15:11:00 -0700288 int newElementId = tagParser.parseAndAdd(parser);
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700289 if (newElementId >= 0) {
290 // Keep track of the set of screens which need to be added to the db.
291 if (!screenIds.contains(screenId) &&
292 container == Favorites.CONTAINER_DESKTOP) {
293 screenIds.add(screenId);
294 }
295 return 1;
296 }
297 return 0;
298 }
299
Sunny Goyalefb7e842018-10-04 15:11:00 -0700300 protected int addShortcut(String title, Intent intent, int type) {
301 int id = mCallback.generateNewItemId();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700302 mValues.put(Favorites.INTENT, intent.toUri(0));
303 mValues.put(Favorites.TITLE, title);
304 mValues.put(Favorites.ITEM_TYPE, type);
305 mValues.put(Favorites.SPANX, 1);
306 mValues.put(Favorites.SPANY, 1);
307 mValues.put(Favorites._ID, id);
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +0000308
Sunny Goyalafaa8f02023-03-30 12:34:43 -0700309 if (type == ITEM_TYPE_APPLICATION) {
310 ComponentName cn = intent.getComponent();
311 if (cn != null && mActivityOverride.containsKey(cn.getPackageName())) {
312 LauncherActivityInfo replacementInfo = mActivityOverride.get(cn.getPackageName());
313 mValues.put(Favorites.PROFILE_ID, UserCache.INSTANCE.get(mContext)
314 .getSerialNumberForUser(replacementInfo.getUser()));
315 mValues.put(Favorites.INTENT, AppInfo.makeLaunchIntent(replacementInfo).toUri(0));
316 }
317 }
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +0000318
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700319 if (mCallback.insertAndCheck(mDb, mValues) < 0) {
320 return -1;
321 } else {
322 return id;
323 }
324 }
325
Rajeev Kumar26453a22017-06-09 16:02:25 -0700326 protected ArrayMap<String, TagParser> getFolderElementsMap() {
327 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700328 parsers.put(TAG_APP_ICON, new AppShortcutParser());
329 parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
Sunny Goyalafaa8f02023-03-30 12:34:43 -0700330 parsers.put(TAG_SHORTCUT, new ShortcutParser());
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700331 return parsers;
332 }
333
Rajeev Kumar26453a22017-06-09 16:02:25 -0700334 protected ArrayMap<String, TagParser> getLayoutElementsMap() {
335 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700336 parsers.put(TAG_APP_ICON, new AppShortcutParser());
337 parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
338 parsers.put(TAG_FOLDER, new FolderParser());
Sunny Goyal86df1382016-08-10 15:03:22 -0700339 parsers.put(TAG_APPWIDGET, new PendingWidgetParser());
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700340 parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
Sunny Goyalafaa8f02023-03-30 12:34:43 -0700341 parsers.put(TAG_SHORTCUT, new ShortcutParser());
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700342 return parsers;
343 }
344
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700345 protected interface TagParser {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700346 /**
347 * Parses the tag and adds to the db
348 * @return the id of the row added or -1;
349 */
Sunny Goyal0d742312019-03-04 20:22:26 -0800350 int parseAndAdd(XmlPullParser parser)
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700351 throws XmlPullParserException, IOException;
352 }
353
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700354 /**
355 * App shortcuts: required attributes packageName and className
356 */
357 protected class AppShortcutParser implements TagParser {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700358
359 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800360 public int parseAndAdd(XmlPullParser parser) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700361 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
362 final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
363
364 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
365 ActivityInfo info;
366 try {
367 ComponentName cn;
368 try {
369 cn = new ComponentName(packageName, className);
370 info = mPackageManager.getActivityInfo(cn, 0);
371 } catch (PackageManager.NameNotFoundException nnfe) {
372 String[] packages = mPackageManager.currentToCanonicalPackageNames(
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700373 new String[]{packageName});
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700374 cn = new ComponentName(packages[0], className);
375 info = mPackageManager.getActivityInfo(cn, 0);
376 }
377 final Intent intent = new Intent(Intent.ACTION_MAIN, null)
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700378 .addCategory(Intent.CATEGORY_LAUNCHER)
379 .setComponent(cn)
380 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
381 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700382
383 return addShortcut(info.loadLabel(mPackageManager).toString(),
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +0000384 intent, ITEM_TYPE_APPLICATION);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700385 } catch (PackageManager.NameNotFoundException e) {
Sunny Goyaleb3ba0f2017-03-27 11:22:36 -0700386 Log.e(TAG, "Favorite not found: " + packageName + "/" + className);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700387 }
388 return -1;
389 } else {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700390 return invalidPackageOrClass(parser);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700391 }
392 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700393
394 /**
395 * Helper method to allow extending the parser capabilities
396 */
Sunny Goyal0d742312019-03-04 20:22:26 -0800397 protected int invalidPackageOrClass(XmlPullParser parser) {
Adam Cohencf0c7462015-08-06 14:02:23 -0700398 Log.w(TAG, "Skipping invalid <favorite> with no component");
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700399 return -1;
400 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700401 }
402
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700403 /**
404 * AutoInstall: required attributes packageName and className
405 */
406 protected class AutoInstallParser implements TagParser {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700407
408 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800409 public int parseAndAdd(XmlPullParser parser) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700410 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
411 final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
412 if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
413 if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
414 return -1;
415 }
416
Sunny Goyal95899162019-03-27 16:03:06 -0700417 mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700418 final Intent intent = new Intent(Intent.ACTION_MAIN, null)
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700419 .addCategory(Intent.CATEGORY_LAUNCHER)
420 .setComponent(new ComponentName(packageName, className))
421 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
422 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700423 return addShortcut(mContext.getString(R.string.package_state_unknown), intent,
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +0000424 ITEM_TYPE_APPLICATION);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700425 }
426 }
427
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700428 /**
Sunny Goyalafaa8f02023-03-30 12:34:43 -0700429 * Parses a deep shortcut. Required attributes packageName and shortcutId
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700430 */
431 protected class ShortcutParser implements TagParser {
432
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700433 @Override
Sunny Goyal0d742312019-03-04 20:22:26 -0800434 public int parseAndAdd(XmlPullParser parser) {
Sunny Goyalafaa8f02023-03-30 12:34:43 -0700435 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
436 final String shortcutId = getAttributeValue(parser, ATTR_SHORTCUT_ID);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700437
Sunny Goyalafaa8f02023-03-30 12:34:43 -0700438 try {
439 LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
440 launcherApps.pinShortcuts(packageName, Collections.singletonList(shortcutId),
441 Process.myUserHandle());
442 Intent intent = ShortcutKey.makeIntent(shortcutId, packageName);
443 mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_RESTORED_ICON);
444 return addShortcut(null, intent, Favorites.ITEM_TYPE_DEEP_SHORTCUT);
445 } catch (Exception e) {
446 Log.e(TAG, "Unable to pin the shortcut for shortcut id = " + shortcutId
447 + " and package name = " + packageName, e);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700448 }
Sunny Goyalafaa8f02023-03-30 12:34:43 -0700449 return -1;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700450 }
451 }
452
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700453 /**
454 * AppWidget parser: Required attributes packageName, className, spanX and spanY.
455 * Options child nodes: <extra key=... value=... />
Sunny Goyal86df1382016-08-10 15:03:22 -0700456 * It adds a pending widget which allows the widget to come later. If there are extras, those
457 * are passed to widget options during bind.
458 * The config activity for the widget (if present) is not shown, so any optional configurations
459 * should be passed as extras and the widget should support reading these widget options.
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700460 */
Sunny Goyal86df1382016-08-10 15:03:22 -0700461 protected class PendingWidgetParser implements TagParser {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700462
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700463 @Nullable
464 public ComponentName getComponentName(XmlPullParser parser) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700465 final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
466 final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
467 if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700468 return null;
469 }
470 return new ComponentName(packageName, className);
471 }
472
473
474 @Override
475 public int parseAndAdd(XmlPullParser parser)
476 throws XmlPullParserException, IOException {
477 ComponentName cn = getComponentName(parser);
478 if (cn == null) {
Sunny Goyal86df1382016-08-10 15:03:22 -0700479 if (LOGD) Log.d(TAG, "Skipping invalid <appwidget> with no component");
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700480 return -1;
481 }
482
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700483 mValues.put(Favorites.SPANX, getAttributeValue(parser, ATTR_SPAN_X));
484 mValues.put(Favorites.SPANY, getAttributeValue(parser, ATTR_SPAN_Y));
Sunny Goyal86df1382016-08-10 15:03:22 -0700485 mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_APPWIDGET);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700486
487 // Read the extras
488 Bundle extras = new Bundle();
489 int widgetDepth = parser.getDepth();
490 int type;
491 while ((type = parser.next()) != XmlPullParser.END_TAG ||
492 parser.getDepth() > widgetDepth) {
493 if (type != XmlPullParser.START_TAG) {
494 continue;
495 }
496
497 if (TAG_EXTRA.equals(parser.getName())) {
498 String key = getAttributeValue(parser, ATTR_KEY);
499 String value = getAttributeValue(parser, ATTR_VALUE);
500 if (key != null && value != null) {
501 extras.putString(key, value);
502 } else {
503 throw new RuntimeException("Widget extras must have a key and value");
504 }
505 } else {
506 throw new RuntimeException("Widgets can contain only extras");
507 }
508 }
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700509 return verifyAndInsert(cn, extras);
Sunny Goyal86df1382016-08-10 15:03:22 -0700510 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700511
Sunny Goyalefb7e842018-10-04 15:11:00 -0700512 protected int verifyAndInsert(ComponentName cn, Bundle extras) {
Sunny Goyal86df1382016-08-10 15:03:22 -0700513 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
514 mValues.put(Favorites.RESTORED,
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700515 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
516 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
517 | LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
Sunny Goyal86df1382016-08-10 15:03:22 -0700518 mValues.put(Favorites._ID, mCallback.generateNewItemId());
519 if (!extras.isEmpty()) {
520 mValues.put(Favorites.INTENT, new Intent().putExtras(extras).toUri(0));
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700521 }
Sunny Goyal86df1382016-08-10 15:03:22 -0700522
Sunny Goyalefb7e842018-10-04 15:11:00 -0700523 int insertedId = mCallback.insertAndCheck(mDb, mValues);
Sunny Goyal86df1382016-08-10 15:03:22 -0700524 if (insertedId < 0) {
525 return -1;
526 } else {
527 return insertedId;
528 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700529 }
530 }
531
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700532 protected class SearchWidgetParser extends PendingWidgetParser {
533 @Override
534 @Nullable
Stefan Andonian7fcee912023-04-07 17:56:29 +0000535 @WorkerThread
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700536 public ComponentName getComponentName(XmlPullParser parser) {
537 return QsbContainerView.getSearchComponentName(mContext);
538 }
539
540 @Override
541 protected int verifyAndInsert(ComponentName cn, Bundle extras) {
542 mValues.put(Favorites.OPTIONS, LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET);
543 int flags = mValues.getAsInteger(Favorites.RESTORED)
544 | WorkspaceItemInfo.FLAG_RESTORE_STARTED;
545 mValues.put(Favorites.RESTORED, flags);
546 return super.verifyAndInsert(cn, extras);
547 }
548 }
549
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700550 protected class FolderParser implements TagParser {
Rajeev Kumar26453a22017-06-09 16:02:25 -0700551 private final ArrayMap<String, TagParser> mFolderElements;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700552
553 public FolderParser() {
554 this(getFolderElementsMap());
555 }
556
Rajeev Kumar26453a22017-06-09 16:02:25 -0700557 public FolderParser(ArrayMap<String, TagParser> elements) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700558 mFolderElements = elements;
559 }
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700560
561 @Override
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +0000562 public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException, IOException {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700563 final String title;
564 final int titleResId = getAttributeResourceValue(parser, ATTR_TITLE, 0);
565 if (titleResId != 0) {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700566 title = mSourceRes.getString(titleResId);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700567 } else {
Sunny Goyal55e2b162020-06-09 15:44:48 -0700568 String titleText = getAttributeValue(parser, ATTR_TITLE_TEXT);
569 title = TextUtils.isEmpty(titleText) ? "" : titleText;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700570 }
571
572 mValues.put(Favorites.TITLE, title);
573 mValues.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_FOLDER);
574 mValues.put(Favorites.SPANX, 1);
575 mValues.put(Favorites.SPANY, 1);
576 mValues.put(Favorites._ID, mCallback.generateNewItemId());
Sunny Goyalefb7e842018-10-04 15:11:00 -0700577 int folderId = mCallback.insertAndCheck(mDb, mValues);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700578 if (folderId < 0) {
579 if (LOGD) Log.e(TAG, "Unable to add folder");
580 return -1;
581 }
582
583 final ContentValues myValues = new ContentValues(mValues);
Sunny Goyalefb7e842018-10-04 15:11:00 -0700584 IntArray folderItems = new IntArray();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700585
586 int type;
587 int folderDepth = parser.getDepth();
Sunny Goyal56a57bb2015-07-06 11:15:45 -0700588 int rank = 0;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700589 while ((type = parser.next()) != XmlPullParser.END_TAG ||
590 parser.getDepth() > folderDepth) {
591 if (type != XmlPullParser.START_TAG) {
592 continue;
593 }
594 mValues.clear();
595 mValues.put(Favorites.CONTAINER, folderId);
Sunny Goyal56a57bb2015-07-06 11:15:45 -0700596 mValues.put(Favorites.RANK, rank);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700597
598 TagParser tagParser = mFolderElements.get(parser.getName());
599 if (tagParser != null) {
Sunny Goyalefb7e842018-10-04 15:11:00 -0700600 final int id = tagParser.parseAndAdd(parser);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700601 if (id >= 0) {
602 folderItems.add(id);
Sunny Goyal56a57bb2015-07-06 11:15:45 -0700603 rank++;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700604 }
605 } else {
606 throw new RuntimeException("Invalid folder item " + parser.getName());
607 }
608 }
609
Sunny Goyalefb7e842018-10-04 15:11:00 -0700610 int addedId = folderId;
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700611
612 // We can only have folders with >= 2 items, so we need to remove the
613 // folder and clean up if less than 2 items were included, or some
614 // failed to add, and less than 2 were actually added
615 if (folderItems.size() < 2) {
616 // Delete the folder
Sunny Goyal1d4a2df2015-03-30 11:11:46 -0700617 Uri uri = Favorites.getContentUri(folderId);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700618 SqlArguments args = new SqlArguments(uri, null, null);
619 mDb.delete(args.table, args.where, args.args);
620 addedId = -1;
621
622 // If we have a single item, promote it to where the folder
623 // would have been.
624 if (folderItems.size() == 1) {
625 final ContentValues childValues = new ContentValues();
626 copyInteger(myValues, childValues, Favorites.CONTAINER);
627 copyInteger(myValues, childValues, Favorites.SCREEN);
628 copyInteger(myValues, childValues, Favorites.CELLX);
629 copyInteger(myValues, childValues, Favorites.CELLY);
630
631 addedId = folderItems.get(0);
Sunny Goyalb5b55c82016-05-10 12:28:59 -0700632 mDb.update(Favorites.TABLE_NAME, childValues,
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700633 Favorites._ID + "=" + addedId, null);
634 }
635 }
636 return addedId;
637 }
638 }
639
Sunny Goyal762d0612020-07-29 15:03:46 -0700640 public static void beginDocument(XmlPullParser parser, String firstElementName)
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700641 throws XmlPullParserException, IOException {
642 int type;
643 while ((type = parser.next()) != XmlPullParser.START_TAG
644 && type != XmlPullParser.END_DOCUMENT);
645
646 if (type != XmlPullParser.START_TAG) {
647 throw new XmlPullParserException("No start tag found");
648 }
649
650 if (!parser.getName().equals(firstElementName)) {
651 throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
652 ", expected " + firstElementName);
653 }
654 }
655
Sunny Goyal96a09632015-12-16 11:32:54 -0800656 private static String convertToDistanceFromEnd(String value, int endValue) {
657 if (!TextUtils.isEmpty(value)) {
658 int x = Integer.parseInt(value);
659 if (x < 0) {
660 return Integer.toString(endValue + x);
661 }
662 }
663 return value;
664 }
665
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700666 /**
667 * Return attribute value, attempting launcher-specific namespace first
668 * before falling back to anonymous attribute.
669 */
Sunny Goyal0d742312019-03-04 20:22:26 -0800670 protected static String getAttributeValue(XmlPullParser parser, String attribute) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700671 String value = parser.getAttributeValue(
672 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute);
673 if (value == null) {
674 value = parser.getAttributeValue(null, attribute);
675 }
676 return value;
677 }
678
679 /**
680 * Return attribute resource value, attempting launcher-specific namespace
681 * first before falling back to anonymous attribute.
682 */
Sunny Goyal0d742312019-03-04 20:22:26 -0800683 protected static int getAttributeResourceValue(XmlPullParser parser, String attribute,
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700684 int defaultValue) {
Sunny Goyal0d742312019-03-04 20:22:26 -0800685 AttributeSet attrs = Xml.asAttributeSet(parser);
686 int value = attrs.getAttributeResourceValue(
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700687 "http://schemas.android.com/apk/res-auto/com.android.launcher3", attribute,
688 defaultValue);
689 if (value == defaultValue) {
Sunny Goyal0d742312019-03-04 20:22:26 -0800690 value = attrs.getAttributeResourceValue(null, attribute, defaultValue);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700691 }
692 return value;
693 }
694
Rajeev Kumar26453a22017-06-09 16:02:25 -0700695 public interface LayoutParserCallback {
Sunny Goyalefb7e842018-10-04 15:11:00 -0700696 int generateNewItemId();
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700697
Sunny Goyalefb7e842018-10-04 15:11:00 -0700698 int insertAndCheck(SQLiteDatabase db, ContentValues values);
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700699 }
700
Samuel Fufaca37b8a2019-08-19 17:04:36 -0700701 @Thunk
702 static void copyInteger(ContentValues from, ContentValues to, String key) {
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700703 to.put(key, from.getAsInteger(key));
704 }
Nicolas Sleiman31e9fa42023-01-25 16:30:32 +0000705
Sunny Goyal0fe505b2014-08-06 09:55:36 -0700706}