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.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.util.ArrayUtils;
52 import com.android.server.pm.pkg.parsing.ParsingPackage;
53 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
54 import com.android.server.pm.pkg.parsing.ParsingUtils;
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             return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver,
245                     false /*isAlias*/, visibleToEphemeral, input,
246                     R.styleable.AndroidManifestActivity_parentActivityName,
247                     R.styleable.AndroidManifestActivity_permission,
248                     R.styleable.AndroidManifestActivity_exported
249             );
250         } finally {
251             sa.recycle();
252         }
253     }
254 
255     @NonNull
parseActivityAlias(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, @Nullable String defaultSplitName, @NonNull ParseInput input)256     public static ParseResult<ParsedActivity> parseActivityAlias(ParsingPackage pkg, Resources res,
257             XmlResourceParser parser, boolean useRoundIcon, @Nullable String defaultSplitName,
258             @NonNull ParseInput input) throws XmlPullParserException, IOException {
259         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestActivityAlias);
260         try {
261             String targetActivity = sa.getNonConfigurationString(
262                     R.styleable.AndroidManifestActivityAlias_targetActivity,
263                     Configuration.NATIVE_CONFIG_VERSION);
264             if (targetActivity == null) {
265                 return input.error("<activity-alias> does not specify android:targetActivity");
266             }
267 
268             String packageName = pkg.getPackageName();
269             targetActivity = ParsingUtils.buildClassName(packageName, targetActivity);
270             if (targetActivity == null) {
271                 return input.error("Empty class name in package " + packageName);
272             }
273 
274             ParsedActivity target = null;
275 
276             List<ParsedActivity> activities = pkg.getActivities();
277             final int activitiesSize = ArrayUtils.size(activities);
278             for (int i = 0; i < activitiesSize; i++) {
279                 ParsedActivity t = activities.get(i);
280                 if (targetActivity.equals(t.getName())) {
281                     target = t;
282                     break;
283                 }
284             }
285 
286             if (target == null) {
287                 return input.error("<activity-alias> target activity " + targetActivity
288                         + " not found in manifest with activities = "
289                         + pkg.getActivities()
290                         + ", parsedActivities = " + activities);
291             }
292 
293             ParsedActivityImpl activity = ParsedActivityImpl.makeAlias(targetActivity, target);
294             String tag = "<" + parser.getName() + ">";
295 
296             ParseResult<ParsedActivityImpl> result = ParsedMainComponentUtils.parseMainComponent(
297                     activity, tag, null, pkg, sa, 0, useRoundIcon, defaultSplitName, input,
298                     R.styleable.AndroidManifestActivityAlias_banner,
299                     R.styleable.AndroidManifestActivityAlias_description,
300                     NOT_SET /*directBootAwareAttr*/,
301                     R.styleable.AndroidManifestActivityAlias_enabled,
302                     R.styleable.AndroidManifestActivityAlias_icon,
303                     R.styleable.AndroidManifestActivityAlias_label,
304                     R.styleable.AndroidManifestActivityAlias_logo,
305                     R.styleable.AndroidManifestActivityAlias_name,
306                     NOT_SET /*processAttr*/,
307                     R.styleable.AndroidManifestActivityAlias_roundIcon,
308                     NOT_SET /*splitNameAttr*/,
309                     R.styleable.AndroidManifestActivityAlias_attributionTags);
310             if (result.isError()) {
311                 return input.error(result);
312             }
313 
314             // TODO add visibleToInstantApps attribute to activity alias
315             final boolean visibleToEphemeral =
316                     ((activity.getFlags() & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0);
317 
318             return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, false /*isReceiver*/, true /*isAlias*/,
319                     visibleToEphemeral, input,
320                     R.styleable.AndroidManifestActivityAlias_parentActivityName,
321                     R.styleable.AndroidManifestActivityAlias_permission,
322                     R.styleable.AndroidManifestActivityAlias_exported);
323         } finally {
324             sa.recycle();
325         }
326     }
327 
328     /**
329      * This method shares parsing logic between Activity/Receiver/alias instances, but requires
330      * passing in booleans for isReceiver/isAlias, since there's no indicator in the other
331      * parameters.
332      *
333      * They're used to filter the parsed tags and their behavior. This makes the method rather
334      * messy, but it's more maintainable than writing 3 separate methods for essentially the same
335      * type of logic.
336      */
337     @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)338     private static ParseResult<ParsedActivity> parseActivityOrAlias(ParsedActivityImpl activity,
339             ParsingPackage pkg, String tag, XmlResourceParser parser, Resources resources,
340             TypedArray array, boolean isReceiver, boolean isAlias, boolean visibleToEphemeral,
341             ParseInput input, int parentActivityNameAttr, int permissionAttr,
342             int exportedAttr) throws IOException, XmlPullParserException {
343         String parentActivityName = array.getNonConfigurationString(parentActivityNameAttr, Configuration.NATIVE_CONFIG_VERSION);
344         if (parentActivityName != null) {
345             String packageName = pkg.getPackageName();
346             String parentClassName = ParsingUtils.buildClassName(packageName, parentActivityName);
347             if (parentClassName == null) {
348                 Log.e(TAG, "Activity " + activity.getName()
349                         + " specified invalid parentActivityName " + parentActivityName);
350             } else {
351                 activity.setParentActivityName(parentClassName);
352             }
353         }
354 
355         String permission = array.getNonConfigurationString(permissionAttr, 0);
356         if (isAlias) {
357             // An alias will override permissions to allow referencing an Activity through its alias
358             // without needing the original permission. If an alias needs the same permission,
359             // it must be re-declared.
360             activity.setPermission(permission);
361         } else {
362             activity.setPermission(permission != null ? permission : pkg.getPermission());
363         }
364 
365         final ParseResult<Set<String>> knownActivityEmbeddingCertsResult =
366                 parseKnownActivityEmbeddingCerts(array, resources, isAlias
367                         ? R.styleable.AndroidManifestActivityAlias_knownActivityEmbeddingCerts
368                         : R.styleable.AndroidManifestActivity_knownActivityEmbeddingCerts, input);
369         if (knownActivityEmbeddingCertsResult.isError()) {
370             return input.error(knownActivityEmbeddingCertsResult);
371         } else {
372             final Set<String> knownActivityEmbeddingCerts = knownActivityEmbeddingCertsResult
373                     .getResult();
374             if (knownActivityEmbeddingCerts != null) {
375                 activity.setKnownActivityEmbeddingCerts(knownActivityEmbeddingCerts);
376             }
377         }
378 
379         final boolean setExported = array.hasValue(exportedAttr);
380         if (setExported) {
381             activity.setExported(array.getBoolean(exportedAttr, false));
382         }
383 
384         final int depth = parser.getDepth();
385         int type;
386         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
387                 && (type != XmlPullParser.END_TAG
388                 || parser.getDepth() > depth)) {
389             if (type != XmlPullParser.START_TAG) {
390                 continue;
391             }
392 
393             final ParseResult result;
394             if (parser.getName().equals("intent-filter")) {
395                 ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
396                         !isReceiver, visibleToEphemeral, resources, parser, input);
397                 if (intentResult.isSuccess()) {
398                     ParsedIntentInfoImpl intentInfo = intentResult.getResult();
399                     if (intentInfo != null) {
400                         IntentFilter intentFilter = intentInfo.getIntentFilter();
401                         activity.setOrder(Math.max(intentFilter.getOrder(), activity.getOrder()));
402                         activity.addIntent(intentInfo);
403                         if (LOG_UNSAFE_BROADCASTS && isReceiver
404                                 && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
405                             int actionCount = intentFilter.countActions();
406                             for (int i = 0; i < actionCount; i++) {
407                                 final String action = intentFilter.getAction(i);
408                                 if (action == null || !action.startsWith("android.")) {
409                                     continue;
410                                 }
411 
412                                 if (!SAFE_BROADCASTS.contains(action)) {
413                                     Slog.w(TAG,
414                                             "Broadcast " + action + " may never be delivered to "
415                                                     + pkg.getPackageName() + " as requested at: "
416                                                     + parser.getPositionDescription());
417                                 }
418                             }
419                         }
420                     }
421                 }
422                 result = intentResult;
423             } else if (parser.getName().equals("meta-data")) {
424                 result = ParsedComponentUtils.addMetaData(activity, pkg, resources, parser, input);
425             } else if (parser.getName().equals("property")) {
426                 result = ParsedComponentUtils.addProperty(activity, pkg, resources, parser, input);
427             } else if (!isReceiver && !isAlias && parser.getName().equals("preferred")) {
428                 ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
429                         true /*allowImplicitEphemeralVisibility*/, visibleToEphemeral,
430                         resources, parser, input);
431                 if (intentResult.isSuccess()) {
432                     ParsedIntentInfoImpl intent = intentResult.getResult();
433                     if (intent != null) {
434                         pkg.addPreferredActivityFilter(activity.getClassName(), intent);
435                     }
436                 }
437                 result = intentResult;
438             } else if (!isReceiver && !isAlias && parser.getName().equals("layout")) {
439                 ParseResult<ActivityInfo.WindowLayout> layoutResult =
440                         parseActivityWindowLayout(resources, parser, input);
441                 if (layoutResult.isSuccess()) {
442                     activity.setWindowLayout(layoutResult.getResult());
443                 }
444                 result = layoutResult;
445             } else {
446                 result = ParsingUtils.unknownTag(tag, pkg, parser, input);
447             }
448 
449             if (result.isError()) {
450                 return input.error(result);
451             }
452         }
453 
454         if (!isAlias && activity.getLaunchMode() != LAUNCH_SINGLE_INSTANCE_PER_TASK
455                 && activity.getMetaData().containsKey(
456                 ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE)) {
457             final String launchMode = activity.getMetaData().getString(
458                     ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE);
459             if (launchMode != null && launchMode.equals("singleInstancePerTask")) {
460                 activity.setLaunchMode(LAUNCH_SINGLE_INSTANCE_PER_TASK);
461             }
462         }
463 
464         if (!isAlias) {
465             // Default allow the activity to be displayed on a remote device unless it explicitly
466             // set to false.
467             boolean canDisplayOnRemoteDevices = array.getBoolean(
468                     R.styleable.AndroidManifestActivity_canDisplayOnRemoteDevices, true);
469             if (!activity.getMetaData().getBoolean(
470                     ParsingPackageUtils.METADATA_CAN_DISPLAY_ON_REMOTE_DEVICES, true)) {
471                 canDisplayOnRemoteDevices = false;
472             }
473             if (canDisplayOnRemoteDevices) {
474                 activity.setFlags(activity.getFlags()
475                         | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES);
476             }
477         }
478 
479         ParseResult<ActivityInfo.WindowLayout> layoutResult =
480                 resolveActivityWindowLayout(activity, input);
481         if (layoutResult.isError()) {
482             return input.error(layoutResult);
483         }
484         activity.setWindowLayout(layoutResult.getResult());
485 
486         if (!setExported) {
487             boolean hasIntentFilters = activity.getIntents().size() > 0;
488             if (hasIntentFilters) {
489                 final ParseResult exportedCheckResult = input.deferError(
490                         activity.getName() + ": Targeting S+ (version " + Build.VERSION_CODES.S
491                         + " and above) requires that an explicit value for android:exported be"
492                         + " defined when intent filters are present",
493                         DeferredError.MISSING_EXPORTED_FLAG);
494                 if (exportedCheckResult.isError()) {
495                     return input.error(exportedCheckResult);
496                 }
497             }
498             activity.setExported(hasIntentFilters);
499         }
500 
501         return input.success(activity);
502     }
503 
504     @NonNull
parseIntentFilter(ParsingPackage pkg, ParsedActivityImpl activity, boolean allowImplicitEphemeralVisibility, boolean visibleToEphemeral, Resources resources, XmlResourceParser parser, ParseInput input)505     private static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(ParsingPackage pkg,
506             ParsedActivityImpl activity, boolean allowImplicitEphemeralVisibility,
507             boolean visibleToEphemeral, Resources resources, XmlResourceParser parser,
508             ParseInput input) throws IOException, XmlPullParserException {
509         ParseResult<ParsedIntentInfoImpl> result = ParsedMainComponentUtils.parseIntentFilter(activity,
510                 pkg, resources, parser, visibleToEphemeral, true /*allowGlobs*/,
511                 true /*allowAutoVerify*/, allowImplicitEphemeralVisibility,
512                 true /*failOnNoActions*/, input);
513         if (result.isError()) {
514             return input.error(result);
515         }
516 
517         ParsedIntentInfoImpl intent = result.getResult();
518         if (intent != null) {
519             final IntentFilter intentFilter = intent.getIntentFilter();
520             if (intentFilter.isVisibleToInstantApp()) {
521                 activity.setFlags(activity.getFlags() | ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP);
522             }
523             if (intentFilter.isImplicitlyVisibleToInstantApp()) {
524                 activity.setFlags(
525                         activity.getFlags() | ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP);
526             }
527         }
528 
529         return input.success(intent);
530     }
531 
getActivityResizeMode(ParsingPackage pkg, TypedArray sa, int screenOrientation)532     private static int getActivityResizeMode(ParsingPackage pkg, TypedArray sa,
533             int screenOrientation) {
534         Boolean resizeableActivity = pkg.getResizeableActivity();
535 
536         if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity)
537                 || resizeableActivity != null) {
538             // Activity or app explicitly set if it is resizeable or not;
539             if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity,
540                     resizeableActivity != null && resizeableActivity)) {
541                 return ActivityInfo.RESIZE_MODE_RESIZEABLE;
542             } else {
543                 return ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
544             }
545         }
546 
547         if (pkg.isResizeableActivityViaSdkVersion()) {
548             // The activity or app didn't explicitly set the resizing option, however we want to
549             // make it resize due to the sdk version it is targeting.
550             return ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
551         }
552 
553         // resize preference isn't set and target sdk version doesn't support resizing apps by
554         // default. For the app to be resizeable if it isn't fixed orientation or immersive.
555         if (ActivityInfo.isFixedOrientationPortrait(screenOrientation)) {
556             return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
557         } else if (ActivityInfo.isFixedOrientationLandscape(screenOrientation)) {
558             return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
559         } else if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
560             return ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
561         } else {
562             return ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
563         }
564     }
565 
566     @NonNull
parseActivityWindowLayout(Resources res, AttributeSet attrs, ParseInput input)567     private static ParseResult<ActivityInfo.WindowLayout> parseActivityWindowLayout(Resources res,
568             AttributeSet attrs, ParseInput input) {
569         TypedArray sw = res.obtainAttributes(attrs, R.styleable.AndroidManifestLayout);
570         try {
571             int width = -1;
572             float widthFraction = -1f;
573             int height = -1;
574             float heightFraction = -1f;
575             final int widthType = sw.getType(R.styleable.AndroidManifestLayout_defaultWidth);
576             if (widthType == TypedValue.TYPE_FRACTION) {
577                 widthFraction = sw.getFraction(R.styleable.AndroidManifestLayout_defaultWidth, 1, 1,
578                         -1);
579             } else if (widthType == TypedValue.TYPE_DIMENSION) {
580                 width = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_defaultWidth,
581                         -1);
582             }
583             final int heightType = sw.getType(R.styleable.AndroidManifestLayout_defaultHeight);
584             if (heightType == TypedValue.TYPE_FRACTION) {
585                 heightFraction = sw.getFraction(R.styleable.AndroidManifestLayout_defaultHeight, 1,
586                         1, -1);
587             } else if (heightType == TypedValue.TYPE_DIMENSION) {
588                 height = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_defaultHeight,
589                         -1);
590             }
591             int gravity = sw.getInt(R.styleable.AndroidManifestLayout_gravity, Gravity.CENTER);
592             int minWidth = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_minWidth, -1);
593             int minHeight = sw.getDimensionPixelSize(R.styleable.AndroidManifestLayout_minHeight,
594                     -1);
595             String windowLayoutAffinity =
596                     sw.getNonConfigurationString(
597                             R.styleable.AndroidManifestLayout_windowLayoutAffinity, 0);
598             final ActivityInfo.WindowLayout windowLayout = new ActivityInfo.WindowLayout(width,
599                     widthFraction, height, heightFraction, gravity, minWidth, minHeight,
600                     windowLayoutAffinity);
601             return input.success(windowLayout);
602         } finally {
603             sw.recycle();
604         }
605     }
606 
607     /**
608      * Resolves values in {@link ActivityInfo.WindowLayout}.
609      *
610      * <p>{@link ActivityInfo.WindowLayout#windowLayoutAffinity} has a fallback metadata used in
611      * Android R and some variants of pre-R.
612      */
resolveActivityWindowLayout( ParsedActivity activity, ParseInput input)613     private static ParseResult<ActivityInfo.WindowLayout> resolveActivityWindowLayout(
614             ParsedActivity activity, ParseInput input) {
615         // There isn't a metadata for us to fall back. Whatever is in layout is correct.
616         if (!activity.getMetaData().containsKey(
617                 ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
618             return input.success(activity.getWindowLayout());
619         }
620 
621         // Layout already specifies a value. We should just use that one.
622         if (activity.getWindowLayout() != null && activity.getWindowLayout().windowLayoutAffinity != null) {
623             return input.success(activity.getWindowLayout());
624         }
625 
626         String windowLayoutAffinity = activity.getMetaData().getString(
627                 ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY);
628         ActivityInfo.WindowLayout layout = activity.getWindowLayout();
629         if (layout == null) {
630             layout = new ActivityInfo.WindowLayout(-1 /* width */, -1 /* widthFraction */,
631                     -1 /* height */, -1 /* heightFraction */, Gravity.NO_GRAVITY,
632                     -1 /* minWidth */, -1 /* minHeight */, windowLayoutAffinity);
633         } else {
634             layout.windowLayoutAffinity = windowLayoutAffinity;
635         }
636         return input.success(layout);
637     }
638 
639     /**
640      * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
641      * @param recreateOnConfigChanges The bit mask recreateOnConfigChanges fetched from
642      *                                AndroidManifest.xml.
643      * @hide
644      */
getActivityConfigChanges(int configChanges, int recreateOnConfigChanges)645     public static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) {
646         return configChanges | ((~recreateOnConfigChanges) & RECREATE_ON_CONFIG_CHANGES_MASK);
647     }
648 }
649