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