Backport android.content.res.TypedArray.close()
Method close was added in API level 31. Rewrite to recycle which has
been present since API level 1.
Fixes: b/263076143
Change-Id: Ie4c3d0bd8e8a69d0952e207a5454b5b5f82e3b65
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 579b21f..c798daf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -85,6 +85,9 @@
"Ljava/lang/invoke/MethodHandles$Lookup;";
public static final String dalvikAnnotationOptimizationPrefixString =
"Ldalvik/annotation/optimization/";
+ public static final String androidUtilSparseArrayDescriptorString = "Landroid/util/SparseArray;";
+ public static final String androidContentResTypedArrayDescriptorString =
+ "Landroid/content/res/TypedArray;";
/** Set of types that may be synthesized during compilation. */
private final Set<DexType> possibleCompilerSynthesizedTypes = Sets.newIdentityHashSet();
@@ -608,7 +611,9 @@
createStaticallyKnownType("Landroid/util/Property;");
public final DexType androidViewViewType = createStaticallyKnownType("Landroid/view/View;");
public final DexType androidUtilSparseArrayType =
- createStaticallyKnownType("Landroid/util/SparseArray;");
+ createStaticallyKnownType(androidUtilSparseArrayDescriptorString);
+ public final DexType androidContentResTypedArrayType =
+ createStaticallyKnownType(androidContentResTypedArrayDescriptorString);
public final StringBuildingMethods stringBuilderMethods =
new StringBuildingMethods(stringBuilderType);
@@ -662,6 +667,8 @@
public final AndroidViewViewMembers androidViewViewMembers = new AndroidViewViewMembers();
public final AndroidUtilSparseArrayMembers androidUtilSparseArrayMembers =
new AndroidUtilSparseArrayMembers();
+ public final AndroidContentResTypedArrayMembers androidContentResTypedArrayMembers =
+ new AndroidContentResTypedArrayMembers();
// java.**
public final JavaIoFileMembers javaIoFileMembers = new JavaIoFileMembers();
@@ -1135,6 +1142,7 @@
}
}
+ // android.util.SparseArray
public class AndroidUtilSparseArrayMembers extends LibraryMembers {
public final DexMethod put =
createMethod(androidUtilSparseArrayType, createProto(voidType, intType, objectType), "put");
@@ -1143,6 +1151,14 @@
androidUtilSparseArrayType, createProto(voidType, intType, objectType), setString);
}
+ // android.content.res.TypedArray
+ public class AndroidContentResTypedArrayMembers extends LibraryMembers {
+ public final DexMethod recycle =
+ createMethod(androidContentResTypedArrayType, createProto(voidType), "recycle");
+ public final DexMethod close =
+ createMethod(androidContentResTypedArrayType, createProto(voidType), "close");
+ }
+
public class BooleanMembers extends BoxedPrimitiveMembers {
public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
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 47d762c..28377d6 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
@@ -51,6 +51,7 @@
import com.android.tools.r8.ir.desugar.backports.ObjectsMethodRewrites;
import com.android.tools.r8.ir.desugar.backports.OptionalMethodRewrites;
import com.android.tools.r8.ir.desugar.backports.SparseArrayMethodRewrites;
+import com.android.tools.r8.ir.desugar.backports.TypedArrayMethodRewrites;
import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeter;
import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
@@ -1286,10 +1287,18 @@
// android.util.SparseArray
- // void android.util.SparseArray.set(int, Object))
+ // void android.util.SparseArray.set(int, Object)
addProvider(
new InvokeRewriter(
factory.androidUtilSparseArrayMembers.set, SparseArrayMethodRewrites.rewriteSet()));
+
+ // android.content.res.TypedArray
+
+ // void android.content.res.TypedArray.close()
+ addProvider(
+ new InvokeRewriter(
+ factory.androidContentResTypedArrayMembers.close,
+ TypedArrayMethodRewrites.rewriteClose()));
}
private void initializeAndroidSv2MethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/TypedArrayMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/TypedArrayMethodRewrites.java
new file mode 100644
index 0000000..941f0ba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/TypedArrayMethodRewrites.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2023, 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;
+
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import org.objectweb.asm.Opcodes;
+
+public final class TypedArrayMethodRewrites {
+
+ private TypedArrayMethodRewrites() {}
+
+ public static MethodInvokeRewriter rewriteClose() {
+ // Rewrite android/content/res/TypedArray#close to android/content/res/TypedArray#recycle
+ return (invoke, factory) ->
+ new CfInvoke(
+ Opcodes.INVOKEVIRTUAL, factory.androidContentResTypedArrayMembers.recycle, false);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TypedArrayBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/TypedArrayBackportTest.java
new file mode 100644
index 0000000..40eef59
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TypedArrayBackportTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2023, 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 org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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 TypedArrayBackportTest extends AbstractBackportTest {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+ }
+
+ public TypedArrayBackportTest(TestParameters parameters) throws IOException {
+ super(
+ parameters,
+ TypedArrayBackportTest.getTypedArray(parameters),
+ ImmutableList.of(
+ TypedArrayBackportTest.getTestRunner(),
+ TypedArrayBackportTest.getTypedArray(parameters)));
+
+ // The constructor is used by the test and recycle has been available since API 1 and is the
+ // method close is rewritten to.
+ ignoreInvokes("<init>");
+ ignoreInvokes("recycle");
+
+ // android.content.res.TypedArray.close added in API 31.
+ registerTarget(AndroidApiLevel.S, 1);
+ }
+
+ private static byte[] getTypedArray(TestParameters parameters) throws IOException {
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.S)) {
+ assertTrue(parameters.getRuntime().asDex().getVm().isNewerThanOrEqual(DexVm.ART_12_0_0_HOST));
+ return transformer(TypedArrayAndroid12.class)
+ .setClassDescriptor(DexItemFactory.androidContentResTypedArrayDescriptorString)
+ .transform();
+ } else {
+ return transformer(TypedArray.class)
+ .setClassDescriptor(DexItemFactory.androidContentResTypedArrayDescriptorString)
+ .transform();
+ }
+ }
+
+ private static byte[] getTestRunner() throws IOException {
+ return transformer(TestRunner.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(TypedArray.class),
+ DexItemFactory.androidContentResTypedArrayDescriptorString)
+ .transform();
+ }
+
+ public static class TypedArray {
+ public boolean wasClosed = false;
+
+ public void close() {
+ TestRunner.doFail("close should not be called");
+ }
+
+ public void recycle() {
+ wasClosed = true;
+ }
+ }
+
+ public static class TypedArrayAndroid12 {
+ public boolean wasClosed = false;
+
+ public void close() {
+ wasClosed = true;
+ }
+
+ public void recycle() {
+ TestRunner.doFail("recycle should not be called");
+ }
+ }
+
+ public static class TestRunner extends MiniAssert {
+
+ public static void main(String[] args) {
+ TypedArray typedArray = new TypedArray();
+ MiniAssert.assertFalse(typedArray.wasClosed);
+ typedArray.close();
+ MiniAssert.assertTrue(typedArray.wasClosed);
+ }
+
+ // Forwards to MiniAssert to avoid having to make it public.
+ public static void doFail(String message) {
+ MiniAssert.fail(message);
+ }
+ }
+}