Match Soong's handling of {min,target}_sdk_version in android_binary

To determine values to use for manifest fixing, Soong calls
EffectiveVersionString on the raw {min,target}_sdk_version properties
if defined, or on sdk_version if not. Additionally, a few product
variables, as well as whether the app is updatable, can affect ways
the raw value is overriden.
This CL  matches this logic in android_binary.
Another CL in the same topic has android_binary's bp2build converter pass the
raw {min,target}_sdk_version property values directly to
android_binary's manifest_values, instead of the post-processed ones.

Bug: 274474008
Test: CI + Unit tests
Change-Id: I3a132ee09dffe67f3d84062bc37f4310bf36ffe3
diff --git a/product_config/BUILD b/product_config/BUILD
index 4d4795f..e27a982 100644
--- a/product_config/BUILD
+++ b/product_config/BUILD
@@ -69,6 +69,7 @@
     "malloc_zero_contents",
     "native_coverage",
     "pdk",
+    "platform_sdk_final",
     "release_aidl_use_unfrozen",
     "safestack",
     "treble_linker_namespaces",
diff --git a/rules/android/BUILD.bazel b/rules/android/BUILD.bazel
index e9a1090..a753c2a 100644
--- a/rules/android/BUILD.bazel
+++ b/rules/android/BUILD.bazel
@@ -12,8 +12,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 load(":android_app_certificate.bzl", "no_android_app_certificate")
+load(":manifest_fixer_test.bzl", "manifest_fixer_test_suite")
 
 no_android_app_certificate(
     name = "no_android_app_certificate",
     visibility = ["//visibility:public"],
 )
+
+manifest_fixer_test_suite(
+    name = "manifest_fixer_tests",
+)
diff --git a/rules/android/android_binary.bzl b/rules/android/android_binary.bzl
index 1627d86..b3ec57a 100644
--- a/rules/android/android_binary.bzl
+++ b/rules/android/android_binary.bzl
@@ -41,8 +41,13 @@
         )
     )
 
+    # The following attributes are unknown the native android_binary rule and must be removed
+    # prior to instantiating it.
     attrs.pop("$enable_manifest_merging", None)
     attrs["proguard_specs"] = []
+    attrs.pop("sdk_version")
+    if "updatable" in attrs:
+        attrs.pop("updatable")
 
     native.android_binary(
         application_resources = android_binary_aosp_internal_name,
@@ -105,6 +110,7 @@
         target_compatible_with = target_compatible_with,
         tags = tags + ["manual"],
         visibility = ["//visibility:private"],
+        sdk_version = sdk_version,
         **kwargs
     )
 
diff --git a/rules/android/android_binary_aosp_internal/impl.bzl b/rules/android/android_binary_aosp_internal/impl.bzl
index 3479d40..f27909e 100644
--- a/rules/android/android_binary_aosp_internal/impl.bzl
+++ b/rules/android/android_binary_aosp_internal/impl.bzl
@@ -24,8 +24,10 @@
 load("@rules_android//rules:resources.bzl", _resources = "resources")
 load("@rules_android//rules:utils.bzl", "get_android_toolchain", "utils")
 load("@rules_android//rules/android_binary_internal:impl.bzl", "finalize", _BASE_PROCESSORS = "PROCESSORS")
+load("//build/bazel/rules/android:manifest_fixer.bzl", "manifest_fixer")
 load("//build/bazel/rules/cc:cc_stub_library.bzl", "CcStubInfo")
 load("//build/bazel/rules/common:api.bzl", "api")
+load("//build/bazel/rules/common:sdk_version.bzl", "sdk_version")
 
 CollectedCcStubsInfo = provider(
     "Tracks cc stub libraries to exclude from APK packaging.",
@@ -125,17 +127,64 @@
         ]),
     )
 
-def _process_manifest_aosp(ctx, **_unused_ctxs):
-    manifest_ctx = _resources.set_default_min_sdk(
-        ctx,
-        manifest = ctx.file.manifest,
-        default = api.default_app_target_sdk(),
-        enforce_min_sdk_floor_tool = get_android_toolchain(ctx).enforce_min_sdk_floor_tool.files_to_run,
+# Starlark implementation of AndroidApp.MinSdkVersion from build/soong/java/app.go
+def _maybe_override_min_sdk_version(ctx):
+    min_sdk_version = sdk_version.api_level_string_with_fallback(
+        ctx.attr.manifest_values.get("minSdkVersion"),
+        ctx.attr.sdk_version,
     )
+    override_apex_manifest_default_version = ctx.attr._override_apex_manifest_default_version[BuildSettingInfo].value
+    if (ctx.attr.updatable and
+        override_apex_manifest_default_version and
+        (api.parse_api_level_from_version(override_apex_manifest_default_version) >
+         api.parse_api_level_from_version(min_sdk_version))):
+        return override_apex_manifest_default_version
+    return min_sdk_version
+
+def _maybe_override_manifest_values(ctx):
+    min_sdk_version = api.effective_version_string(_maybe_override_min_sdk_version(ctx))
+
+    has_unbundled_build_apps = (ctx.attr._unbundled_build_apps[BuildSettingInfo].value != None and
+                                len(ctx.attr._unbundled_build_apps[BuildSettingInfo].value) > 0)
+
+    # TODO: b/300916281 - When Api fingerprinting is used, it should be appended to the target SDK version here.
+    target_sdk_version = manifest_fixer.target_sdk_version_for_manifest_fixer(
+        target_sdk_version = sdk_version.api_level_string_with_fallback(
+            ctx.attr.manifest_values.get("targetSdkVersion"),
+            ctx.attr.sdk_version,
+        ),
+        platform_sdk_final = ctx.attr._platform_sdk_final[BuildSettingInfo].value,
+        has_unbundled_build_apps = has_unbundled_build_apps,
+    )
+    return struct(
+        min_sdk_version = min_sdk_version,
+        target_sdk_version = target_sdk_version,
+    )
+
+def _process_manifest_aosp(ctx, **_unused_ctxs):
+    maybe_overriden_values = _maybe_override_manifest_values(ctx)
+    out_manifest = ctx.actions.declare_file("fixed_manifest/" + ctx.label.name + "/" + "AndroidManifest.xml")
+    manifest_fixer.fix(
+        ctx,
+        manifest_fixer = ctx.executable._manifest_fixer,
+        in_manifest = ctx.file.manifest,
+        out_manifest = out_manifest,
+        min_sdk_version = maybe_overriden_values.min_sdk_version,
+        target_sdk_version = maybe_overriden_values.target_sdk_version,
+    )
+
+    updated_manifest_values = {
+        key: ctx.attr.manifest_values[key]
+        for key in ctx.attr.manifest_values.keys()
+        if key not in ("minSdkVersion", "targetSdkVersion")
+    }
 
     return ProviderInfo(
         name = "manifest_ctx",
-        value = manifest_ctx,
+        value = _resources.ManifestContextInfo(
+            processed_manifest = out_manifest,
+            processed_manifest_values = updated_manifest_values,
+        ),
     )
 
 # (b/274150785)  validation processor does not allow min_sdk that are a string
diff --git a/rules/android/android_binary_aosp_internal/rule.bzl b/rules/android/android_binary_aosp_internal/rule.bzl
index 2b6e1b0..3ac3a06 100644
--- a/rules/android/android_binary_aosp_internal/rule.bzl
+++ b/rules/android/android_binary_aosp_internal/rule.bzl
@@ -46,6 +46,31 @@
             default = Label("//build/bazel/product_config:device_abi"),
             doc = "Implicit attr used to extract target device ABI information (for apk lib naming).",
         ),
+        _platform_sdk_final = attr.label(
+            default = "//build/bazel/product_config:platform_sdk_final",
+            doc = "PlatformSdkFinal product variable",
+        ),
+        _unbundled_build_apps = attr.label(
+            default = "//build/bazel/product_config:unbundled_build_apps",
+            doc = "UnbundledBuildApps product variable",
+        ),
+        _override_apex_manifest_default_version = attr.label(
+            default = "//build/bazel/rules/apex:override_apex_manifest_default_version",
+            doc = "If the app is updatable, and this attribute is specified, and higher than the value specified in manifest_values, will override minSdkVersion in manifest with this value instead of the value in manifest_values.",
+        ),
+        _manifest_fixer = attr.label(
+            cfg = "exec",
+            executable = True,
+            default = "//build/soong/scripts:manifest_fixer",
+        ),
+        sdk_version = attr.string(
+            doc = "The sdk_version this app should build against.",
+        ),
+        # TODO: b/301425155 - Handle all of the ways updatable affects this rule.
+        updatable = attr.bool(
+            default = False,
+            doc = "Whether this app is updatable.",
+        ),
     ),
 )
 
@@ -57,4 +82,4 @@
     Args:
       **attrs: Rule attributes
     """
-    android_binary_aosp_internal(**sanitize_attrs(attrs))
+    android_binary_aosp_internal(**sanitize_attrs(attrs, _ATTRS))
diff --git a/rules/android/android_binary_aosp_internal/test/test.bzl b/rules/android/android_binary_aosp_internal/test/test.bzl
index 4f9ec5f..4eeb909 100644
--- a/rules/android/android_binary_aosp_internal/test/test.bzl
+++ b/rules/android/android_binary_aosp_internal/test/test.bzl
@@ -102,6 +102,7 @@
         ],
         srcs = ["fake.java"],
         tags = ["manual"],
+        sdk_version = "current",
     )
 
     android_binary_aosp_internal_providers_test(
diff --git a/rules/android/manifest_fixer.bzl b/rules/android/manifest_fixer.bzl
index ec2e057..25fefd9 100644
--- a/rules/android/manifest_fixer.bzl
+++ b/rules/android/manifest_fixer.bzl
@@ -12,6 +12,33 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+load("@soong_injection//api_levels:platform_versions.bzl", "platform_versions")
+load("//build/bazel/rules/common:api.bzl", "api", "api_from_product")
+load(":manifest_fixer_internal.bzl", _internal = "manifest_fixer_internal")
+
+# TODO(b/300428335): access these variables in a transition friendly way.
+_PLATFORM_SDK_VERSION = platform_versions.platform_sdk_version
+_PLATFORM_SDK_CODENAME = platform_versions.platform_sdk_codename
+_PLATFORM_VERSION_ACTIVE_CODENAMES = platform_versions.platform_version_active_codenames
+
+# Starlark implementation of TargetSdkVersionForManifestFixer from build/soong/java/android_manifest.go
+def _target_sdk_version_for_manifest_fixer(
+        target_sdk_version,
+        platform_sdk_final,
+        has_unbundled_build_apps):
+    platform_sdk_variables = struct(
+        platform_sdk_final = platform_sdk_final,
+        platform_sdk_version = _PLATFORM_SDK_VERSION,
+        platform_sdk_codename = _PLATFORM_SDK_CODENAME,
+        platform_version_active_codenames = _PLATFORM_VERSION_ACTIVE_CODENAMES,
+    )
+    return _internal.target_sdk_version_for_manifest_fixer(
+        target_sdk_version = target_sdk_version,
+        has_unbundled_build_apps = has_unbundled_build_apps,
+        platform_sdk_variables = platform_sdk_variables,
+    )
+
+# TODO: b/301430823 - Only pass ctx.actions to limit the scope of what this function can access.
 def _fix(
         ctx,
         manifest_fixer,
@@ -42,4 +69,5 @@
 
 manifest_fixer = struct(
     fix = _fix,
+    target_sdk_version_for_manifest_fixer = _target_sdk_version_for_manifest_fixer,
 )
diff --git a/rules/android/manifest_fixer_internal.bzl b/rules/android/manifest_fixer_internal.bzl
new file mode 100644
index 0000000..d05b631
--- /dev/null
+++ b/rules/android/manifest_fixer_internal.bzl
@@ -0,0 +1,43 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+load("//build/bazel/rules/common:api.bzl", "api", "api_from_product")
+
+visibility("private")
+
+# Starlark implementation of shouldReturnFinalOrFutureInt from build/soong/java/android_manifest.go
+# TODO: b/300916781 - In Soong this also returns true when the module is an MTS test.
+def _should_return_future_int(
+        target_sdk_version,
+        platform_sdk_variables,
+        has_unbundled_build_apps):
+    if platform_sdk_variables.platform_sdk_final:
+        return False
+    return api_from_product(platform_sdk_variables).is_preview(target_sdk_version) and has_unbundled_build_apps
+
+# Starlark implementation of TargetSdkVersionForManifestFixer from build/soong/java/android_manifest.go
+def _target_sdk_version_for_manifest_fixer(
+        target_sdk_version,
+        platform_sdk_variables,
+        has_unbundled_build_apps):
+    if _should_return_future_int(
+        target_sdk_version = target_sdk_version,
+        platform_sdk_variables = platform_sdk_variables,
+        has_unbundled_build_apps = has_unbundled_build_apps,
+    ):
+        return str(api.FUTURE_API_LEVEL)
+    return api_from_product(platform_sdk_variables).effective_version_string(target_sdk_version)
+
+manifest_fixer_internal = struct(
+    target_sdk_version_for_manifest_fixer = _target_sdk_version_for_manifest_fixer,
+)
diff --git a/rules/android/manifest_fixer_test.bzl b/rules/android/manifest_fixer_test.bzl
new file mode 100644
index 0000000..f30af55
--- /dev/null
+++ b/rules/android/manifest_fixer_test.bzl
@@ -0,0 +1,65 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
+load("//build/bazel/rules/android:manifest_fixer_internal.bzl", manifest_fixer_for_testing = "manifest_fixer_internal")
+load("//build/bazel/rules/common:api.bzl", "api")
+
+def _target_sdk_version_override_test_impl(ctx):
+    env = unittest.begin(ctx)
+    platform_sdk_codename = "Tiramisu"
+    platform_sdk_version = "33"
+    platform_version_active_codenames = [platform_sdk_codename]
+
+    # Schema: (Input targetSdkVersion, PlatformSdkFinal, Is unbundled app build) -> Expected targetSdkVersion
+    _VERSIONS_UNDER_TEST = {
+        ("29", False, False): "29",
+        ("30", False, True): "30",
+        ("current", False, True): str(api.FUTURE_API_LEVEL),
+        ("30", True, False): "30",
+        ("30", True, True): "30",
+        ("Tiramisu", True, True): "33",
+        ("current", True, True): "33",
+    }
+    for (target_sdk_version, platform_sdk_final, is_unbundled_app_build), expected_target_sdk_version in _VERSIONS_UNDER_TEST.items():
+        platform_sdk_variables = struct(
+            platform_sdk_codename = platform_sdk_codename,
+            platform_sdk_final = platform_sdk_final,
+            platform_sdk_version = platform_sdk_version,
+            platform_version_active_codenames = platform_version_active_codenames,
+        )
+        asserts.equals(
+            env,
+            expected_target_sdk_version,
+            manifest_fixer_for_testing.target_sdk_version_for_manifest_fixer(
+                target_sdk_version,
+                platform_sdk_variables,
+                is_unbundled_app_build,
+            ),
+            ("unexpected target SDK version for manifest fixer %s with input target" +
+             "SDK version %s, platform SDK variables %s and is_unbundled_app_build %s") % (
+                expected_target_sdk_version,
+                target_sdk_version,
+                platform_sdk_variables,
+                is_unbundled_app_build,
+            ),
+        )
+    return unittest.end(env)
+
+target_sdk_version_override_test = unittest.make(_target_sdk_version_override_test_impl)
+
+def manifest_fixer_test_suite(name):
+    unittest.suite(
+        name,
+        target_sdk_version_override_test,
+    )
diff --git a/rules/common/api.bzl b/rules/common/api.bzl
index 052bbba..553d655 100644
--- a/rules/common/api.bzl
+++ b/rules/common/api.bzl
@@ -31,12 +31,17 @@
 _preview_codenames_to_ints = api_internal.preview_codenames_to_ints(_PLATFORM_VERSION_ACTIVE_CODENAMES)
 
 # Returns true if a string or int version is in preview (not finalized).
-def _is_preview(version):
-    return api_internal.is_preview(version, _preview_codenames_to_ints)
+def _is_preview(version, platform_sdk_variables):
+    return api_internal.is_preview(
+        version = version,
+        preview_codenames_to_ints = api_internal.preview_codenames_to_ints(
+            platform_sdk_variables.platform_version_active_codenames,
+        ),
+    )
 
 # Return 10000 for unfinalized versions, otherwise return unchanged.
 def _final_or_future(version):
-    if _is_preview(version):
+    if api_internal.is_preview(version, _preview_codenames_to_ints):
         return api_internal.FUTURE_API_LEVEL
     else:
         return version
@@ -66,7 +71,7 @@
     if version == "current":
         return api_internal.FUTURE_API_LEVEL
 
-    if _is_preview(version):
+    if api_internal.is_preview(version = version, preview_codenames_to_ints = _preview_codenames_to_ints):
         return _preview_codenames_to_ints.get(version) or int(version)
 
     # Not preview nor current.
@@ -84,15 +89,15 @@
 # Starlark implementation of DefaultAppTargetSDK from build/soong/android/config.go
 # https://cs.android.com/android/platform/superproject/+/master:build/soong/android/config.go;l=875-889;drc=b0dc477ef740ec959548fe5517bd92ac4ea0325c
 # check what you want returned for codename == "" case before using
-def _default_app_target_sdk():
+def _default_app_target_sdk(platform_sdk_variables):
     """default_app_target_sdk returns the API level that platform apps are targeting.
        This converts a codename to the exact ApiLevel it represents.
     """
     return _parse_api_level_from_version(
         api_internal.default_app_target_sdk_string(
-            _PLATFORM_SDK_FINAL,
-            _PLATFORM_SDK_VERSION,
-            _PLATFORM_SDK_CODENAME,
+            platform_sdk_final = platform_sdk_variables.platform_sdk_final,
+            platform_sdk_version = platform_sdk_variables.platform_sdk_version,
+            platform_sdk_codename = platform_sdk_variables.platform_sdk_codename,
         ),
     )
 
@@ -100,25 +105,40 @@
 # EffectiveVersionString converts an api level string into the concrete version string that the module
 # should use. For modules targeting an unreleased SDK (meaning it does not yet have a number)
 # it returns the codename (P, Q, R, etc.)
-def _effective_version_string(version):
+def _effective_version_string(version, platform_sdk_variables):
     return api_internal.effective_version_string(
         version,
-        _preview_codenames_to_ints,
-        api_internal.default_app_target_sdk_string(
-            _PLATFORM_SDK_FINAL,
-            _PLATFORM_SDK_VERSION,
-            _PLATFORM_SDK_CODENAME,
+        api_internal.preview_codenames_to_ints(
+            platform_sdk_variables.platform_version_active_codenames,
         ),
-        _PLATFORM_VERSION_ACTIVE_CODENAMES,
+        api_internal.default_app_target_sdk_string(
+            platform_sdk_final = platform_sdk_variables.platform_sdk_final,
+            platform_sdk_version = platform_sdk_variables.platform_sdk_version,
+            platform_sdk_codename = platform_sdk_variables.platform_sdk_codename,
+        ),
+        platform_sdk_variables.platform_version_active_codenames,
     )
 
-api = struct(
+api_from_product = lambda platform_sdk_variables: struct(
     NONE_API_LEVEL = api_internal.NONE_API_LEVEL,
     FUTURE_API_LEVEL = api_internal.FUTURE_API_LEVEL,
-    is_preview = _is_preview,
+    is_preview = lambda version: _is_preview(
+        version = version,
+        platform_sdk_variables = platform_sdk_variables,
+    ),
     final_or_future = _final_or_future,
-    default_app_target_sdk = _default_app_target_sdk,
+    default_app_target_sdk = lambda: _default_app_target_sdk(platform_sdk_variables),
     parse_api_level_from_version = _parse_api_level_from_version,
     api_levels = _api_levels_with_previews,
-    effective_version_string = _effective_version_string,
+    effective_version_string = lambda version: _effective_version_string(
+        version = version,
+        platform_sdk_variables = platform_sdk_variables,
+    ),
 )
+
+api = api_from_product(struct(
+    platform_sdk_final = _PLATFORM_SDK_FINAL,
+    platform_sdk_version = _PLATFORM_SDK_VERSION,
+    platform_sdk_codename = _PLATFORM_SDK_CODENAME,
+    platform_version_active_codenames = _PLATFORM_VERSION_ACTIVE_CODENAMES,
+))
diff --git a/rules/common/sdk_version.bzl b/rules/common/sdk_version.bzl
index 33f2f61..1c57acc 100644
--- a/rules/common/sdk_version.bzl
+++ b/rules/common/sdk_version.bzl
@@ -56,7 +56,7 @@
     if sdk_version == "core_platform":
         fail("Only prebuilt SDK versions are available, sdk_version core_platform is not yet supported.")
     if sdk_version == "none":
-        return struct(kind = _KIND_NONE, api_level = api.NONE_API_LEVEL)
+        return struct(kind = _KIND_NONE, api_level = api.NONE_API_LEVEL, _api_level_string = "(no version)")
     if type(sdk_version) != type(""):
         fail("sdk_version must be a string")
     sep_index = sdk_version.rfind("_")
@@ -69,7 +69,10 @@
             sdk_version,
             ",".join(_ALL_KINDS),
         ))
-    return struct(kind = kind, api_level = api_level)
+    return struct(kind = kind, api_level = api_level, _api_level_string = api_level_string)
+
+def _api_level_string_with_fallback(api_level_string, sdk_version):
+    return api_level_string if api_level_string else _sdk_spec_from(sdk_version)._api_level_string
 
 sdk_version = struct(
     KIND_PUBLIC = _KIND_PUBLIC,
@@ -80,5 +83,6 @@
     KIND_CORE = _KIND_CORE,
     KIND_NONE = _KIND_NONE,
     ALL_KINDS = _ALL_KINDS,
+    api_level_string_with_fallback = _api_level_string_with_fallback,
     sdk_spec_from = _sdk_spec_from,
 )