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