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