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