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