Implement constructor pruning for XML-referenced framework subclasses

Bug: b/325884671
Change-Id: I96e21cbd265b65eaa222997e10c9ac034255dd5d
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 1c9b922..bd4c85d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -719,6 +719,12 @@
   public final DexType androidUtilPropertyType =
       createStaticallyKnownType("Landroid/util/Property;");
   public final DexType androidViewViewType = createStaticallyKnownType("Landroid/view/View;");
+  public final DexType androidUtilAttributeSetType =
+      createStaticallyKnownType("Landroid/util/AttributeSet;");
+  public final DexType androidPreferencePreferenceType =
+      createStaticallyKnownType("Landroid/preference/Preference;");
+  public final DexType androidTransitionTransitionType =
+      createStaticallyKnownType("Landroid/transition/Transition;");
   public final DexType androidUtilSparseArrayType =
       createStaticallyKnownType(androidUtilSparseArrayDescriptorString);
   public final DexType androidContentResTypedArrayType =
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 7da4163..61ed436 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexItemFactory.ClassMethods;
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMember;
@@ -61,6 +62,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
@@ -724,8 +726,22 @@
     ReflectiveUseFromXml reason = KeepReason.reflectiveUseFromXml(origin);
     ensureClassKeptForResourceLookup(clazz, reason, markAsLive);
 
+    DexItemFactory factory = appView.dexItemFactory();
+    boolean isFrameworkClass =
+        appInfo
+            .traverseSuperClasses(
+                clazz,
+                (supertype, superclass, subclass) ->
+                    TraversalContinuation.breakIf(
+                        supertype.isIdenticalTo(factory.androidViewViewType)
+                            || supertype.isIdenticalTo(factory.androidPreferencePreferenceType)
+                            || supertype.isIdenticalTo(factory.androidTransitionTransitionType)))
+            .isBreak();
+
     for (ProgramMethod programInstanceInitializer : clazz.programInstanceInitializers()) {
-      // TODO(b/325884671): Only keep the actually framework targeted constructors.
+      if (isFrameworkClass && !isFrameworkTargetedConstructor(programInstanceInitializer)) {
+        continue;
+      }
       applyMinimumKeepInfoWhenLiveOrTargeted(
           programInstanceInitializer, KeepMethodInfo.newEmptyJoiner().disallowOptimization());
       if (markAsLive) {
@@ -741,6 +757,25 @@
     return true;
   }
 
+  private boolean isFrameworkTargetedConstructor(ProgramMethod programInstanceInitializer) {
+    DexTypeList parameters = programInstanceInitializer.getParameters();
+    int size = parameters.size();
+    if (size == 0) {
+      return true;
+    }
+    DexItemFactory factory = appView.dexItemFactory();
+    DexType contextType = factory.androidContentContextType;
+    if (size == 1) {
+      return parameters.values[0].isIdenticalTo(contextType);
+    }
+    if (size == 2) {
+      DexType attributeSetType = factory.androidUtilAttributeSetType;
+      return parameters.values[0].isIdenticalTo(contextType)
+          && parameters.values[1].isIdenticalTo(attributeSetType);
+    }
+    return false;
+  }
+
   private void ensureClassKeptForResourceLookup(
       DexProgramClass clazz, ReflectiveUseFromXml reason, boolean markAsLive) {
     applyMinimumKeepInfoWhenLive(
diff --git a/src/test/java/com/android/tools/r8/androidresources/XmlFilesWithClassReferences.java b/src/test/java/com/android/tools/r8/androidresources/XmlFilesWithClassReferences.java
index cdaa7cd..ecfed4f 100644
--- a/src/test/java/com/android/tools/r8/androidresources/XmlFilesWithClassReferences.java
+++ b/src/test/java/com/android/tools/r8/androidresources/XmlFilesWithClassReferences.java
@@ -105,9 +105,9 @@
             codeInspector -> {
               ClassSubject barClass = codeInspector.clazz(Bar.class);
               assertThat(barClass, isPresentAndNotRenamed());
-              // We should have two and only two methods, the two constructors.
-              assertEquals(barClass.allMethods(MethodSubject::isInstanceInitializer).size(), 2);
-              if (!parameters.isRandomPartialCompilation()) {
+              if (parameters.getPartialCompilationTestParameters().isNone()) {
+                // We should have two and only two methods, the two constructors.
+                assertEquals(barClass.allMethods(MethodSubject::isInstanceInitializer).size(), 2);
                 assertEquals(barClass.allMethods().size(), 2);
                 assertThat(codeInspector.clazz(BarFoo.class), assertFoo ? isPresent() : isAbsent());
               }
@@ -145,6 +145,68 @@
 
   public static class BarFoo {}
 
+  public static class MockContext {}
+
+  public static class MockAttributeSet {}
+
+  public static class MockView {
+    public MockView(MockContext context, MockAttributeSet attrs) {}
+  }
+
+  // Custom View class referenced from XML file
+  public static class BarView extends MockView {
+    public BarView(MockContext context, MockAttributeSet attrs) {
+      super(context, attrs);
+      System.out.println("init view");
+    }
+
+    public BarView(MockContext context, MockAttributeSet attrs, String x) {
+      super(context, attrs);
+      System.out.println("init view with string");
+    }
+  }
+
+  private byte[] getBarViewClassFileData() throws Exception {
+    return transformer(BarView.class)
+        .setSuper("Landroid/view/View;")
+        .replaceClassDescriptorInMembers(descriptor(MockContext.class), "Landroid/content/Context;")
+        .replaceClassDescriptorInMembers(
+            descriptor(MockAttributeSet.class), "Landroid/util/AttributeSet;")
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(MockView.class), "Landroid/view/View;")
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(MockContext.class), "Landroid/content/Context;")
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(MockAttributeSet.class), "Landroid/util/AttributeSet;")
+        .transform();
+  }
+
+  @Test
+  public void testCustomViewConstructorShrinking() throws Exception {
+    String formattedXmlFile =
+        String.format(VIEW_WITH_CLASS_ATTRIBUTE_REFERENCE, BarView.class.getTypeName());
+    testForR8(parameters)
+        .addProgramClasses(TestClass.class, BarFoo.class)
+        .addProgramClassFileData(getBarViewClassFileData())
+        .addAndroidResources(getTestResources(temp, formattedXmlFile))
+        .addKeepMainRule(TestClass.class)
+        .enableOptimizedShrinking()
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(
+            codeInspector -> {
+              ClassSubject barViewClass = codeInspector.clazz(BarView.class);
+              assertThat(barViewClass, isPresentAndNotRenamed());
+              if (parameters.getPartialCompilationTestParameters().isNone()) {
+                // The custom constructor (Context, AttributeSet, String) must be stripped, leaving
+                // only the targeted constructor.
+                assertEquals(
+                    barViewClass.allMethods(MethodSubject::isInstanceInitializer).size(), 1);
+              }
+            })
+        .assertSuccess();
+  }
+
   public static class R {
     public static class xml {
       public static int xml_with_bar_reference;