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