1 /*
2  * Copyright (C) 2022 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 
17 package com.android.internal.pm.pkg.component;
18 
19 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK;
20 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
21 
22 import static com.android.internal.pm.pkg.component.ComponentParseUtils.flag;
23 import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET;
24 import static com.android.internal.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.ActivityTaskManager;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.pm.ActivityInfo;
32 import android.content.pm.parsing.FrameworkParsingPackageUtils;
33 import android.content.pm.parsing.result.ParseInput;
34 import android.content.pm.parsing.result.ParseInput.DeferredError;
35 import android.content.pm.parsing.result.ParseResult;
36 import android.content.res.Configuration;
37 import android.content.res.Resources;
38 import android.content.res.TypedArray;
39 import android.content.res.XmlResourceParser;
40 import android.os.Build;
41 import android.util.ArraySet;
42 import android.util.AttributeSet;
43 import android.util.Log;
44 import android.util.Slog;
45 import android.util.TypedValue;
46 import android.view.Gravity;
47 import android.view.WindowManager;
48 
49 import com.android.internal.R;
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.pm.pkg.parsing.ParsingPackage;
52 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
53 import com.android.internal.pm.pkg.parsing.ParsingUtils;
54 import com.android.internal.util.ArrayUtils;
55 
56 import org.xmlpull.v1.XmlPullParser;
57 import org.xmlpull.v1.XmlPullParserException;
58 
59 import java.io.IOException;
60 import java.util.List;
61 import java.util.Objects;
62 import java.util.Set;
63 
64 /**
65  * @hide
66  */
67 public class ParsedActivityUtils {
68 
69     private static final String TAG = ParsingUtils.TAG;
70 
71     public static final boolean LOG_UNSAFE_BROADCASTS = false;
72 
73     // Set of broadcast actions that are safe for manifest receivers
74     public static final Set<String> SAFE_BROADCASTS = new ArraySet<>();
75     static {
76         SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED);
77     }
78 
79     /**
80      * Bit mask of all the valid bits that can be set in recreateOnConfigChanges.
81      */
82     private static final int RECREATE_ON_CONFIG_CHANGES_MASK =
83             ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
84 
85     @NonNull
86     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
parseActivityOrReceiver(String[] separateProcesses, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, boolean useRoundIcon, @Nullable String defaultSplitName, ParseInput input)87     public static ParseResult<ParsedActivity> parseActivityOrReceiver(String[] separateProcesses,
88             ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
89             boolean useRoundIcon, @Nullable String defaultSplitName, ParseInput input)
90             throws XmlPullParserException, IOException {
91         final String packageName = pkg.getPackageName();
92         final ParsedActivityImpl activity = new ParsedActivityImpl();
93 
94         boolean receiver = "receiver".equals(parser.getName());
95         String tag = "<" + parser.getName() + ">";
96         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivity);
97         try {
98             ParseResult<ParsedActivityImpl> result =
99                     ParsedMainComponentUtils.parseMainComponent(activity, tag, separateProcesses,
100                             pkg, sa, flags, useRoundIcon, defaultSplitName, input,
101                             R.styleable.AndroidManifestActivity_banner,
102                             R.styleable.AndroidManifestActivity_description,
103                             R.styleable.AndroidManifestActivity_directBootAware,
104                             R.styleable.AndroidManifestActivity_enabled,
105                             R.styleable.AndroidManifestActivity_icon,
106                             R.styleable.AndroidManifestActivity_label,
107                             R.styleable.AndroidManifestActivity_logo,
108                             R.styleable.AndroidManifestActivity_name,
109                             R.styleable.AndroidManifestActivity_process,
110                             R.styleable.AndroidManifestActivity_roundIcon,
111                             R.styleable.AndroidManifestActivity_splitName,
112                             R.styleable.AndroidManifestActivity_attributionTags);
113             if (result.isError()) {
114                 return input.error(result);
115             }
116 
117             if (receiver && pkg.isSaveStateDisallowed()) {
118                 // A heavy-weight application can not have receivers in its main process
119                 if (Objects.equals(activity.getProcessName(), packageName)) {
120                     return input.error("Heavy-weight applications can not have receivers "
121                             + "in main process");
122                 }
123             }
124 
125             // The following section has formatting off to make it easier to read the flags.
126             // Multi-lining them to fit within the column restriction makes it hard to tell what
127             // field is assigned where.
128             // @formatter:off
129             activity.setTheme(sa.getResourceId(R.styleable.AndroidManifestActivity_theme, 0))
130                     .setUiOptions(sa.getInt(R.styleable.AndroidManifestActivity_uiOptions, pkg.getUiOptions()));
131 
132             activity.setFlags(activity.getFlags() | (flag(ActivityInfo.FLAG_ALLOW_TASK_REPARENTING, R.styleable.AndroidManifestActivity_allowTaskReparenting, pkg.isTaskReparentingAllowed(), sa)
133                                 | flag(ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE, R.styleable.AndroidManifestActivity_alwaysRetainTaskState, sa)
134                                 | flag(ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH, R.styleable.AndroidManifestActivity_clearTaskOnLaunch, sa)
135                                 | flag(ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS, R.styleable.AndroidManifestActivity_excludeFromRecents, sa)
136                                 | flag(ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS, R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs, sa)
137                                 | flag(ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH, R.styleable.AndroidManifestActivity_finishOnTaskLaunch, sa)
138                                 | flag(ActivityInfo.FLAG_IMMERSIVE, R.styleable.AndroidManifestActivity_immersive, sa)
139                                 | flag(ActivityInfo.FLAG_MULTIPROCESS, R.styleable.AndroidManifestActivity_multiprocess, sa)
140                                 | flag(ActivityInfo.FLAG_NO_HISTORY, R.styleable.AndroidManifestActivity_noHistory, sa)
141                                 | flag(ActivityInfo.FLAG_SHOW_FOR_ALL_USERS, R.styleable.AndroidManifestActivity_showForAllUsers, sa)
142                                 | flag(ActivityInfo.FLAG_SHOW_FOR_ALL_USERS, R.styleable.AndroidManifestActivity_showOnLockScreen, sa)
143                                 | flag(ActivityInfo.FLAG_STATE_NOT_NEEDED, R.styleable.AndroidManifestActivity_stateNotNeeded, sa)
144                                 | flag(ActivityInfo.FLAG_SYSTEM_USER_ONLY, R.styleable.AndroidManifestActivity_systemUserOnly, sa)));
145 
146             if (!receiver) {
147                 activity.setFlags(activity.getFlags() | (flag(ActivityInfo.FLAG_HARDWARE_ACCELERATED, R.styleable.AndroidManifestActivity_hardwareAccelerated, pkg.isHardwareAccelerated(), sa)
148                                         | flag(ActivityInfo.FLAG_ALLOW_EMBEDDED, R.styleable.AndroidManifestActivity_allowEmbedded, sa)
149                                         | flag(ActivityInfo.FLAG_ALWAYS_FOCUSABLE, R.styleable.AndroidManifestActivity_alwaysFocusable, sa)
150                                         | flag(ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS, R.styleable.AndroidManifestActivity_autoRemoveFromRecents, sa)
151                                         | flag(ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY, R.styleable.AndroidManifestActivity_relinquishTaskIdentity, sa)
152                                         | flag(ActivityInfo.FLAG_RESUME_WHILE_PAUSING, R.styleable.AndroidManifestActivity_resumeWhilePausing, sa)
153                                         | flag(ActivityInfo.FLAG_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_showWhenLocked, sa)
154                                         | flag(ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE, R.styleable.AndroidManifestActivity_supportsPictureInPicture, sa)
155                                         | flag(ActivityInfo.FLAG_TURN_SCREEN_ON, R.styleable.AndroidManifestActivity_turnScreenOn, sa)
156                                         | flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa))
157                                         | flag(ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING, R.styleable.AndroidManifestActivity_allowUntrustedActivityEmbedding, sa));
158 
159                 activity.setPrivateFlags(activity.getPrivateFlags() | (flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED,
160                                         R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa)
161                                         | flag(ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND,
162                                         R.styleable.AndroidManifestActivity_playHomeTransitionSound, true, sa)));
163 
164                 activity.setColorMode(sa.getInt(R.styleable.AndroidManifestActivity_colorMode, ActivityInfo.COLOR_MODE_DEFAULT))
165                         .setDocumentLaunchMode(sa.getInt(R.styleable.AndroidManifestActivity_documentLaunchMode, ActivityInfo.DOCUMENT_LAUNCH_NONE))
166                         .setLaunchMode(sa.getInt(R.styleable.AndroidManifestActivity_launchMode, ActivityInfo.LAUNCH_MULTIPLE))
167                         .setLockTaskLaunchMode(sa.getInt(R.styleable.AndroidManifestActivity_lockTaskMode, 0))
168                         .setMaxRecents(sa.getInt(R.styleable.AndroidManifestActivity_maxRecents, ActivityTaskManager.getDefaultAppRecentsLimitStatic()))
169                         .setPersistableMode(sa.getInteger(R.styleable.AndroidManifestActivity_persistableMode, ActivityInfo.PERSIST_ROOT_ONLY))
170                         .setRequestedVrComponent(sa.getString(R.styleable.AndroidManifestActivity_enableVrMode))
171                         .setRotationAnimation(sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED))
172                         .setSoftInputMode(sa.getInt(R.styleable.AndroidManifestActivity_windowSoftInputMode, 0))
173                         .setConfigChanges(getActivityConfigChanges(
174                                 sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0),
175                                 sa.getInt(R.styleable.AndroidManifestActivity_recreateOnConfigChanges, 0))
176                         );
177 
178                 int screenOrientation = sa.getInt(R.styleable.AndroidManifestActivity_screenOrientation, SCREEN_ORIENTATION_UNSPECIFIED);
179                 int resizeMode = getActivityResizeMode(pkg, sa, screenOrientation);
180                 activity.setScreenOrientation(screenOrientation)
181                         .setResizeMode(resizeMode);
182 
183                 if (sa.hasValue(R.styleable.AndroidManifestActivity_maxAspectRatio)
184                         && sa.getType(R.styleable.AndroidManifestActivity_maxAspectRatio)
185                         == TypedValue.TYPE_FLOAT) {
186                     activity.setMaxAspectRatio(resizeMode,
187                             sa.getFloat(R.styleable.AndroidManifestActivity_maxAspectRatio,
188                                     0 /*default*/));
189                 }
190 
191                 if (sa.hasValue(R.styleable.AndroidManifestActivity_minAspectRatio)
192                         && sa.getType(R.styleable.AndroidManifestActivity_minAspectRatio)
193                         == TypedValue.TYPE_FLOAT) {
194                     activity.setMinAspectRatio(resizeMode,
195                             sa.getFloat(R.styleable.AndroidManifestActivity_minAspectRatio,
196                                     0 /*default*/));
197                 }
198 
199                 if (sa.hasValue(R.styleable.AndroidManifestActivity_enableOnBackInvokedCallback)) {
200                     boolean enable = sa.getBoolean(
201                             R.styleable.AndroidManifestActivity_enableOnBackInvokedCallback,
202                             false);
203                     activity.setPrivateFlags(activity.getPrivateFlags()
204                             | (enable ? ActivityInfo.PRIVATE_FLAG_ENABLE_ON_BACK_INVOKED_CALLBACK
205                                     : ActivityInfo.PRIVATE_FLAG_DISABLE_ON_BACK_INVOKED_CALLBACK));
206                 }
207             } else {
208                 activity.setLaunchMode(ActivityInfo.LAUNCH_MULTIPLE)
209                         .setConfigChanges(0)
210                         .setFlags(activity.getFlags()|flag(ActivityInfo.FLAG_SINGLE_USER, R.styleable.AndroidManifestActivity_singleUser, sa));
211             }
212             // @formatter:on
213 
214             String taskAffinity = sa.getNonConfigurationString(
215                     R.styleable.AndroidManifestActivity_taskAffinity,
216                     Configuration.NATIVE_CONFIG_VERSION);
217 
218             ParseResult<String> affinityNameResult = ComponentParseUtils.buildTaskAffinityName(
219                     packageName, pkg.getTaskAffinity(), taskAffinity, input);
220             if (affinityNameResult.isError()) {
221                 return input.error(affinityNameResult);
222             }
223 
224             activity.setTaskAffinity(affinityNameResult.getResult());
225 
226             boolean visibleToEphemeral = sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToInstantApps, false);
227             if (visibleToEphemeral) {
228                 activity.setFlags(activity.getFlags() | ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP);
229                 pkg.setVisibleToInstantApps(true);
230             }
231 
232             String requiredDisplayCategory = sa.getNonConfigurationString(
233                     R.styleable.AndroidManifestActivity_requiredDisplayCategory, 0);
234 
235             if (requiredDisplayCategory != null
236                     && FrameworkParsingPackageUtils.validateName(requiredDisplayCategory,
237                     false /* requireSeparator */, false /* requireFilename */) != null) {
238                 return input.error("requiredDisplayCategory attribute can only consist "
239                         + "of alphanumeric characters, '_', and '.'");
240             }
241 
242             activity.setRequiredDisplayCategory(requiredDisplayCategory);
243 
244             activity.setRequireContentUriPermissionFromCaller(sa.getInt(
245                     R.styleable.AndroidManifestActivity_requireContentUriPermissionFromCaller,
246                     ActivityInfo.CONTENT_URI_PERMISSION_NONE));
247 
248             return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver,
249                     false /*isAlias*/, visibleToEphemeral, input,
250                     R.styleable.AndroidManifestActivity_parentActivityName,
251                     R.styleable.AndroidManifestActivity_permission,
252                     R.styleable.AndroidManifestActivity_exported
253             );
254         } finally {
255             sa.recycle();
256         }
257     }
258 
259     @NonNull
parseActivityAlias(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, @Nullable String defaultSplitName, @NonNull ParseInput input)260     public static ParseResult<ParsedActivity> parseActivityAlias(ParsingPackage pkg, Resources res,
261             XmlResourceParser parser, boolean useRoundIcon, @Nullable String defaultSplitName,
262             @NonNull ParseInput input) throws XmlPullParserException, IOException {
263         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivityAlias);
264         try {
265             String targetActivity = sa.getNonConfigurationString(
266                     R.styleable.AndroidManifestActivityAlias_targetActivity,
267                     Configuration.NATIVE_CONFIG_VERSION);
268             if (targetActivity == null) {
269                 return input.error("<activity-alias> does not specify android:targetActivity");
270             }
271 
272             String packageName = pkg.getPackageName();
273             targetActivity = ParsingUtils.buildClassName(packageName, targetActivity);
274             if (targetActivity == null) {
275                 return input.error("Empty class name in package " + packageName);
276             }
277 
278             ParsedActivity target = null;
279 
280             List<ParsedActivity> activities = pkg.getActivities();
281             final int activitiesSize = ArrayUtils.size(activities);
282             for (int i = 0; i < activitiesSize; i++) {
283                 ParsedActivity t = activities.get(i);
284                 if (targetActivity.equals(t.getName())) {
285                     target = t;
286                     break;
287                 }
288             }
289 
290             if (target == null) {
291                 return input.error("<activity-alias> target activity " + targetActivity
292                         + " not found in manifest with activities = "
293                         + pkg.getActivities()
294                         + ", parsedActivities = " + activities);
295             }
296 
297             ParsedActivityImpl activity = ParsedActivityImpl.makeAlias(targetActivity, target);
298             String tag = "<" + parser.getName() + ">";
299 
300             ParseResult<ParsedActivityImpl> result = ParsedMainComponentUtils.parseMainComponent(
301                     activity, tag, null, pkg, sa, 0, useRoundIcon, defaultSplitName, input,
302                     R.styleable.AndroidManifestActivityAlias_banner,
303                     R.styleable.AndroidManifestActivityAlias_description,
304                     NOT_SET /*directBootAwareAttr*/,
305                     R.styleable.AndroidManifestActivityAlias_enabled,
306                     R.styleable.AndroidManifestActivityAlias_icon,
307                     R.styleable.AndroidManifestActivityAlias_label,
308                     R.styleable.AndroidManifestActivityAlias_logo,
309                     R.styleable.AndroidManifestActivityAlias_name,
310                     NOT_SET /*processAttr*/,
311                     R.styleable.AndroidManifestActivityAlias_roundIcon,
312                     NOT_SET /*splitNameAttr*/,
313                     R.styleable.AndroidManifestActivityAlias_attributionTags);
314             if (result.isError()) {
315                 return input.error(result);
316             }
317 
318             // TODO add visibleToInstantApps attribute to activity alias
319             final boolean visibleToEphemeral =
320                     ((activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0);
321 
322             return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, false /*isReceiver*/, true /*isAlias*/,
323                     visibleToEphemeral, input,
324                     R.styleable.AndroidManifestActivityAlias_parentActivityName,
325                     R.styleable.AndroidManifestActivityAlias_permission,
326                     R.styleable.AndroidManifestActivityAlias_exported);
327         } finally {
328             sa.recycle();
329         }
330     }
331 
332     /**
333      * This method shares parsing logic between Activity/Receiver/alias instances, but requires
334      * passing in booleans for isReceiver/isAlias, since there's no indicator in the other
335      * parameters.
336      *
337      * They're used to filter the parsed tags and their behavior. This makes the method rather
338      * messy, but it's more maintainable than writing 3 separate methods for essentially the same
339      * type of logic.
340      */
341     @NonNull
parseActivityOrAlias(ParsedActivityImpl activity, ParsingPackage pkg, String tag, XmlResourceParser parser, Resources resources, TypedArray array, boolean isReceiver, boolean isAlias, boolean visibleToEphemeral, ParseInput input, int parentActivityNameAttr, int permissionAttr, int exportedAttr)342     private static ParseResult<ParsedActivity> parseActivityOrAlias(ParsedActivityImpl activity,
343             ParsingPackage pkg, String tag, XmlResourceParser parser, Resources resources,
344             TypedArray array, boolean isReceiver, boolean isAlias, boolean visibleToEphemeral,
345             ParseInput input, int parentActivityNameAttr, int permissionAttr,
346             int exportedAttr) throws IOException, XmlPullParserException {
347         String parentActivityName = array.getNonConfigurationString(parentActivityNameAttr, Configuration.NATIVE_CONFIG_VERSION);
348         if (parentActivityName != null) {
349             String packageName = pkg.getPackageName();
350             String parentClassName = ParsingUtils.buildClassName(packageName, parentActivityName);
351             if (parentClassName == null) {
352                 Log.e(TAG, "Activity " + activity.getName()
353                         + " specified invalid parentActivityName " + parentActivityName);
354             } else {
355                 activity.setParentActivityName(parentClassName);
356             }
357         }
358 
359         String permission = array.getNonConfigurationString(permissionAttr, 0);
360         if (isAlias) {
361             // An alias will override permissions to allow referencing an Activity through its alias
362             // without needing the original permission. If an alias needs the same permission,
363             // it must be re-declared.
364             activity.setPermission(permission);
365         } else {
366             activity.setPermission(permission != null ? permission : pkg.getPermission());
367         }
368 
369         final ParseResult<Set<String>> knownActivityEmbeddingCertsResult =
370                 parseKnownActivityEmbeddingCerts(array, resources, isAlias
371                         ? R.styleable.AndroidManifestActivityAlias_knownActivityEmbeddingCerts
372                         : R.styleable.AndroidManifestActivity_knownActivityEmbeddingCerts, input);
373         if (knownActivityEmbeddingCertsResult.isError()) {
374             return input.error(knownActivityEmbeddingCertsResult);
375         } else {
376             final Set<String> knownActivityEmbeddingCerts = knownActivityEmbeddingCertsResult
377                     .getResult();
378             if (knownActivityEmbeddingCerts != null) {
379                 activity.setKnownActivityEmbeddingCerts(knownActivityEmbeddingCerts);
380             }
381         }
382 
383         final boolean setExported = array.hasValue(exportedAttr);
384         if (setExported) {
385             activity.setExported(array.getBoolean(exportedAttr, false));
386         }
387 
388         final int depth = parser.getDepth();
389         int type;
390         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
391                 && (type != XmlPullParser.END_TAG
392                 || parser.getDepth() > depth)) {
393             if (type != XmlPullParser.START_TAG) {
394                 continue;
395             }
396             if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
397                 continue;
398             }
399 
400             final ParseResult result;
401             if (parser.getName().equals("intent-filter")) {
402                 ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
403                         !isReceiver, visibleToEphemeral, resources, parser, input);
404                 if (intentResult.isSuccess()) {
405                     ParsedIntentInfoImpl intentInfo = intentResult.getResult();
406                     if (intentInfo != null) {
407                         IntentFilter intentFilter = intentInfo.getIntentFilter();
408                         activity.setOrder(Math.max(intentFilter.getOrder(), activity.getOrder()));
409                         activity.addIntent(intentInfo);
410                         if (LOG_UNSAFE_BROADCASTS && isReceiver
411                                 && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
412                             int actionCount = intentFilter.countActions();
413                             for (int i = 0; i < actionCount; i++) {
414                                 final String action = intentFilter.getAction(i);
415                                 if (action == null || !action.startsWith("android.")) {
416                                     continue;
417                                 }
418 
419                                 if (!SAFE_BROADCASTS.contains(action)) {
420                                     Slog.w(TAG,
421                                             "Broadcast " + action + " may never be delivered to "
422                                                     + pkg.getPackageName() + " as requested at: "
423                                                     + parser.getPositionDescription());
424                                 }
425                             }
426                         }
427                     }
428                 }
429                 result = intentResult;
430             } else if (parser.getName().equals("meta-data")) {
431                 result = ParsedComponentUtils.addMetaData(activity, pkg, resources, parser, input);
432             } else if (parser.getName().equals("property")) {
433                 result = ParsedComponentUtils.addProperty(activity, pkg, resources, parser, input);
434             } else if (!isReceiver && !isAlias && parser.getName().equals("preferred")) {
435                 ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
436                         true /*allowImplicitEphemeralVisibility*/, visibleToEphemeral,
437                         resources, parser, input);
438                 if (intentResult.isSuccess()) {
439                     ParsedIntentInfoImpl intent = intentResult.getResult();
440                     if (intent != null) {
441                         pkg.addPreferredActivityFilter(activity.getClassName(), intent);
442                     }
443                 }
444                 result = intentResult;
445             } else if (!isReceiver && !isAlias && parser.getName().equals("layout")) {
446                 ParseResult<ActivityInfo.WindowLayout> layoutResult =
447                         parseActivityWindowLayout(resources, parser, input);
448                 if (layoutResult.isSuccess()) {
449                     activity.setWindowLayout(layoutResult.getResult());
450                 }
451                 result = layoutResult;
452             } else {
453                 result = ParsingUtils.unknownTag(tag, pkg, parser, input);
454             }
455 
456             if (result.isError()) {
457                 return input.error(result);
458             }
459         }
460 
461         if (!isAlias && activity.getLaunchMode() != LAUNCH_SINGLE_INSTANCE_PER_TASK
462                 && activity.getMetaData().containsKey(
463                 ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE)) {
464             final String launchMode = activity.getMetaData().getString(
465                     ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE);
466             if (launchMode != null && launchMode.equals("singleInstancePerTask")) {
467                 activity.setLaunchMode(LAUNCH_SINGLE_INSTANCE_PER_TASK);
468             }
469         }
470 
471         if (!isAlias) {
472             // Default allow the activity to be displayed on a remote device unless it explicitly
473             // set to false.
474             boolean canDisplayOnRemoteDevices = array.getBoolean(
475                     R.styleable.AndroidManifestActivity_canDisplayOnRemoteDevices, true);
476             if (!activity.getMetaData().getBoolean(
477                     ParsingPackageUtils.METADATA_CAN_DISPLAY_ON_REMOTE_DEVICES, true)) {
478                 canDisplayOnRemoteDevices = false;
479             }
480             if (canDisplayOnRemoteDevices) {
481                 activity.setFlags(activity.getFlags()
482                         | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES);
483             }
484         }
485 
486         ParseResult<ActivityInfo.WindowLayout> layoutResult =
487                 resolveActivityWindowLayout(activity, input);
488         if (layoutResult.isError()) {
489             return input.error(layoutResult);
490         }
491         activity.setWindowLayout(layoutResult.getResult());
492 
493         if (!setExported) {
494             boolean hasIntentFilters = activity.getIntents().size() > 0;
495             if (hasIntentFilters) {
496                 final ParseResult exportedCheckResult = input.deferError(
497                         activity.getName() + ": Targeting S+ (version " + Build.VERSION_CODES.S
498                         + " and above) requires that an explicit value for android:exported be"
499                         + " defined when intent filters are present",
500                         DeferredError.MISSING_EXPORTED_FLAG);
501                 if (exportedCheckResult.isError()) {
502                     return input.error(exportedCheckResult);
503                 }
504             }
505             activity.setExported(hasIntentFilters);
506         }
507 
508         return input.success(activity);
509     }
510 
511     @NonNull
parseIntentFilter(ParsingPackage pkg, ParsedActivityImpl activity, boolean allowImplicitEphemeralVisibility, boolean visibleToEphemeral, Resources resources, XmlResourceParser parser, ParseInput input)512     private static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(ParsingPackage pkg,
513             ParsedActivityImpl activity, boolean allowImplicitEphemeralVisibility,
514             boolean visibleToEphemeral, Resources resources, XmlResourceParser parser,
515             ParseInput input) throws IOException, XmlPullParserException {
516         ParseResult<ParsedIntentInfoImpl> result = ParsedMainComponentUtils.parseIntentFilter(activity,
517                 pkg, resources, parser, visibleToEphemeral, true /*allowGlobs*/,
518                 true /*allowAutoVerify*/, allowImplicitEphemeralVisibility,
519                 true /*failOnNoActions*/, input);
520         if (result.isError()) {
521             return input.error(result);
522         }
523 
524         ParsedIntentInfoImpl intent = result.getResult();
525         if (intent != null) {
526             final IntentFilter intentFilter = intent.getIntentFilter();
527             if (intentFilter.isVisibleToInstantApp()) {
528                 activity.setFlags(activity.getFlags() | ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP);
529             }
530             if (intentFilter.isImplicitlyVisibleToInstantApp()) {
531                 activity.setFlags(
532                         activity.getFlags() | ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP);
533             }
534         }
535 
536         return input.success(intent);
537     }
538 
getActivityResizeMode(ParsingPackage pkg, TypedArray sa, int screenOrientation)539     private static int getActivityResizeMode(ParsingPackage pkg, TypedArray sa,
540             int screenOrientation) {
541         Boolean resizeableActivity = pkg.getResizeableActivity();
542 
543         if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity)
544                 || resizeableActivity != null) {
545             // Activity or app explicitly set if it is resizeable or not;
546             if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity,
547                     resizeableActivity != null && resizeableActivity)) {
548                 return ActivityInfo.RESIZE_MODE_RESIZEABLE;
549             } else {
550                 return ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
551             }
552         }
553 
554         if (pkg.isResizeableActivityViaSdkVersion()) {
555             // The activity or app didn't explicitly set the resizing option, however we want to
556             // make it resize due to the sdk version it is targeting.
557             return ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
558         }
559 
560         // resize preference isn't set and target sdk version doesn't support resizing apps by
561         // default. For the app to be resizeable if it isn't fixed orientation or immersive.
562         if (ActivityInfo.isFixedOrientationPortrait(screenOrientation)) {
563             return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
564         } else if (ActivityInfo.isFixedOrientationLandscape(screenOrientation)) {
565             return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
566         } else if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
567             return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
568         } else {
569             return ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
570         }
571     }
572 
573     @NonNull
parseActivityWindowLayout(Resources res, AttributeSet attrs, ParseInput input)574     private static ParseResult<ActivityInfo.WindowLayout> parseActivityWindowLayout(Resources res,
575             AttributeSet attrs, ParseInput input) {
576         TypedArray sw = res.obtainAttributes(attrs, R.styleable.AndroidManifestLayout);
577         try {
578             int width = -1;
579             float widthFraction = -1f;
580             int height = -1;
581             float heightFraction = -1f;
582             final int widthType = sw.getType(R.styleable.AndroidManifestLayout_defaultWidth);
583             if (widthType == TypedValue.TYPE_FRACTION) {
584                 widthFraction = sw.getFraction(R.styleable.AndroidManifestLayout_defaultWidth, 1, 1,
585                         -1);
586             } else if (widthType == TypedValue.TYPE_DIMENSION) {
587                 width = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_defaultWidth,
588                         -1);
589             }
590             final int heightType = sw.getType(R.styleable.AndroidManifestLayout_defaultHeight);
591             if (heightType == TypedValue.TYPE_FRACTION) {
592                 heightFraction = sw.getFraction(R.styleable.AndroidManifestLayout_defaultHeight, 1,
593                         1, -1);
594             } else if (heightType == TypedValue.TYPE_DIMENSION) {
595                 height = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_defaultHeight,
596                         -1);
597             }
598             int gravity = sw.getInt(R.styleable.AndroidManifestLayout_gravity, Gravity.CENTER);
599             int minWidth = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_minWidth, -1);
600             int minHeight = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_minHeight,
601                     -1);
602             String windowLayoutAffinity =
603                     sw.getNonConfigurationString(
604                             R.styleable.AndroidManifestLayout_windowLayoutAffinity, 0);
605             final ActivityInfo.WindowLayout windowLayout = new ActivityInfo.WindowLayout(width,
606                     widthFraction, height, heightFraction, gravity, minWidth, minHeight,
607                     windowLayoutAffinity);
608             return input.success(windowLayout);
609         } finally {
610             sw.recycle();
611         }
612     }
613 
614     /**
615      * Resolves values in {@link ActivityInfo.WindowLayout}.
616      *
617      * <p>{@link ActivityInfo.WindowLayout#windowLayoutAffinity} has a fallback metadata used in
618      * Android R and some variants of pre-R.
619      */
resolveActivityWindowLayout( ParsedActivity activity, ParseInput input)620     private static ParseResult<ActivityInfo.WindowLayout> resolveActivityWindowLayout(
621             ParsedActivity activity, ParseInput input) {
622         // There isn't a metadata for us to fall back. Whatever is in layout is correct.
623         if (!activity.getMetaData().containsKey(
624                 ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
625             return input.success(activity.getWindowLayout());
626         }
627 
628         // Layout already specifies a value. We should just use that one.
629         if (activity.getWindowLayout() != null && activity.getWindowLayout().windowLayoutAffinity != null) {
630             return input.success(activity.getWindowLayout());
631         }
632 
633         String windowLayoutAffinity = activity.getMetaData().getString(
634                 ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY);
635         ActivityInfo.WindowLayout layout = activity.getWindowLayout();
636         if (layout == null) {
637             layout = new ActivityInfo.WindowLayout(-1 /* width */, -1 /* widthFraction */,
638                     -1 /* height */, -1 /* heightFraction */, Gravity.NO_GRAVITY,
639                     -1 /* minWidth */, -1 /* minHeight */, windowLayoutAffinity);
640         } else {
641             layout.windowLayoutAffinity = windowLayoutAffinity;
642         }
643         return input.success(layout);
644     }
645 
646     /**
647      * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
648      * @param recreateOnConfigChanges The bit mask recreateOnConfigChanges fetched from
649      *                                AndroidManifest.xml.
650      * @hide
651      */
getActivityConfigChanges(int configChanges, int recreateOnConfigChanges)652     public static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) {
653         return configChanges | ((~recreateOnConfigChanges) & RECREATE_ON_CONFIG_CHANGES_MASK);
654     }
655 }
656