blob: 1ec30ba68dae027fa228005b03809371dc3538cf [file] [log] [blame]
Sunny Goyal3a5a9d12014-10-01 15:33:41 -07001package com.android.launcher3;
2
3import android.appwidget.AppWidgetHost;
Sunny Goyal86df1382016-08-10 15:03:22 -07004import android.appwidget.AppWidgetManager;
5import android.content.ComponentName;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -07006import android.content.Context;
7import android.content.Intent;
8import android.content.pm.ActivityInfo;
9import android.content.pm.ApplicationInfo;
10import android.content.pm.PackageManager;
11import android.content.pm.ResolveInfo;
12import android.content.res.Resources;
13import android.content.res.XmlResourceParser;
Sunny Goyal86df1382016-08-10 15:03:22 -070014import android.os.Bundle;
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;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070018import com.android.launcher3.LauncherSettings.Favorites;
Adam Cohen091440a2015-03-18 14:16:05 -070019import com.android.launcher3.util.Thunk;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070020import java.io.IOException;
21import java.net.URISyntaxException;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070022import java.util.List;
Rajeev Kumar26453a22017-06-09 16:02:25 -070023import org.xmlpull.v1.XmlPullParser;
24import org.xmlpull.v1.XmlPullParserException;
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070025
26/**
27 * Implements the layout parser with rules for internal layouts and partner layouts.
28 */
29public class DefaultLayoutParser extends AutoInstallsLayout {
30 private static final String TAG = "DefaultLayoutParser";
31
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080032 protected static final String TAG_RESOLVE = "resolve";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070033 private static final String TAG_FAVORITES = "favorites";
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080034 protected static final String TAG_FAVORITE = "favorite";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070035 private static final String TAG_APPWIDGET = "appwidget";
Sunny Goyala5c8a9e2016-07-08 08:32:44 -070036 protected static final String TAG_SHORTCUT = "shortcut";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070037 private static final String TAG_FOLDER = "folder";
38 private static final String TAG_PARTNER_FOLDER = "partner-folder";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070039
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080040 protected static final String ATTR_URI = "uri";
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070041 private static final String ATTR_CONTAINER = "container";
42 private static final String ATTR_SCREEN = "screen";
43 private static final String ATTR_FOLDER_ITEMS = "folderItems";
44
Sunny Goyal86df1382016-08-10 15:03:22 -070045 // TODO: Remove support for this broadcast, instead use widget options to send bind time options
46 private static final String ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE =
47 "com.android.launcher.action.APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE";
48
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070049 public DefaultLayoutParser(Context context, AppWidgetHost appWidgetHost,
50 LayoutParserCallback callback, Resources sourceRes, int layoutId) {
51 super(context, appWidgetHost, callback, sourceRes, layoutId, TAG_FAVORITES);
Sunny Goyalbb3b02f2015-01-15 12:00:14 -080052 }
53
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070054 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070055 protected ArrayMap<String, TagParser> getFolderElementsMap() {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070056 return getFolderElementsMap(mSourceRes);
57 }
58
Rajeev Kumar26453a22017-06-09 16:02:25 -070059 @Thunk ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
60 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070061 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
62 parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
63 return parsers;
64 }
65
66 @Override
Rajeev Kumar26453a22017-06-09 16:02:25 -070067 protected ArrayMap<String, TagParser> getLayoutElementsMap() {
68 ArrayMap<String, TagParser> parsers = new ArrayMap<>();
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070069 parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
70 parsers.put(TAG_APPWIDGET, new AppWidgetParser());
71 parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
72 parsers.put(TAG_RESOLVE, new ResolveParser());
73 parsers.put(TAG_FOLDER, new MyFolderParser());
74 parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser());
75 return parsers;
76 }
77
78 @Override
79 protected void parseContainerAndScreen(XmlResourceParser parser, long[] out) {
80 out[0] = LauncherSettings.Favorites.CONTAINER_DESKTOP;
81 String strContainer = getAttributeValue(parser, ATTR_CONTAINER);
82 if (strContainer != null) {
83 out[0] = Long.valueOf(strContainer);
84 }
85 out[1] = Long.parseLong(getAttributeValue(parser, ATTR_SCREEN));
86 }
87
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070088 /**
89 * AppShortcutParser which also supports adding URI based intents
90 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -070091 public class AppShortcutWithUriParser extends AppShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -070092
93 @Override
94 protected long invalidPackageOrClass(XmlResourceParser parser) {
95 final String uri = getAttributeValue(parser, ATTR_URI);
96 if (TextUtils.isEmpty(uri)) {
97 Log.e(TAG, "Skipping invalid <favorite> with no component or uri");
98 return -1;
99 }
100
101 final Intent metaIntent;
102 try {
103 metaIntent = Intent.parseUri(uri, 0);
104 } catch (URISyntaxException e) {
105 Log.e(TAG, "Unable to add meta-favorite: " + uri, e);
106 return -1;
107 }
108
109 ResolveInfo resolved = mPackageManager.resolveActivity(metaIntent,
110 PackageManager.MATCH_DEFAULT_ONLY);
111 final List<ResolveInfo> appList = mPackageManager.queryIntentActivities(
112 metaIntent, PackageManager.MATCH_DEFAULT_ONLY);
113
114 // Verify that the result is an app and not just the resolver dialog asking which
115 // app to use.
116 if (wouldLaunchResolverActivity(resolved, appList)) {
117 // If only one of the results is a system app then choose that as the default.
118 final ResolveInfo systemApp = getSingleSystemActivity(appList);
119 if (systemApp == null) {
120 // There is no logical choice for this meta-favorite, so rather than making
121 // a bad choice just add nothing.
122 Log.w(TAG, "No preference or single system activity found for "
123 + metaIntent.toString());
124 return -1;
125 }
126 resolved = systemApp;
127 }
128 final ActivityInfo info = resolved.activityInfo;
129 final Intent intent = mPackageManager.getLaunchIntentForPackage(info.packageName);
130 if (intent == null) {
131 return -1;
132 }
133 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
134 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
135
136 return addShortcut(info.loadLabel(mPackageManager).toString(), intent,
137 Favorites.ITEM_TYPE_APPLICATION);
138 }
139
140 private ResolveInfo getSingleSystemActivity(List<ResolveInfo> appList) {
141 ResolveInfo systemResolve = null;
142 final int N = appList.size();
143 for (int i = 0; i < N; ++i) {
144 try {
145 ApplicationInfo info = mPackageManager.getApplicationInfo(
146 appList.get(i).activityInfo.packageName, 0);
147 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
148 if (systemResolve != null) {
149 return null;
150 } else {
151 systemResolve = appList.get(i);
152 }
153 }
154 } catch (PackageManager.NameNotFoundException e) {
155 Log.w(TAG, "Unable to get info about resolve results", e);
156 return null;
157 }
158 }
159 return systemResolve;
160 }
161
162 private boolean wouldLaunchResolverActivity(ResolveInfo resolved,
163 List<ResolveInfo> appList) {
164 // If the list contains the above resolved activity, then it can't be
165 // ResolverActivity itself.
166 for (int i = 0; i < appList.size(); ++i) {
167 ResolveInfo tmp = appList.get(i);
168 if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
169 && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
170 return false;
171 }
172 }
173 return true;
174 }
175 }
176
177
178 /**
179 * Shortcut parser which allows any uri and not just web urls.
180 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700181 public class UriShortcutParser extends ShortcutParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700182
183 public UriShortcutParser(Resources iconRes) {
184 super(iconRes);
185 }
186
187 @Override
188 protected Intent parseIntent(XmlResourceParser parser) {
189 String uri = null;
190 try {
191 uri = getAttributeValue(parser, ATTR_URI);
192 return Intent.parseUri(uri, 0);
193 } catch (URISyntaxException e) {
194 Log.w(TAG, "Shortcut has malformed uri: " + uri);
195 return null; // Oh well
196 }
197 }
198 }
199
200 /**
201 * Contains a list of <favorite> nodes, and accepts the first successfully parsed node.
202 */
Sunny Goyala5c8a9e2016-07-08 08:32:44 -0700203 public class ResolveParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700204
205 private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser();
206
207 @Override
208 public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
209 IOException {
210 final int groupDepth = parser.getDepth();
211 int type;
212 long addedId = -1;
213 while ((type = parser.next()) != XmlPullParser.END_TAG ||
214 parser.getDepth() > groupDepth) {
215 if (type != XmlPullParser.START_TAG || addedId > -1) {
216 continue;
217 }
218 final String fallback_item_name = parser.getName();
219 if (TAG_FAVORITE.equals(fallback_item_name)) {
220 addedId = mChildParser.parseAndAdd(parser);
221 } else {
222 Log.e(TAG, "Fallback groups can contain only favorites, found "
223 + fallback_item_name);
224 }
225 }
226 return addedId;
227 }
228 }
229
230 /**
231 * A parser which adds a folder whose contents come from partner apk.
232 */
Adam Cohen091440a2015-03-18 14:16:05 -0700233 @Thunk class PartnerFolderParser implements TagParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700234
235 @Override
236 public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
237 IOException {
238 // Folder contents come from an external XML resource
239 final Partner partner = Partner.get(mPackageManager);
240 if (partner != null) {
241 final Resources partnerRes = partner.getResources();
242 final int resId = partnerRes.getIdentifier(Partner.RES_FOLDER,
243 "xml", partner.getPackageName());
244 if (resId != 0) {
245 final XmlResourceParser partnerParser = partnerRes.getXml(resId);
246 beginDocument(partnerParser, TAG_FOLDER);
247
248 FolderParser folderParser = new FolderParser(getFolderElementsMap(partnerRes));
249 return folderParser.parseAndAdd(partnerParser);
250 }
251 }
252 return -1;
253 }
254 }
255
256 /**
257 * An extension of FolderParser which allows adding items from a different xml.
258 */
Adam Cohen091440a2015-03-18 14:16:05 -0700259 @Thunk class MyFolderParser extends FolderParser {
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700260
261 @Override
262 public long parseAndAdd(XmlResourceParser parser) throws XmlPullParserException,
263 IOException {
264 final int resId = getAttributeResourceValue(parser, ATTR_FOLDER_ITEMS, 0);
265 if (resId != 0) {
266 parser = mSourceRes.getXml(resId);
267 beginDocument(parser, TAG_FOLDER);
268 }
269 return super.parseAndAdd(parser);
270 }
271 }
Sunny Goyal86df1382016-08-10 15:03:22 -0700272
273
274 /**
275 * AppWidget parser which enforces that the app is already installed when the layout is parsed.
276 */
277 protected class AppWidgetParser extends PendingWidgetParser {
278
279 @Override
280 protected long verifyAndInsert(ComponentName cn, Bundle extras) {
281 try {
282 mPackageManager.getReceiverInfo(cn, 0);
283 } catch (Exception e) {
284 String[] packages = mPackageManager.currentToCanonicalPackageNames(
285 new String[] { cn.getPackageName() });
286 cn = new ComponentName(packages[0], cn.getClassName());
287 try {
288 mPackageManager.getReceiverInfo(cn, 0);
289 } catch (Exception e1) {
290 Log.d(TAG, "Can't find widget provider: " + cn.getClassName());
291 return -1;
292 }
293 }
294
295 final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
296 long insertedId = -1;
297 try {
298 int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
299
300 if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
301 Log.e(TAG, "Unable to bind app widget id " + cn);
302 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
303 return -1;
304 }
305
306 mValues.put(Favorites.APPWIDGET_ID, appWidgetId);
307 mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
308 mValues.put(Favorites._ID, mCallback.generateNewItemId());
309 insertedId = mCallback.insertAndCheck(mDb, mValues);
310 if (insertedId < 0) {
311 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
312 return insertedId;
313 }
314
315 // Send a broadcast to configure the widget
316 if (!extras.isEmpty()) {
317 Intent intent = new Intent(ACTION_APPWIDGET_DEFAULT_WORKSPACE_CONFIGURE);
318 intent.setComponent(cn);
319 intent.putExtras(extras);
320 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
321 mContext.sendBroadcast(intent);
322 }
323 } catch (RuntimeException ex) {
324 Log.e(TAG, "Problem allocating appWidgetId", ex);
325 }
326 return insertedId;
327 }
328 }
Sunny Goyal3a5a9d12014-10-01 15:33:41 -0700329}