Add backporting of Baklava SDK SDK_INT_FULL

The static field android.os.Build$VERSION.SDK_INT_FULL was added for
Android Bakalva (present in Developer Preview 1). Backport the static
field get to allow code reading this field to work on all android
versions.

The backport is based on the fact that SDK_INT_FULL is defined as
SDK_INT * 100_000 for all versions before Baklava. These constants
are present in the Baklava SDK in android.os.Build$VERSION_CODES_FULL.

The lint information for backported methods now also contains field
information. For SDK_INT_FULL the line in the method list (which is now
a methods and fields list) is:

android/os/Build$VERSION#SDK_INT_FULL

Bug: b/380038007
Change-Id: Ib20c77e67b71182ef9e0a8a4f8dc42e94f8fc810
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 985fbc9..9ee017e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -220,6 +220,10 @@
     return false;
   }
 
+  public CfStaticFieldRead asStaticFieldGet() {
+    return null;
+  }
+
   public boolean isFieldPut() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
index f7ae0ff..12467da 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
@@ -48,6 +48,11 @@
   }
 
   @Override
+  public CfStaticFieldRead asStaticFieldGet() {
+    return this;
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerStaticFieldReadInstruction(this);
diff --git a/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java
index f3b5171..93ac3df 100644
--- a/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java
@@ -3,22 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.errors;
 
-import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 
 @KeepForApi
 public class IgnoredBackportMethodDiagnostic implements DesugarDiagnostic {
 
-  private final DexMethod backport;
+  private final DexMember<?, ?> backport;
   private final Origin origin;
   private final Position position;
   private final int minApiLevel;
 
   public IgnoredBackportMethodDiagnostic(
-      DexMethod backport, Origin origin, Position position, int minApiLevel) {
+      DexMember<?, ?> backport, Origin origin, Position position, int minApiLevel) {
     this.backport = backport;
     this.origin = origin;
     this.position = position;
@@ -26,7 +27,11 @@
   }
 
   public MethodReference getIgnoredBackportMethod() {
-    return backport.asMethodReference();
+    return backport.isDexMethod() ? backport.asDexMethod().asMethodReference() : null;
+  }
+
+  public FieldReference getIgnoredBackportField() {
+    return backport.isDexField() ? backport.asDexField().asFieldReference() : null;
   }
 
   public int getConfiguredMinApiLevel() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 53d670a..cea12d6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1216,6 +1216,8 @@
     public final DexField RELEASE = createField(androidOsBuildVersionType, stringType, "RELEASE");
     public final DexField SDK = createField(androidOsBuildVersionType, stringType, "SDK");
     public final DexField SDK_INT = createField(androidOsBuildVersionType, intType, "SDK_INT");
+    public final DexField SDK_INT_FULL =
+        createField(androidOsBuildVersionType, intType, "SDK_INT_FULL");
     public final DexField SECURITY_PATCH =
         createField(androidOsBuildVersionType, stringType, "SECURITY_PATCH");
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index a9ec6d2..f9d4c18 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Constants;
@@ -32,7 +33,9 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
@@ -100,22 +103,40 @@
 
   @Override
   public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
-    if (!instruction.isInvoke()) {
+    // Only invokes and static field gets are backported.
+    if (!instruction.isInvoke() && !instruction.isStaticFieldGet()) {
       return DesugarDescription.nothing();
     }
-
-    CfInvoke invoke = instruction.asInvoke();
-    MethodProvider methodProvider = getMethodProviderOrNull(invoke.getMethod(), context);
-    if (methodProvider == null
-        || appView
-            .getSyntheticItems()
-            .isSyntheticOfKind(context.getContextType(), kinds -> kinds.BACKPORT_WITH_FORWARDING)) {
-      return DesugarDescription.nothing();
+    if (instruction.isInvoke()) {
+      CfInvoke invoke = instruction.asInvoke();
+      MethodProvider<DexMethod> methodProvider = getProviderOrNull(invoke.getMethod(), context);
+      if (methodProvider == null
+          || appView
+              .getSyntheticItems()
+              .isSyntheticOfKind(
+                  context.getContextType(), kinds -> kinds.BACKPORT_WITH_FORWARDING)) {
+        return DesugarDescription.nothing();
+      }
+      return desugarInstruction(invoke, methodProvider);
+    } else {
+      assert instruction.isStaticFieldGet();
+      CfStaticFieldRead staticGet = instruction.asStaticFieldGet();
+      MethodProvider<DexField> methodProvider = getProviderOrNull(staticGet.getField());
+      if (methodProvider == null
+          || appView
+              .getSyntheticItems()
+              .isSyntheticOfKind(context.getContextType(), kinds -> kinds.BACKPORT_WITH_FORWARDING)
+          || appView
+              .getSyntheticItems()
+              .isSyntheticOfKind(context.getContextType(), kinds -> kinds.API_MODEL_OUTLINE)) {
+        return DesugarDescription.nothing();
+      }
+      return desugarInstruction(staticGet, methodProvider);
     }
-    return desugarInstruction(invoke, methodProvider);
   }
 
-  private DesugarDescription desugarInstruction(CfInvoke invoke, MethodProvider methodProvider) {
+  private DesugarDescription desugarInstruction(
+      CfInvoke invoke, MethodProvider<DexMethod> methodProvider) {
     return DesugarDescription.builder()
         .setDesugarRewrite(
             (position,
@@ -127,7 +148,7 @@
                 methodProcessingContext,
                 desugaringCollection,
                 dexItemFactory) ->
-                methodProvider.rewriteInvoke(
+                methodProvider.rewriteInstruction(
                     position,
                     invoke,
                     appView,
@@ -137,35 +158,63 @@
         .build();
   }
 
-  public static List<DexMethod> generateListOfBackportedMethods(
-      AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
+  private DesugarDescription desugarInstruction(
+      CfStaticFieldRead staticGet, MethodProvider<DexField> methodProvider) {
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (position,
+                freshLocalProvider,
+                localStackAllocator,
+                desugaringInfo,
+                eventConsumer,
+                context,
+                methodProcessingContext,
+                desugaringCollection,
+                dexItemFactory) ->
+                methodProvider.rewriteInstruction(
+                    position,
+                    staticGet,
+                    appView,
+                    eventConsumer,
+                    methodProcessingContext,
+                    localStackAllocator))
+        .build();
+  }
+
+  public static void generateListOfBackportedMethodsAndFields(
+      AndroidApp androidApp,
+      InternalOptions options,
+      ExecutorService executor,
+      Consumer<DexMethod> methods,
+      Consumer<DexField> fields)
+      throws IOException {
     DexApplication app = null;
     if (androidApp != null) {
       app = new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
     }
-    return generateListOfBackportedMethods(app, options);
+    generateListOfBackportedMethodsAndFields(app, options, methods, fields);
   }
 
-  public static List<DexMethod> generateListOfBackportedMethods(
-      DexApplication app, InternalOptions options) throws IOException {
-    List<DexMethod> methods = new ArrayList<>();
+  public static void generateListOfBackportedMethodsAndFields(
+      DexApplication app,
+      InternalOptions options,
+      Consumer<DexMethod> methods,
+      Consumer<DexField> fields)
+      throws IOException {
     options.loadMachineDesugaredLibrarySpecification(Timing.empty(), app);
     TypeRewriter typeRewriter = options.getTypeRewriter();
-    AppInfo appInfo = null;
-    if (app != null) {
-      appInfo = AppInfo.createInitialAppInfo(app, GlobalSyntheticsStrategy.forNonSynthesizing());
-
-    }
+    AppInfo appInfo =
+        AppInfo.createInitialAppInfo(app, GlobalSyntheticsStrategy.forNonSynthesizing());
     AppView<?> appView = AppView.createForD8(appInfo, typeRewriter, Timing.empty());
     BackportedMethodRewriter.RewritableMethods rewritableMethods =
         new BackportedMethodRewriter.RewritableMethods(appView);
-    rewritableMethods.visit(methods::add);
+    rewritableMethods.visit(methods);
     if (appInfo != null) {
       DesugaredLibraryRetargeter desugaredLibraryRetargeter =
           new DesugaredLibraryRetargeter(appView);
-      desugaredLibraryRetargeter.visit(methods::add);
+      desugaredLibraryRetargeter.visit(methods);
     }
-    return methods;
+    rewritableMethods.visitFields(fields);
   }
 
   public static void registerAssumedLibraryTypes(InternalOptions options) {
@@ -173,10 +222,11 @@
     BackportedMethods.registerSynthesizedCodeReferences(options.itemFactory);
   }
 
-  private MethodProvider getMethodProviderOrNull(DexMethod method, ProgramMethod context) {
+  private MethodProvider<DexMethod> getProviderOrNull(DexMethod member, ProgramMethod context) {
+    DexMethod method = member.asDexMethod();
     DexMethod original = appView.graphLens().getOriginalMethodSignature(method);
     assert original != null;
-    MethodProvider provider = rewritableMethods.getProvider(original);
+    MethodProvider<DexMethod> provider = rewritableMethods.getProvider(original);
     // Old versions of desugared library have in the jar file pre-desugared code. This is used
     // to undesugar pre-desugared code, then the code is re-desugared with D8/R8. This is
     // maintained for legacy only, recent desugared library should not be shipped with
@@ -196,14 +246,14 @@
       // unit, assume that the compilation is the defining instance and no backport is needed.
       DexClass clazz =
           appView
-              .contextIndependentDefinitionForWithResolutionResult(provider.method.holder)
+              .contextIndependentDefinitionForWithResolutionResult(provider.member.getHolderType())
               .toSingleClassWithProgramOverLibrary();
       if (clazz == null || !clazz.isProgramDefinition()) {
         appView
             .reporter()
             .warning(
                 new IgnoredBackportMethodDiagnostic(
-                    provider.method,
+                    provider.member,
                     context.getOrigin(),
                     MethodPosition.create(context),
                     appView.options().getMinApiLevel().getLevel()));
@@ -213,14 +263,24 @@
     return provider;
   }
 
+  private MethodProvider<DexField> getProviderOrNull(DexField field) {
+    MethodProvider<DexField> provider = rewritableMethods.getProvider(field);
+    return provider;
+  }
+
   private static final class RewritableMethods {
 
     private final Map<DexType, AndroidApiLevel> typeMinApi;
 
     private final AppView<?> appView;
 
-    // Map backported method to a provider for creating the actual target method (with code).
-    private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
+    // Map backported field or method to a provider for creating the actual target method
+    // (with code).
+    private final Map<DexMethod, MethodProvider<DexMethod>> rewritableMethods =
+        new IdentityHashMap<>();
+    private final Map<DexField, MethodProvider<DexField>> rewritableFields =
+        new IdentityHashMap<>();
+    MethodProvider<DexField> singleBackportedField = null; // As there is only one now skip map.
 
     RewritableMethods(AppView<?> appView) {
       InternalOptions options = appView.options();
@@ -298,6 +358,9 @@
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.V)) {
         initializeAndroidVMethodProviders(factory);
       }
+      if (options.getMinApiLevel().isLessThan(AndroidApiLevel.BAKLAVA)) {
+        initializeAndroidBaklavaMethodProviders(factory);
+      }
     }
 
     private Map<DexType, AndroidApiLevel> initializeTypeMinApi(DexItemFactory factory) {
@@ -384,11 +447,15 @@
     }
 
     boolean isEmpty() {
-      return rewritable.isEmpty();
+      return rewritableMethods.isEmpty() && rewritableFields.isEmpty();
     }
 
     public void visit(Consumer<DexMethod> consumer) {
-      rewritable.keySet().forEach(consumer);
+      rewritableMethods.keySet().forEach(consumer);
+    }
+
+    public void visitFields(Consumer<DexField> consumer) {
+      rewritableFields.keySet().forEach(consumer);
     }
 
     private void initializeAndroidKObjectsMethodProviders(DexItemFactory factory) {
@@ -1694,6 +1761,20 @@
       }
     }
 
+    private void initializeAndroidBaklavaMethodProviders(DexItemFactory factory) {
+      // android.os.Build$VERSION
+      DexType type = factory.androidOsBuildVersionType;
+
+      // int android.os.Build$VERSION.SDK_INT_FULL
+      DexString name = factory.createString("SDK_INT_FULL");
+      DexField field = factory.createField(type, factory.intType, name);
+      addProviderForField(
+          new StaticFieldGetMethodWithForwardingGenerator(
+              field,
+              // Template code calls the method again.
+              BackportedMethods::AndroidOsBuildVersionMethods_getSdkIntFull));
+    }
+
     private void initializeAndroidUMethodProviders(DexItemFactory factory) {
       DexType type;
       DexString name;
@@ -1815,34 +1896,50 @@
       addProvider(new ThreadLocalWithInitialWithSupplierGenerator(method));
     }
 
-    private void addProvider(MethodProvider generator) {
-      MethodProvider replaced = rewritable.put(generator.method, generator);
+    private void addProvider(MethodProvider<DexMethod> generator) {
+      MethodProvider<DexMethod> replaced = rewritableMethods.put(generator.member, generator);
       assert replaced == null;
     }
 
-    MethodProvider getProvider(DexMethod method) {
-      return rewritable.get(method);
+    MethodProvider<DexMethod> getProvider(DexMethod method) {
+      return rewritableMethods.get(method);
+    }
+
+    private void addProviderForField(MethodProvider<DexField> generator) {
+      MethodProvider<DexField> replaced = rewritableFields.put(generator.member, generator);
+      assert replaced == null;
+      assert singleBackportedField == null;
+      singleBackportedField = generator;
+    }
+
+    MethodProvider<DexField> getProvider(DexField field) {
+      if (field.isIdenticalTo(singleBackportedField.member)) {
+        return singleBackportedField;
+      }
+      assert !rewritableFields.containsKey(field);
+      return null;
     }
   }
 
-  public abstract static class MethodProvider {
+  public abstract static class MethodProvider<
+      T extends DexMember<? extends DexItem, ? extends DexMember<?, ?>>> {
 
-    final DexMethod method;
+    final T member;
 
-    public MethodProvider(DexMethod method) {
-      this.method = method;
+    public MethodProvider(T member) {
+      this.member = member;
     }
 
-    public abstract Collection<CfInstruction> rewriteInvoke(
+    public abstract Collection<CfInstruction> rewriteInstruction(
         Position position,
-        CfInvoke invoke,
+        CfInstruction instruction,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
         MethodProcessingContext methodProcessingContext,
         LocalStackAllocator localStackAllocator);
   }
 
-  private static final class InvokeRewriter extends MethodProvider {
+  private static final class InvokeRewriter extends MethodProvider<DexMethod> {
 
     private final MethodInvokeRewriter rewriter;
 
@@ -1852,15 +1949,15 @@
     }
 
     @Override
-    public Collection<CfInstruction> rewriteInvoke(
+    public Collection<CfInstruction> rewriteInstruction(
         Position position,
-        CfInvoke invoke,
+        CfInstruction instruction,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
         MethodProcessingContext methodProcessingContext,
         LocalStackAllocator localStackAllocator) {
       Collection<CfInstruction> instructions =
-          rewriter.rewrite(invoke, appView.dexItemFactory(), localStackAllocator);
+          rewriter.rewrite(instruction.asInvoke(), appView.dexItemFactory(), localStackAllocator);
       if (position == null) {
         return instructions;
       }
@@ -1868,7 +1965,7 @@
       CfLabel start = new CfLabel();
       CfLabel end = new CfLabel();
       Position inlinePosition =
-          SourcePosition.builder().setCallerPosition(position).setMethod(method).setLine(0).build();
+          SourcePosition.builder().setCallerPosition(position).setMethod(member).setLine(0).build();
       instructionsWithPositions.add(start);
       instructionsWithPositions.add(new CfPosition(start, inlinePosition));
       instructionsWithPositions.addAll(instructions);
@@ -1878,7 +1975,7 @@
     }
   }
 
-  private static class MethodGenerator extends MethodProvider {
+  private static class MethodGenerator extends MethodProvider<DexMethod> {
 
     private final TemplateMethodFactory factory;
 
@@ -1897,9 +1994,9 @@
     }
 
     @Override
-    public Collection<CfInstruction> rewriteInvoke(
+    public Collection<CfInstruction> rewriteInstruction(
         Position position,
-        CfInvoke invoke,
+        CfInstruction instruction,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
         MethodProcessingContext methodProcessingContext,
@@ -1927,14 +2024,14 @@
                             Code code = generateTemplateMethod(appView.dexItemFactory(), methodSig);
                             if (appView.options().hasMappingFileSupport()) {
                               return code.getCodeAsInlining(
-                                  methodSig, true, method, false, appView.dexItemFactory());
+                                  methodSig, true, member, false, appView.dexItemFactory());
                             }
                             return code;
                           }));
     }
 
     public DexProto getProto(DexItemFactory itemFactory) {
-      return method.proto;
+      return member.getProto();
     }
 
     public Code generateTemplateMethod(DexItemFactory dexItemFactory, DexMethod method) {
@@ -1957,7 +2054,7 @@
 
     @Override
     public DexProto getProto(DexItemFactory factory) {
-      return method.getProto().prependParameter(receiverType, factory);
+      return member.getProto().prependParameter(receiverType, factory);
     }
   }
 
@@ -2008,9 +2105,9 @@
     }
 
     @Override
-    public Collection<CfInstruction> rewriteInvoke(
+    public Collection<CfInstruction> rewriteInstruction(
         Position position,
-        CfInvoke invoke,
+        CfInstruction instruction,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
         MethodProcessingContext methodProcessingContext,
@@ -2172,4 +2269,65 @@
     public abstract Collection<CfInstruction> rewrite(
         CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator);
   }
+
+  // Generator for backports of static field gets which will again read the field they backport in
+  // the backport code. So using BACKPORT_WITH_FORWARDING as such backports cannot not go through
+  // backporting again as that would cause an infinite backporting loop.
+  private static class StaticFieldGetMethodWithForwardingGenerator
+      extends MethodProvider<DexField> {
+
+    private final TemplateMethodFactory factory;
+
+    StaticFieldGetMethodWithForwardingGenerator(DexField field, TemplateMethodFactory factory) {
+      super(field);
+      this.factory = factory;
+    }
+
+    @Override
+    public Collection<CfInstruction> rewriteInstruction(
+        Position position,
+        CfInstruction instruction,
+        AppView<?> appView,
+        BackportedMethodDesugaringEventConsumer eventConsumer,
+        MethodProcessingContext methodProcessingContext,
+        LocalStackAllocator localStackAllocator) {
+      ProgramMethod method = getSyntheticMethod(appView, methodProcessingContext);
+      eventConsumer.acceptBackportedMethod(method, methodProcessingContext.getMethodContext());
+      return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
+    }
+
+    protected SyntheticKind getSyntheticKind(SyntheticNaming naming) {
+      return naming.BACKPORT_WITH_FORWARDING;
+    }
+
+    private ProgramMethod getSyntheticMethod(
+        AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+      return appView
+          .getSyntheticItems()
+          .createMethod(
+              this::getSyntheticKind,
+              methodProcessingContext.createUniqueContext(),
+              appView,
+              builder ->
+                  builder
+                      // As this is forwarding to the field read set the API level accordingly to
+                      // ensure API outlining when needed.
+                      .setApiLevelForCode(
+                          appView.apiLevelCompute().computeApiLevelForLibraryReference(member))
+                      .setProto(getProto(appView.dexItemFactory()))
+                      .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                      .setCode(
+                          methodSig ->
+                              generateTemplateMethod(appView.dexItemFactory(), methodSig)));
+    }
+
+    public DexProto getProto(DexItemFactory itemFactory) {
+      // Proto for the method replacing the field read.
+      return itemFactory.createProto(member.getType());
+    }
+
+    public Code generateTemplateMethod(DexItemFactory dexItemFactory, DexMethod method) {
+      return factory.create(dexItemFactory, method);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 29259c9..259234e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -95,7 +95,13 @@
 
   private ComputedApiLevel getComputedApiLevelInstructionOnHolderWithMinApi(
       CfInstruction instruction, ProgramMethod context) {
-    if (context.getDefinition().isD8R8Synthesized()) {
+    // Some backports will forward to the method/field they backport. For such synthetics run
+    // outlining. Other synthetics should not need it. And explicitly not API outlines, as that
+    // would cause infinite outlining.
+    if (context.getDefinition().isD8R8Synthesized()
+        && !appView
+            .getSyntheticItems()
+            .isSyntheticOfKind(context.getHolderType(), k -> k.BACKPORT_WITH_FORWARDING)) {
       return appView.computedMinApiLevel();
     }
     DexReference reference = getReferenceFromInstruction(instruction);
@@ -194,10 +200,11 @@
       ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
       ProgramMethod context) {
     assert instruction.isInvoke()
-        || instruction.isFieldInstruction()
-        || instruction.isCheckCast()
-        || instruction.isInstanceOf()
-        || instruction.isConstClass();
+            || instruction.isFieldInstruction()
+            || instruction.isCheckCast()
+            || instruction.isInstanceOf()
+            || instruction.isConstClass()
+        : instruction;
     ProgramMethod outlinedMethod =
         ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
     eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 6c73cde..005543b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -56,6 +56,7 @@
 public final class BackportedMethods {
 
   public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("Landroid/os/Build$VERSION;");
     factory.createSynthesizedType("Ljava/lang/ArithmeticException;");
     factory.createSynthesizedType("Ljava/lang/AssertionError;");
     factory.createSynthesizedType("Ljava/lang/Double;");
@@ -122,6 +123,45 @@
     factory.createSynthesizedType("[Ljava/util/Map$Entry;");
   }
 
+  public static CfCode AndroidOsBuildVersionMethods_getSdkIntFull(
+      DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        0,
+        ImmutableList.of(
+            label0,
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Landroid/os/Build$VERSION;"),
+                    factory.intType,
+                    factory.createString("SDK_INT"))),
+            new CfConstNumber(36, ValueType.INT),
+            new CfIfCmp(IfType.GE, ValueType.INT, label2),
+            label1,
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Landroid/os/Build$VERSION;"),
+                    factory.intType,
+                    factory.createString("SDK_INT"))),
+            new CfConstNumber(100000, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT),
+            new CfReturn(ValueType.INT),
+            label2,
+            new CfFrame(),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Landroid/os/Build$VERSION;"),
+                    factory.intType,
+                    factory.createString("SDK_INT_FULL"))),
+            new CfReturn(ValueType.INT)),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode AssertionErrorMethods_createAssertionError(
       DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
index 8d1ede7..b257bc2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.StringResource;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -107,6 +108,14 @@
       desugaredApisSignatures.add(
           classBinaryName + '#' + extraMethod.name + extraMethod.proto.toDescriptorString());
     }
+    if (FORMAT_WITH_FIELD) {
+      for (DexField extraField : supportedClasses.getExtraFields()) {
+        String classBinaryName =
+            DescriptorUtils.getClassBinaryNameFromDescriptor(
+                extraField.getHolderType().descriptor.toString());
+        desugaredApisSignatures.add(classBinaryName + '#' + extraField.name);
+      }
+    }
 
     // Write a plain text file with the desugared APIs.
     desugaredApisSignatures.sort(Comparator.naturalOrder());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
index db8847b..3e568da 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
@@ -32,10 +32,15 @@
 public class SupportedClasses {
   private final Map<DexType, SupportedClass> supportedClasses;
   private final List<DexMethod> extraMethods;
+  private final List<DexField> extraFields;
 
-  SupportedClasses(Map<DexType, SupportedClass> supportedClasses, List<DexMethod> extraMethods) {
+  SupportedClasses(
+      Map<DexType, SupportedClass> supportedClasses,
+      List<DexMethod> extraMethods,
+      List<DexField> extraFields) {
     this.supportedClasses = supportedClasses;
     this.extraMethods = extraMethods;
+    this.extraFields = extraFields;
   }
 
   public void forEachClass(Consumer<SupportedClass> consumer) {
@@ -46,6 +51,10 @@
     return extraMethods;
   }
 
+  public List<DexField> getExtraFields() {
+    return extraFields;
+  }
+
   public static class SupportedClass {
 
     private final DexClass clazz;
@@ -199,6 +208,7 @@
 
     Map<DexType, SupportedClass.Builder> supportedClassBuilders = new IdentityHashMap<>();
     private List<DexMethod> extraMethods = ImmutableList.of();
+    private List<DexField> extraFields = ImmutableList.of();
 
     ClassAnnotation getClassAnnotation(DexType type) {
       SupportedClass.Builder builder = supportedClassBuilders.get(type);
@@ -282,6 +292,10 @@
       this.extraMethods = extraMethods;
     }
 
+    public void setExtraFields(List<DexField> extraFields) {
+      this.extraFields = extraFields;
+    }
+
     public boolean hasOnlyExtraMethods() {
       return supportedClassBuilders.isEmpty();
     }
@@ -292,7 +306,7 @@
           (type, classBuilder) -> {
             map.put(type, classBuilder.build());
           });
-      return new SupportedClasses(ImmutableSortedMap.copyOf(map), extraMethods);
+      return new SupportedClasses(ImmutableSortedMap.copyOf(map), extraMethods, extraFields);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
index a3ce881..d458dc0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
@@ -43,7 +43,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.io.IOException;
@@ -53,6 +52,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 
 public class SupportedClassesGenerator {
 
@@ -181,7 +181,8 @@
       AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
 
       // This should depend only on machine specification and min api.
-      List<DexMethod> backports = generateListOfBackportedMethods();
+      List<DexMethod> backports = new ArrayList<>();
+      generateListOfBackportedMethodsAndFields(backports::add, f -> {});
 
       int finalApi = api;
       builder.forEachClassAndMethod(
@@ -306,7 +307,9 @@
     DirectMappedDexApplication implementationApplication =
         new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
 
-    List<DexMethod> backports = generateListOfBackportedMethods();
+    List<DexMethod> backports = new ArrayList<>();
+    List<DexField> backportFields = new ArrayList<>();
+    generateListOfBackportedMethodsAndFields(backports::add, backportFields::add);
 
     for (DexProgramClass clazz : implementationApplication.classes()) {
       // All emulated interfaces static and default methods are supported.
@@ -389,14 +392,24 @@
       }
       extraMethods.sort(Comparator.naturalOrder());
       builder.setExtraMethods(extraMethods);
+
+      List<DexField> extraFields = new ArrayList<>();
+      for (DexField backport : backportFields) {
+        if (implementationApplication.definitionFor(backport.getHolderType()) == null) {
+          extraFields.add(backport);
+        }
+      }
+      extraFields.sort(Comparator.naturalOrder());
+      builder.setExtraFields(extraFields);
     }
   }
 
-  private List<DexMethod> generateListOfBackportedMethods() throws IOException {
-    if (androidPlatformBuild) {
-      return ImmutableList.of();
+  private void generateListOfBackportedMethodsAndFields(
+      Consumer<DexMethod> methods, Consumer<DexField> fields) throws IOException {
+    if (!androidPlatformBuild) {
+      BackportedMethodRewriter.generateListOfBackportedMethodsAndFields(
+          appForMax, options, methods, fields);
     }
-    return BackportedMethodRewriter.generateListOfBackportedMethods(appForMax, options);
   }
 
   private void registerMethod(
diff --git a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
index f003fdf..37f266a 100644
--- a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
+++ b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
@@ -118,6 +118,11 @@
     assertEquals(
         apiLevel < AndroidApiLevel.V.getLevel(),
         backports.contains("java/lang/Character#toString(I)Ljava/lang/String;"));
+
+    // BAKLAVA added static field android/os/Build$VERSION.SDK_INT_FULL.
+    assertEquals(
+        apiLevel < AndroidApiLevel.BAKLAVA.getLevel(),
+        backports.contains("android/os/Build$VERSION#SDK_INT_FULL"));
   }
 
   private void addLibraryDesugaring(BackportedMethodListCommand.Builder builder) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AndroidOsBuildVersionBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AndroidOsBuildVersionBackportTest.java
new file mode 100644
index 0000000..ebf9f8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AndroidOsBuildVersionBackportTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.backports;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AndroidOsBuildVersionBackportTest extends AbstractBackportTest {
+
+  private static final String ANDROID_OS_BUILD_VERSION_TYPE_NAME = "android.os.Build$VERSION";
+  private static final String ANDROID_OS_BUILD_VERSION_DESCRIPTOR =
+      DescriptorUtils.javaTypeToDescriptor(ANDROID_OS_BUILD_VERSION_TYPE_NAME);
+
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+        .withAllApiLevels()
+        .build();
+  }
+
+  public AndroidOsBuildVersionBackportTest(TestParameters parameters)
+      throws IOException, NoSuchFieldException {
+    super(
+        parameters,
+        ANDROID_OS_BUILD_VERSION_TYPE_NAME,
+        ImmutableList.of(getTestRunner(), getTransformedBuildVERSIONClass()));
+
+    // android.os.Build$VERSION.SDK_INT_FULL is on API 36.
+    registerFieldTarget(AndroidApiLevel.BAKLAVA, 1);
+  }
+
+  // Stub out android.os.Build$VERSION as it does not exist when building R8.
+  public static class /*android.os.Build$*/ VERSION {
+
+    public static /*final*/ int SDK_INT = -1;
+    public static /*final*/ int SDK_INT_FULL = -1;
+  }
+
+  private static byte[] getTransformedBuildVERSIONClass() throws IOException, NoSuchFieldException {
+    return transformer(VERSION.class)
+        .setClassDescriptor(ANDROID_OS_BUILD_VERSION_DESCRIPTOR)
+        .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+        .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT_FULL"), AccessFlags::setFinal)
+        .transform();
+  }
+
+  private static byte[] getTestRunner() throws IOException {
+    return transformer(TestRunner.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(VERSION.class), ANDROID_OS_BUILD_VERSION_DESCRIPTOR)
+        .transform();
+  }
+
+  public static class TestRunner extends MiniAssert {
+
+    public static void main(String[] args) throws Exception {
+      System.out.println(VERSION.SDK_INT_FULL);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/SdkIntFullBackportOutlineInBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/SdkIntFullBackportOutlineInBackportTest.java
new file mode 100644
index 0000000..260ea5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/SdkIntFullBackportOutlineInBackportTest.java
@@ -0,0 +1,208 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.backports;
+
+import static com.android.tools.r8.utils.AndroidApiLevel.BAKLAVA;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.androidapi.AndroidApiLevelHashingDatabaseImpl;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class SdkIntFullBackportOutlineInBackportTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void checkSdkIntFullApiLevel() {
+    assumeTrue(parameters.isNoneRuntime());
+    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+    InternalOptions options = new InternalOptions();
+    AndroidApiLevelHashingDatabaseImpl androidApiLevelDatabase =
+        new AndroidApiLevelHashingDatabaseImpl(ImmutableList.of(), options, diagnosticsHandler);
+    assertEquals(
+        BAKLAVA.getLevel(),
+        androidApiLevelDatabase
+            .getFieldApiLevel(
+                options.dexItemFactory().androidOsBuildVersionMembers.SDK_INT_FULL.asDexField())
+            .getLevel());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+      if (apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.LATEST)) {
+        continue;
+      }
+      testForD8()
+          .addProgramClassFileData(getTransformedMainClass())
+          .addLibraryClassFileData(getTransformedBuildVersionClass())
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+          .setMinApi(apiLevel)
+          .compile()
+          .inspect(inspector -> inspectD8(inspector, apiLevel));
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+      // SDK_INT was introduced in D so don't bother testing before.
+      if (apiLevel.isLessThanOrEqualTo(AndroidApiLevel.D)
+          || apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.LATEST)) {
+        continue;
+      }
+      System.out.println(apiLevel);
+      testForR8(Backend.DEX)
+          .addProgramClassFileData(getTransformedMainClass())
+          .addKeepMainRule(TestClass.class)
+          .addLibraryClassFileData(getTransformedBuildVersionClass())
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+          .setMinApi(apiLevel)
+          .addDontObfuscate()
+          .compile()
+          .inspect(inspector -> inspectR8(inspector, apiLevel));
+    }
+  }
+
+  private void inspectD8(CodeInspector inspector, AndroidApiLevel apiLevel) {
+    if (apiLevel.isGreaterThanOrEqualTo(BAKLAVA)) {
+      // From BAKLAVA the SDK_INT_FULL get stays.
+      assertEquals(
+          1,
+          countStaticGets(
+              inspector.clazz(TestClass.class).mainMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+    } else {
+      // Before BAKLAVA the SDK_INT_FULL static get is backported and the SDK_INT_FULL static get
+      // is outlined from the backport as well.
+      ClassSubject backport =
+          inspector.clazz(
+              SyntheticItemsTestUtils.syntheticBackportWithForwardingClass(TestClass.class, 1));
+      assertThat(backport, isPresent());
+      assertEquals(
+          2,
+          countStaticGets(
+              backport.uniqueMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT));
+      assertEquals(
+          0,
+          countStaticGets(
+              backport.uniqueMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+      ClassSubject apiOutline =
+          inspector.clazz(SyntheticItemsTestUtils.syntheticApiOutlineClass(TestClass.class, 0));
+      assertThat(apiOutline.uniqueMethod(), isPresent());
+      assertEquals(
+          1,
+          countStaticGets(
+              apiOutline.uniqueMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+    }
+  }
+
+  private void inspectR8(CodeInspector inspector, AndroidApiLevel apiLevel) {
+    if (apiLevel.isGreaterThanOrEqualTo(BAKLAVA)) {
+      // From BAKLAVA the SDK_INT_FULL get stays.
+      assertEquals(
+          1,
+          countStaticGets(
+              inspector.clazz(TestClass.class).mainMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+    } else {
+      // Before BAKLAVA the SDK_INT_FULL static get is backported and the SDK_INT_FULL static get
+      // is outlined from the backport as well. With just one use of the backport it is inlined
+      // into main.
+      ClassSubject backport =
+          inspector.clazz(
+              SyntheticItemsTestUtils.syntheticBackportWithForwardingClass(TestClass.class, 1));
+      assertThat(backport, isAbsent());
+      assertEquals(
+          1,
+          countStaticGets(
+              inspector.clazz(TestClass.class).mainMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT));
+      assertEquals(
+          0,
+          countStaticGets(
+              inspector.clazz(TestClass.class).mainMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+      ClassSubject apiOutline =
+          inspector.clazz(SyntheticItemsTestUtils.syntheticApiOutlineClass(TestClass.class, 0));
+      assertThat(apiOutline.uniqueMethod(), isPresent());
+      assertEquals(
+          1,
+          countStaticGets(
+              apiOutline.uniqueMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+    }
+  }
+
+  private long countStaticGets(MethodSubject subject, DexField field) {
+    return subject
+        .streamInstructions()
+        .filter(InstructionSubject::isStaticGet)
+        .map(InstructionSubject::getField)
+        .filter(dexField -> dexField.isIdenticalTo(field))
+        .count();
+  }
+
+  private static byte[] getTransformedMainClass() throws IOException {
+    return transformer(TestClass.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(VERSION.class), "Landroid/os/Build$VERSION;")
+        .transform();
+  }
+
+  private byte[] getTransformedBuildVersionClass() throws IOException, NoSuchFieldException {
+    return transformer(VERSION.class)
+        .setClassDescriptor("Landroid/os/Build$VERSION;")
+        .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+        .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT_FULL"), AccessFlags::setFinal)
+        .transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(VERSION.SDK_INT_FULL);
+    }
+  }
+
+  public static class /*android.os.Build$*/ VERSION {
+
+    public static /*final*/ int SDK_INT = -1;
+    public static /*final*/ int SDK_INT_FULL = -1;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 26098b7..c3b9af4 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -6,12 +6,14 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -24,9 +26,11 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableSet;
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
@@ -100,14 +104,19 @@
       CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(apiLevel));
       InternalOptions options = new InternalOptions();
       options.setMinApiLevel(apiLevel);
-      List<DexMethod> backportedMethods =
-          BackportedMethodRewriter.generateListOfBackportedMethods(
-              AndroidApp.builder().build(), options, ThreadUtils.getExecutorService(options));
+      List<DexMethod> backportedMethods = new ArrayList<>();
+      List<DexField> backportedFields = new ArrayList<>();
+      BackportedMethodRewriter.generateListOfBackportedMethodsAndFields(
+          AndroidApp.builder().build(),
+          options,
+          ThreadUtils.getExecutorService(options),
+          backportedMethods::add,
+          backportedFields::add);
       Set<DexMethod> alwaysPresent = expectedToAlwaysBePresentInAndroidJar(options.itemFactory);
       for (DexMethod method : backportedMethods) {
         // Two different DexItemFactories are in play, but as toSourceString is used for lookup
         // that is not an issue.
-        ClassSubject clazz = inspector.clazz(method.holder.toSourceString());
+        ClassSubject clazz = inspector.clazz(method.getHolderType().toSourceString());
         MethodSubject foundInAndroidJar =
             clazz.method(
                 method.proto.returnType.toSourceString(),
@@ -120,6 +129,15 @@
             foundInAndroidJar,
             notIf(isPresent(), !alwaysPresent.contains(method)));
       }
+      for (DexField field : backportedFields) {
+        // Two different DexItemFactories are in play, but as toSourceString is used for lookup
+        // that is not an issue.
+        ClassSubject clazz = inspector.clazz(field.getHolderType().toSourceString());
+        FieldSubject foundInAndroidJar =
+            clazz.field(field.getType().toSourceString(), field.getName().toSourceString());
+        assertThat(
+            foundInAndroidJar + " present in " + apiLevel, foundInAndroidJar, not(isPresent()));
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
index cdbee2c..ac65d6a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
@@ -129,7 +129,8 @@
                     finalApp, GlobalSyntheticsStrategy.forNonSynthesizing()))
             .appInfoForDesugaring();
     Set<DexMethod> backports = Sets.newIdentityHashSet();
-    backports.addAll(BackportedMethodRewriter.generateListOfBackportedMethods(libHolder, options));
+    BackportedMethodRewriter.generateListOfBackportedMethodsAndFields(
+        libHolder, options, backports::add, f -> {});
     Map<DexMethod, Object> failures = new IdentityHashMap<>();
     for (FoundClassSubject clazz : inspector.allClasses()) {
       if (clazz.toString().startsWith("j$.sun.nio.cs.")
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/AndroidOsBuildVersionMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/AndroidOsBuildVersionMethods.java
new file mode 100644
index 0000000..9b127ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/AndroidOsBuildVersionMethods.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.backports;
+
+public final class AndroidOsBuildVersionMethods {
+  // Stub out android.os.Build$VERSION as it does not exist when building R8.
+  private static class AndroidOsBuildVersionStub {
+    public static int SDK_INT;
+    public static int SDK_INT_FULL;
+  }
+
+  // Android runtime value of field android.os.Build$VERSION.SDK_INT_FULL for all Android
+  // versions. Calculated from android.os.Build$VERSION.SDK_INT before Baklava (API level 36).
+  // See android.os.Build$VERSION_CODES_FULL for the constants for versions before Baklava.
+  public static int getSdkIntFull() {
+    if (AndroidOsBuildVersionStub.SDK_INT < 36) {
+      // Based on the constants in android.os.Build$VERSION_CODES_FULL.
+      return AndroidOsBuildVersionStub.SDK_INT * 100_000;
+    }
+    return AndroidOsBuildVersionStub.SDK_INT_FULL;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 9e964ea..7ec725f 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ToolHelper.TestDataSourceSet;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -35,6 +36,7 @@
       factory.createType("Lcom/android/tools/r8/ir/desugar/backports/BackportedMethods;");
   private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
       ImmutableList.of(
+          AndroidOsBuildVersionMethods.class,
           AssertionErrorMethods.class,
           AtomicReferenceArrayMethods.class,
           AtomicReferenceFieldUpdaterMethods.class,
@@ -139,6 +141,28 @@
     return instruction;
   }
 
+  private static CfInstruction rewriteToAndroidOsBuildVersion(
+      DexItemFactory itemFactory, CfInstruction instruction) {
+    // Rewrite references to UnsafeStub to sun.misc.Unsafe.
+    if (instruction.isStaticFieldGet()) {
+      CfStaticFieldRead fieldGet = instruction.asStaticFieldGet();
+      return new CfStaticFieldRead(
+          itemFactory.createField(
+              itemFactory.createType("Landroid/os/Build$VERSION;"),
+              fieldGet.getField().getType(),
+              fieldGet.getField().getName()));
+    }
+    if (instruction.isFrame()) {
+      return instruction
+          .asFrame()
+          .mapReferenceTypes(
+              type -> {
+                throw new RuntimeException("Unexpected CfFrame instruction.");
+              });
+    }
+    return instruction;
+  }
+
   @Override
   protected CfCode getCode(String holderName, String methodName, CfCode code) {
     if (methodName.endsWith("Stub")) {
@@ -157,6 +181,12 @@
               .map(instruction -> rewriteToUnsafe(factory, instruction))
               .collect(Collectors.toList()));
     }
+    if (holderName.equals("AndroidOsBuildVersionMethods") && methodName.equals("getSdkIntFull")) {
+      code.setInstructions(
+          code.getInstructions().stream()
+              .map(instruction -> rewriteToAndroidOsBuildVersion(factory, instruction))
+              .collect(Collectors.toList()));
+    }
     return code;
   }
 
diff --git a/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index c9e7983..18c8f65 100644
--- a/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -43,6 +43,7 @@
   private final Path testJar;
   private final String testClassName;
   private final Int2IntSortedMap invokeStaticCounts = new Int2IntAVLTreeMap();
+  private final Int2IntSortedMap staticGetCounts = new Int2IntAVLTreeMap();
   private final Set<String> ignoredInvokes = new HashSet<>();
 
   private static class ClassInfo {
@@ -136,19 +137,29 @@
       this.testClassName = testClassName;
     }
 
-    // Assume all method calls will be rewritten on the lowest API level.
+    // Assume all method calls and static gets will be rewritten on the lowest API level.
     invokeStaticCounts.put(AndroidApiLevel.B.getLevel(), 0);
+    staticGetCounts.put(AndroidApiLevel.B.getLevel(), 0);
   }
 
   protected void registerTarget(AndroidApiLevel apiLevel, int invokeStaticCount) {
     invokeStaticCounts.put(apiLevel.getLevel(), invokeStaticCount);
   }
 
+  void registerFieldTarget(AndroidApiLevel apiLevel, int getStaticCount) {
+    staticGetCounts.put(apiLevel.getLevel(), getStaticCount);
+  }
+
   private int getTargetInvokesCount(AndroidApiLevel apiLevel) {
     int key = invokeStaticCounts.headMap(apiLevel.getLevel() + 1).lastIntKey();
     return invokeStaticCounts.get(key);
   }
 
+  private int getTargetGetCount(AndroidApiLevel apiLevel) {
+    int key = staticGetCounts.headMap(apiLevel.getLevel() + 1).lastIntKey();
+    return staticGetCounts.get(key);
+  }
+
   protected void ignoreInvokes(String methodName) {
     ignoredInvokes.add(methodName);
   }
@@ -243,6 +254,27 @@
         + actualTargetInvokes
         + ": "
         + javaInvokeStatics, expectedTargetInvokes, actualTargetInvokes);
+
+    List<InstructionSubject> javaStaticGets =
+        testSubject.allMethods().stream()
+            .flatMap(MethodSubject::streamInstructions)
+            .filter(InstructionSubject::isStaticGet)
+            .filter(is -> is.getField().holder.toSourceString().equals(targetClass.getName()))
+            .collect(toList());
+
+    long expectedTargetStaticGets = getTargetGetCount(apiLevel);
+    long actualTargetStaticGets = javaStaticGets.size();
+    assertEquals(
+        "Expected "
+            + expectedTargetStaticGets
+            + " static gets on "
+            + targetClass.getName()
+            + " but found "
+            + actualTargetStaticGets
+            + ": "
+            + javaStaticGets,
+        expectedTargetStaticGets,
+        actualTargetStaticGets);
   }
 
   public String getTestClassName() {
diff --git a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index de1aec9..da0c617 100644
--- a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -134,6 +134,10 @@
     return syntheticClass(classReference, naming.BACKPORT, id);
   }
 
+  public static ClassReference syntheticBackportWithForwardingClass(Class<?> clazz, int id) {
+    return syntheticClass(clazz, naming.BACKPORT_WITH_FORWARDING, id);
+  }
+
   public static ClassReference syntheticBackportWithForwardingClass(
       ClassReference classReference, int id) {
     return syntheticClass(classReference, naming.BACKPORT_WITH_FORWARDING, id);