blob: 90f08c30139ad7a0741b9c9c90f1dd8d5e483f80 [file] [log] [blame]
Makoto Onuki22fcc682016-05-17 14:52:19 -07001/*
2 * Copyright (C) 2016 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 */
16package com.android.server.pm;
17
Mehdi Alizadeh32774622018-11-05 17:32:01 -080018import android.annotation.NonNull;
Makoto Onuki22fcc682016-05-17 14:52:19 -070019import android.annotation.Nullable;
20import android.annotation.UserIdInt;
21import android.content.ComponentName;
22import android.content.Intent;
23import android.content.pm.ActivityInfo;
Makoto Onukib08790c2016-06-23 14:05:46 -070024import android.content.pm.ResolveInfo;
Makoto Onuki22fcc682016-05-17 14:52:19 -070025import android.content.pm.ShortcutInfo;
26import android.content.res.TypedArray;
27import android.content.res.XmlResourceParser;
Makoto Onuki22fcc682016-05-17 14:52:19 -070028import android.text.TextUtils;
29import android.util.ArraySet;
30import android.util.AttributeSet;
Makoto Onukidf6da042016-06-16 09:51:40 -070031import android.util.Log;
Makoto Onuki22fcc682016-05-17 14:52:19 -070032import android.util.Slog;
Makoto Onuki6771d732016-07-14 12:58:40 -070033import android.util.TypedValue;
Makoto Onuki22fcc682016-05-17 14:52:19 -070034import android.util.Xml;
35
36import com.android.internal.R;
37import com.android.internal.annotations.VisibleForTesting;
38
39import org.xmlpull.v1.XmlPullParser;
40import org.xmlpull.v1.XmlPullParserException;
41
42import java.io.IOException;
43import java.util.ArrayList;
44import java.util.List;
45import java.util.Set;
46
47public class ShortcutParser {
48 private static final String TAG = ShortcutService.TAG;
49
50 private static final boolean DEBUG = ShortcutService.DEBUG || false; // DO NOT SUBMIT WITH TRUE
51
52 @VisibleForTesting
Makoto Onukidf6da042016-06-16 09:51:40 -070053 static final String METADATA_KEY = "android.app.shortcuts";
Makoto Onuki22fcc682016-05-17 14:52:19 -070054
55 private static final String TAG_SHORTCUTS = "shortcuts";
56 private static final String TAG_SHORTCUT = "shortcut";
Makoto Onukidf6da042016-06-16 09:51:40 -070057 private static final String TAG_INTENT = "intent";
58 private static final String TAG_CATEGORIES = "categories";
Mehdi Alizadeh32774622018-11-05 17:32:01 -080059 private static final String TAG_SHARE_TARGET = "share-target";
60 private static final String TAG_DATA = "data";
61 private static final String TAG_CATEGORY = "category";
Makoto Onuki22fcc682016-05-17 14:52:19 -070062
63 @Nullable
Mehdi Alizadeh32774622018-11-05 17:32:01 -080064 public static List<ShortcutInfo> parseShortcuts(ShortcutService service, String packageName,
65 @UserIdInt int userId, @NonNull List<ShareTargetInfo> outShareTargets)
66 throws IOException, XmlPullParserException {
Makoto Onukib08790c2016-06-23 14:05:46 -070067 if (ShortcutService.DEBUG) {
68 Slog.d(TAG, String.format("Scanning package %s for manifest shortcuts on user %d",
69 packageName, userId));
70 }
71 final List<ResolveInfo> activities = service.injectGetMainActivities(packageName, userId);
72 if (activities == null || activities.size() == 0) {
73 return null;
74 }
Makoto Onuki22fcc682016-05-17 14:52:19 -070075
76 List<ShortcutInfo> result = null;
Mehdi Alizadeh32774622018-11-05 17:32:01 -080077 outShareTargets.clear();
Makoto Onuki22fcc682016-05-17 14:52:19 -070078
Makoto Onukidf6da042016-06-16 09:51:40 -070079 try {
Makoto Onukib08790c2016-06-23 14:05:46 -070080 final int size = activities.size();
81 for (int i = 0; i < size; i++) {
82 final ActivityInfo activityInfoNoMetadata = activities.get(i).activityInfo;
83 if (activityInfoNoMetadata == null) {
84 continue;
85 }
86
87 final ActivityInfo activityInfoWithMetadata =
Makoto Onukiee6b6e42016-06-29 17:34:02 -070088 service.getActivityInfoWithMetadata(
Makoto Onukib08790c2016-06-23 14:05:46 -070089 activityInfoNoMetadata.getComponentName(), userId);
90 if (activityInfoWithMetadata != null) {
Mehdi Alizadeh32774622018-11-05 17:32:01 -080091 result = parseShortcutsOneFile(service, activityInfoWithMetadata, packageName,
92 userId, result, outShareTargets);
Makoto Onukidf6da042016-06-16 09:51:40 -070093 }
Makoto Onuki22fcc682016-05-17 14:52:19 -070094 }
Makoto Onukidf6da042016-06-16 09:51:40 -070095 } catch (RuntimeException e) {
Makoto Onukib08790c2016-06-23 14:05:46 -070096 // Resource ID mismatch may cause various runtime exceptions when parsing XMLs,
97 // But we don't crash the device, so just swallow them.
Makoto Onukidf6da042016-06-16 09:51:40 -070098 service.wtf(
99 "Exception caught while parsing shortcut XML for package=" + packageName, e);
100 return null;
Makoto Onuki22fcc682016-05-17 14:52:19 -0700101 }
102 return result;
103 }
104
105 private static List<ShortcutInfo> parseShortcutsOneFile(
106 ShortcutService service,
107 ActivityInfo activityInfo, String packageName, @UserIdInt int userId,
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800108 List<ShortcutInfo> result, @NonNull List<ShareTargetInfo> outShareTargets)
109 throws IOException, XmlPullParserException {
Makoto Onukib08790c2016-06-23 14:05:46 -0700110 if (ShortcutService.DEBUG) {
111 Slog.d(TAG, String.format(
112 "Checking main activity %s", activityInfo.getComponentName()));
113 }
114
Makoto Onuki22fcc682016-05-17 14:52:19 -0700115 XmlResourceParser parser = null;
116 try {
117 parser = service.injectXmlMetaData(activityInfo, METADATA_KEY);
118 if (parser == null) {
119 return result;
120 }
121
122 final ComponentName activity = new ComponentName(packageName, activityInfo.name);
123
124 final AttributeSet attrs = Xml.asAttributeSet(parser);
125
126 int type;
127
Makoto Onukieddbfec2016-05-31 17:04:34 -0700128 int rank = 0;
Makoto Onuki7001a612016-05-27 13:24:28 -0700129 final int maxShortcuts = service.getMaxActivityShortcuts();
130 int numShortcuts = 0;
Makoto Onukieddbfec2016-05-31 17:04:34 -0700131
Makoto Onukidf6da042016-06-16 09:51:40 -0700132 // We instantiate ShortcutInfo at <shortcut>, but we add it to the list at </shortcut>,
133 // after parsing <intent>. We keep the current one in here.
134 ShortcutInfo currentShortcut = null;
135
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800136 // We instantiate ShareTargetInfo at <share-target>, but add it to outShareTargets at
137 // </share-target>, after parsing <data> and <category>. We keep the current one here.
138 ShareTargetInfo currentShareTarget = null;
139
140 // Keeps parsed categories for both ShortcutInfo and ShareTargetInfo
Makoto Onukidf6da042016-06-16 09:51:40 -0700141 Set<String> categories = null;
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800142
143 // Keeps parsed intents for ShortcutInfo
Makoto Onuki440a1ea2016-07-20 14:21:18 -0700144 final ArrayList<Intent> intents = new ArrayList<>();
Makoto Onukidf6da042016-06-16 09:51:40 -0700145
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800146 // Keeps parsed data fields for ShareTargetInfo
147 final ArrayList<ShareTargetInfo.TargetData> dataList = new ArrayList<>();
148
Makoto Onuki22fcc682016-05-17 14:52:19 -0700149 outer:
150 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
151 && (type != XmlPullParser.END_TAG || parser.getDepth() > 0)) {
Makoto Onukidf6da042016-06-16 09:51:40 -0700152 final int depth = parser.getDepth();
153 final String tag = parser.getName();
154
155 // When a shortcut tag is closing, publish.
156 if ((type == XmlPullParser.END_TAG) && (depth == 2) && (TAG_SHORTCUT.equals(tag))) {
157 if (currentShortcut == null) {
158 // Shortcut was invalid.
159 continue;
160 }
161 final ShortcutInfo si = currentShortcut;
162 currentShortcut = null; // Make sure to null out for the next iteration.
163
Makoto Onuki440a1ea2016-07-20 14:21:18 -0700164 if (si.isEnabled()) {
165 if (intents.size() == 0) {
166 Log.e(TAG, "Shortcut " + si.getId() + " has no intent. Skipping it.");
167 continue;
168 }
169 } else {
170 // Just set the default intent to disabled shortcuts.
171 intents.clear();
172 intents.add(new Intent(Intent.ACTION_VIEW));
Makoto Onukidf6da042016-06-16 09:51:40 -0700173 }
174
175 if (numShortcuts >= maxShortcuts) {
176 Log.e(TAG, "More than " + maxShortcuts + " shortcuts found for "
177 + activityInfo.getComponentName() + ". Skipping the rest.");
178 return result;
179 }
Makoto Onuki440a1ea2016-07-20 14:21:18 -0700180
181 // Same flag as what TaskStackBuilder adds.
182 intents.get(0).addFlags(
183 Intent.FLAG_ACTIVITY_NEW_TASK |
184 Intent.FLAG_ACTIVITY_CLEAR_TASK |
185 Intent.FLAG_ACTIVITY_TASK_ON_HOME);
186 try {
187 si.setIntents(intents.toArray(new Intent[intents.size()]));
188 } catch (RuntimeException e) {
189 // This shouldn't happen because intents in XML can't have complicated
190 // extras, but just in case Intent.parseIntent() supports such a thing one
191 // day.
192 Log.e(TAG, "Shortcut's extras contain un-persistable values. Skipping it.");
193 continue;
194 }
195 intents.clear();
196
Makoto Onukidf6da042016-06-16 09:51:40 -0700197 if (categories != null) {
198 si.setCategories(categories);
199 categories = null;
200 }
201
202 if (result == null) {
203 result = new ArrayList<>();
204 }
205 result.add(si);
206 numShortcuts++;
207 rank++;
208 if (ShortcutService.DEBUG) {
209 Slog.d(TAG, "Shortcut added: " + si.toInsecureString());
210 }
211 continue;
212 }
213
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800214 // When a share-target tag is closing, publish.
215 if ((type == XmlPullParser.END_TAG) && (depth == 2)
216 && (TAG_SHARE_TARGET.equals(tag))) {
217 if (currentShareTarget == null) {
218 // ShareTarget was invalid.
219 continue;
220 }
221 final ShareTargetInfo sti = currentShareTarget;
222 currentShareTarget = null; // Make sure to null out for the next iteration.
223
224 if (categories == null || categories.isEmpty() || dataList.isEmpty()) {
225 // Incomplete ShareTargetInfo.
226 continue;
227 }
228
229 final ShareTargetInfo newShareTarget = new ShareTargetInfo(
230 dataList.toArray(new ShareTargetInfo.TargetData[dataList.size()]),
231 sti.mTargetClass, categories.toArray(new String[categories.size()]));
232 outShareTargets.add(newShareTarget);
233 if (ShortcutService.DEBUG) {
234 Slog.d(TAG, "ShareTarget added: " + newShareTarget.toString());
235 }
236 categories = null;
237 dataList.clear();
238 }
239
Makoto Onukidf6da042016-06-16 09:51:40 -0700240 // Otherwise, just look at start tags.
Makoto Onuki22fcc682016-05-17 14:52:19 -0700241 if (type != XmlPullParser.START_TAG) {
242 continue;
243 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700244
245 if (depth == 1 && TAG_SHORTCUTS.equals(tag)) {
246 continue; // Root tag.
247 }
248 if (depth == 2 && TAG_SHORTCUT.equals(tag)) {
249 final ShortcutInfo si = parseShortcutAttributes(
Makoto Onuki9e1f5592016-06-08 12:30:23 -0700250 service, attrs, packageName, activity, userId, rank);
Makoto Onukidf6da042016-06-16 09:51:40 -0700251 if (si == null) {
252 // Shortcut was invalid.
253 continue;
254 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700255 if (ShortcutService.DEBUG) {
Makoto Onukidf6da042016-06-16 09:51:40 -0700256 Slog.d(TAG, "Shortcut found: " + si.toInsecureString());
Makoto Onuki22fcc682016-05-17 14:52:19 -0700257 }
258 if (result != null) {
259 for (int i = result.size() - 1; i >= 0; i--) {
260 if (si.getId().equals(result.get(i).getId())) {
Makoto Onukidf6da042016-06-16 09:51:40 -0700261 Log.e(TAG, "Duplicate shortcut ID detected. Skipping it.");
Makoto Onuki22fcc682016-05-17 14:52:19 -0700262 continue outer;
263 }
264 }
265 }
Makoto Onukidf6da042016-06-16 09:51:40 -0700266 currentShortcut = si;
267 categories = null;
268 continue;
269 }
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800270 if (depth == 2 && TAG_SHARE_TARGET.equals(tag)) {
271 final ShareTargetInfo sti = parseShareTargetAttributes(service, attrs);
272 if (sti == null) {
273 // ShareTarget was invalid.
274 continue;
275 }
276 currentShareTarget = sti;
277 categories = null;
278 dataList.clear();
279 continue;
280 }
Makoto Onukidf6da042016-06-16 09:51:40 -0700281 if (depth == 3 && TAG_INTENT.equals(tag)) {
282 if ((currentShortcut == null)
Makoto Onukidf6da042016-06-16 09:51:40 -0700283 || !currentShortcut.isEnabled()) {
284 Log.e(TAG, "Ignoring excessive intent tag.");
285 continue;
286 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700287
Makoto Onukidf6da042016-06-16 09:51:40 -0700288 final Intent intent = Intent.parseIntent(service.mContext.getResources(),
289 parser, attrs);
290 if (TextUtils.isEmpty(intent.getAction())) {
291 Log.e(TAG, "Shortcut intent action must be provided. activity=" + activity);
Makoto Onuki440a1ea2016-07-20 14:21:18 -0700292 currentShortcut = null; // Invalidate the current shortcut.
Makoto Onukidf6da042016-06-16 09:51:40 -0700293 continue;
294 }
Makoto Onuki440a1ea2016-07-20 14:21:18 -0700295 intents.add(intent);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700296 continue;
297 }
Makoto Onukidf6da042016-06-16 09:51:40 -0700298 if (depth == 3 && TAG_CATEGORIES.equals(tag)) {
299 if ((currentShortcut == null)
300 || (currentShortcut.getCategories() != null)) {
301 continue;
302 }
303 final String name = parseCategories(service, attrs);
304 if (TextUtils.isEmpty(name)) {
305 Log.e(TAG, "Empty category found. activity=" + activity);
306 continue;
307 }
308
309 if (categories == null) {
310 categories = new ArraySet<>();
311 }
312 categories.add(name);
313 continue;
314 }
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800315 if (depth == 3 && TAG_CATEGORY.equals(tag)) {
316 if ((currentShareTarget == null)) {
317 continue;
318 }
319 final String name = parseCategory(service, attrs);
320 if (TextUtils.isEmpty(name)) {
321 Log.e(TAG, "Empty category found. activity=" + activity);
322 continue;
323 }
324
325 if (categories == null) {
326 categories = new ArraySet<>();
327 }
328 categories.add(name);
329 continue;
330 }
331 if (depth == 3 && TAG_DATA.equals(tag)) {
332 if ((currentShareTarget == null)) {
333 continue;
334 }
335 final ShareTargetInfo.TargetData data = parseShareTargetData(service, attrs);
336 if (data == null) {
337 Log.e(TAG, "Invalid data tag found. activity=" + activity);
338 continue;
339 }
340 dataList.add(data);
341 continue;
342 }
Makoto Onukidf6da042016-06-16 09:51:40 -0700343
Makoto Onuki9c850012016-07-26 15:50:50 -0700344 Log.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
Makoto Onuki22fcc682016-05-17 14:52:19 -0700345 }
346 } finally {
347 if (parser != null) {
348 parser.close();
349 }
350 }
351 return result;
352 }
353
Makoto Onukidf6da042016-06-16 09:51:40 -0700354 private static String parseCategories(ShortcutService service, AttributeSet attrs) {
355 final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
356 R.styleable.ShortcutCategories);
357 try {
Makoto Onuki6771d732016-07-14 12:58:40 -0700358 if (sa.getType(R.styleable.ShortcutCategories_name) == TypedValue.TYPE_STRING) {
359 return sa.getNonResourceString(R.styleable.ShortcutCategories_name);
360 } else {
361 Log.w(TAG, "android:name for shortcut category must be string literal.");
362 return null;
363 }
Makoto Onukidf6da042016-06-16 09:51:40 -0700364 } finally {
365 sa.recycle();
366 }
367 }
368
Makoto Onuki22fcc682016-05-17 14:52:19 -0700369 private static ShortcutInfo parseShortcutAttributes(ShortcutService service,
370 AttributeSet attrs, String packageName, ComponentName activity,
Makoto Onukieddbfec2016-05-31 17:04:34 -0700371 @UserIdInt int userId, int rank) {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700372 final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
373 R.styleable.Shortcut);
374 try {
Makoto Onuki6771d732016-07-14 12:58:40 -0700375 if (sa.getType(R.styleable.Shortcut_shortcutId) != TypedValue.TYPE_STRING) {
376 Log.w(TAG, "android:shortcutId must be string literal. activity=" + activity);
377 return null;
378 }
379 final String id = sa.getNonResourceString(R.styleable.Shortcut_shortcutId);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700380 final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true);
Makoto Onukidf6da042016-06-16 09:51:40 -0700381 final int iconResId = sa.getResourceId(R.styleable.Shortcut_icon, 0);
Makoto Onukieddbfec2016-05-31 17:04:34 -0700382 final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutShortLabel, 0);
383 final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutLongLabel, 0);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700384 final int disabledMessageResId = sa.getResourceId(
385 R.styleable.Shortcut_shortcutDisabledMessage, 0);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700386
387 if (TextUtils.isEmpty(id)) {
Makoto Onuki6771d732016-07-14 12:58:40 -0700388 Log.w(TAG, "android:shortcutId must be provided. activity=" + activity);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700389 return null;
390 }
391 if (titleResId == 0) {
Makoto Onuki6771d732016-07-14 12:58:40 -0700392 Log.w(TAG, "android:shortcutShortLabel must be provided. activity=" + activity);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700393 return null;
394 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700395
396 return createShortcutFromManifest(
397 service,
398 userId,
399 id,
400 packageName,
401 activity,
402 titleResId,
403 textResId,
404 disabledMessageResId,
Makoto Onuki22fcc682016-05-17 14:52:19 -0700405 rank,
406 iconResId,
407 enabled);
408 } finally {
409 sa.recycle();
410 }
411 }
412
413 private static ShortcutInfo createShortcutFromManifest(ShortcutService service,
414 @UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
Makoto Onukidf6da042016-06-16 09:51:40 -0700415 int titleResId, int textResId, int disabledMessageResId,
416 int rank, int iconResId, boolean enabled) {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700417
418 final int flags =
419 (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
420 | ShortcutInfo.FLAG_IMMUTABLE
421 | ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0);
Makoto Onukia4f89b12017-10-05 10:37:55 -0700422 final int disabledReason =
423 enabled ? ShortcutInfo.DISABLED_REASON_NOT_DISABLED
424 : ShortcutInfo.DISABLED_REASON_BY_APP;
Makoto Onuki22fcc682016-05-17 14:52:19 -0700425
Makoto Onuki157b1622016-06-02 16:13:10 -0700426 // Note we don't need to set resource names here yet. They'll be set when they're about
427 // to be published.
Makoto Onuki22fcc682016-05-17 14:52:19 -0700428 return new ShortcutInfo(
429 userId,
430 id,
431 packageName,
432 activityComponent,
433 null, // icon
434 null, // title string
435 titleResId,
Makoto Onuki157b1622016-06-02 16:13:10 -0700436 null, // title res name
Makoto Onuki22fcc682016-05-17 14:52:19 -0700437 null, // text string
438 textResId,
Makoto Onuki157b1622016-06-02 16:13:10 -0700439 null, // text res name
Makoto Onuki22fcc682016-05-17 14:52:19 -0700440 null, // disabled message string
441 disabledMessageResId,
Makoto Onuki157b1622016-06-02 16:13:10 -0700442 null, // disabled message res name
Makoto Onukidf6da042016-06-16 09:51:40 -0700443 null, // categories
444 null, // intent
Makoto Onuki22fcc682016-05-17 14:52:19 -0700445 rank,
446 null, // extras
447 service.injectCurrentTimeMillis(),
448 flags,
449 iconResId,
Makoto Onuki157b1622016-06-02 16:13:10 -0700450 null, // icon res name
Makoto Onukia4f89b12017-10-05 10:37:55 -0700451 null, // bitmap path
452 disabledReason);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700453 }
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800454
455 private static String parseCategory(ShortcutService service, AttributeSet attrs) {
456 final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
457 R.styleable.IntentCategory);
458 try {
459 if (sa.getType(R.styleable.IntentCategory_name) != TypedValue.TYPE_STRING) {
460 Log.w(TAG, "android:name must be string literal.");
461 return null;
462 }
463 return sa.getString(R.styleable.IntentCategory_name);
464 } finally {
465 sa.recycle();
466 }
467 }
468
469 private static ShareTargetInfo parseShareTargetAttributes(ShortcutService service,
470 AttributeSet attrs) {
471 final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
472 R.styleable.Intent);
473 try {
474 String targetClass = sa.getString(R.styleable.Intent_targetClass);
475 if (TextUtils.isEmpty(targetClass)) {
476 Log.w(TAG, "android:targetClass must be provided.");
477 return null;
478 }
479 return new ShareTargetInfo(null, targetClass, null);
480 } finally {
481 sa.recycle();
482 }
483 }
484
485 private static ShareTargetInfo.TargetData parseShareTargetData(ShortcutService service,
486 AttributeSet attrs) {
487 final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
488 R.styleable.AndroidManifestData);
489 try {
490 if (sa.getType(R.styleable.AndroidManifestData_mimeType) != TypedValue.TYPE_STRING) {
491 Log.w(TAG, "android:mimeType must be string literal.");
492 return null;
493 }
494 String scheme = sa.getString(R.styleable.AndroidManifestData_scheme);
495 String host = sa.getString(R.styleable.AndroidManifestData_host);
496 String port = sa.getString(R.styleable.AndroidManifestData_port);
497 String path = sa.getString(R.styleable.AndroidManifestData_path);
498 String pathPattern = sa.getString(R.styleable.AndroidManifestData_pathPattern);
499 String pathPrefix = sa.getString(R.styleable.AndroidManifestData_pathPrefix);
500 String mimeType = sa.getString(R.styleable.AndroidManifestData_mimeType);
501 return new ShareTargetInfo.TargetData(scheme, host, port, path, pathPattern, pathPrefix,
502 mimeType);
503 } finally {
504 sa.recycle();
505 }
506 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700507}