Merge "Upgrade to Lint 30.3.0-alpha05"
diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
index ac9db6d..326a777 100644
--- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
+++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
@@ -285,10 +285,6 @@
                         Issues.ADDED_FINAL, new, "${describe(new, capitalize = true)} added 'final' qualifier"
                     )
                 }
-            } else if (oldModifiers.isFinal() && !newModifiers.isFinal()) {
-                report(
-                    Issues.REMOVED_FINAL, new, "${describe(new, capitalize = true)} removed 'final' qualifier"
-                )
             }
 
             if (oldModifiers.isStatic() != newModifiers.isStatic()) {
@@ -338,10 +334,10 @@
             }
         }
 
-        if (old.hasTypeVariables() && new.hasTypeVariables()) {
+        if (old.hasTypeVariables() || new.hasTypeVariables()) {
             val oldTypeParamsCount = old.typeParameterList().typeParameterCount()
             val newTypeParamsCount = new.typeParameterList().typeParameterCount()
-            if (oldTypeParamsCount != newTypeParamsCount) {
+            if (oldTypeParamsCount > 0 && oldTypeParamsCount != newTypeParamsCount) {
                 report(
                     Issues.CHANGED_TYPE, new,
                     "${describe(
@@ -419,8 +415,12 @@
                 report(Issues.CHANGED_TYPE, new, message)
             }
 
-            // Annotation methods?
-            if (!old.hasSameValue(new)) {
+            // Annotation methods
+            if (
+                new.containingClass().isAnnotationType() &&
+                old.containingClass().isAnnotationType() &&
+                new.defaultValue() != old.defaultValue()
+            ) {
                 val prevValue = old.defaultValue()
                 val prevString = if (prevValue.isEmpty()) {
                     "nothing"
@@ -475,6 +475,14 @@
             }
         }
 
+        if (new.containingClass().isInterface() || new.containingClass().isAnnotationType()) {
+            if (oldModifiers.isDefault() && newModifiers.isAbstract()) {
+                report(
+                    Issues.CHANGED_DEFAULT, new, "${describe(new, capitalize = true)} has changed 'default' qualifier"
+                )
+            }
+        }
+
         if (oldModifiers.isNative() != newModifiers.isNative()) {
             report(
                 Issues.CHANGED_NATIVE, new, "${describe(new, capitalize = true)} has changed 'native' qualifier"
@@ -497,10 +505,6 @@
                     report(
                         Issues.ADDED_FINAL, new, "${describe(new, capitalize = true)} has added 'final' qualifier"
                     )
-                } else if (old.isEffectivelyFinal() && !new.isEffectivelyFinal()) {
-                    report(
-                        Issues.REMOVED_FINAL, new, "${describe(new, capitalize = true)} has removed 'final' qualifier"
-                    )
                 }
             }
         }
@@ -614,14 +618,14 @@
                 val message = "${describe(new, capitalize = true)} has changed type from $oldType to $newType"
                 report(Issues.CHANGED_TYPE, new, message)
             } else if (!old.hasSameValue(new)) {
-                val prevValue = old.initialValue(true)
+                val prevValue = old.initialValue()
                 val prevString = if (prevValue == null && !old.modifiers.isFinal()) {
                     "nothing/not constant"
                 } else {
                     prevValue
                 }
 
-                val newValue = new.initialValue(true)
+                val newValue = new.initialValue()
                 val newString = if (newValue is PsiField) {
                     newValue.containingClass?.qualifiedName + "." + newValue.name
                 } else {
@@ -645,13 +649,18 @@
         val oldVisibility = oldModifiers.getVisibilityString()
         val newVisibility = newModifiers.getVisibilityString()
         if (oldVisibility != newVisibility) {
-            // TODO: Use newModifiers.asAccessibleAs(oldModifiers) to provide different error messages
-            // based on whether this seems like a reasonable change, e.g. making a private or final method more
-            // accessible is fine (no overridden method affected) but not making methods less accessible etc
-            report(
-                Issues.CHANGED_SCOPE, new,
-                "${describe(new, capitalize = true)} changed visibility from $oldVisibility to $newVisibility"
-            )
+            // Only report issue if the change is a decrease in access; e.g. public -> protected
+            if (!newModifiers.asAccessibleAs(oldModifiers)) {
+                report(
+                    Issues.CHANGED_SCOPE, new,
+                    "${
+                    describe(
+                        new,
+                        capitalize = true
+                    )
+                    } changed visibility from $oldVisibility to $newVisibility"
+                )
+            }
         }
 
         if (oldModifiers.isStatic() != newModifiers.isStatic()) {
@@ -664,18 +673,16 @@
             report(
                 Issues.ADDED_FINAL, new, "${describe(new, capitalize = true)} has added 'final' qualifier"
             )
-        } else if (oldModifiers.isFinal() && !newModifiers.isFinal()) {
+        } else if (
+            // Final can't be removed if field is static with compile-time constant
+            oldModifiers.isFinal() && !newModifiers.isFinal() &&
+            oldModifiers.isStatic() && old.initialValue() != null
+        ) {
             report(
                 Issues.REMOVED_FINAL, new, "${describe(new, capitalize = true)} has removed 'final' qualifier"
             )
         }
 
-        if (oldModifiers.isTransient() != newModifiers.isTransient()) {
-            report(
-                Issues.CHANGED_TRANSIENT, new, "${describe(new, capitalize = true)} has changed 'transient' qualifier"
-            )
-        }
-
         if (oldModifiers.isVolatile() != newModifiers.isVolatile()) {
             report(
                 Issues.CHANGED_VOLATILE, new, "${describe(new, capitalize = true)} has changed 'volatile' qualifier"
diff --git a/src/main/java/com/android/tools/metalava/Issues.kt b/src/main/java/com/android/tools/metalava/Issues.kt
index a0ea637..231d31c 100644
--- a/src/main/java/com/android/tools/metalava/Issues.kt
+++ b/src/main/java/com/android/tools/metalava/Issues.kt
@@ -44,13 +44,14 @@
     val CHANGED_SUPERCLASS = Issue(Severity.ERROR, Category.COMPATIBILITY)
     val CHANGED_SCOPE = Issue(Severity.ERROR, Category.COMPATIBILITY)
     val CHANGED_ABSTRACT = Issue(Severity.ERROR, Category.COMPATIBILITY)
+    val CHANGED_DEFAULT = Issue(Severity.ERROR, Category.COMPATIBILITY)
     val CHANGED_THROWS = Issue(Severity.ERROR, Category.COMPATIBILITY)
     val CHANGED_NATIVE = Issue(Severity.HIDDEN, Category.COMPATIBILITY)
     val CHANGED_CLASS = Issue(Severity.ERROR, Category.COMPATIBILITY)
     val CHANGED_DEPRECATED = Issue(Severity.HIDDEN, Category.COMPATIBILITY)
     val CHANGED_SYNCHRONIZED = Issue(Severity.HIDDEN, Category.COMPATIBILITY)
     val ADDED_FINAL_UNINSTANTIABLE = Issue(Severity.HIDDEN, Category.COMPATIBILITY)
-    val REMOVED_FINAL = Issue(Severity.HIDDEN, Category.COMPATIBILITY)
+    val REMOVED_FINAL = Issue(Severity.ERROR, Category.COMPATIBILITY)
     val REMOVED_DEPRECATED_CLASS = Issue(REMOVED_CLASS, Category.COMPATIBILITY)
     val REMOVED_DEPRECATED_METHOD = Issue(REMOVED_METHOD, Category.COMPATIBILITY)
     val REMOVED_DEPRECATED_FIELD = Issue(REMOVED_FIELD, Category.COMPATIBILITY)
diff --git a/src/main/java/com/android/tools/metalava/model/FieldItem.kt b/src/main/java/com/android/tools/metalava/model/FieldItem.kt
index a80df56..092c86d 100644
--- a/src/main/java/com/android/tools/metalava/model/FieldItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/FieldItem.kt
@@ -87,8 +87,8 @@
      * to accommodate toolchains with different fp -> string conversions.
      */
     fun hasSameValue(other: FieldItem): Boolean {
-        val thisConstant = initialValue(true)
-        val otherConstant = other.initialValue(true)
+        val thisConstant = initialValue()
+        val otherConstant = other.initialValue()
         if (thisConstant == null != (otherConstant == null)) {
             return false
         }
diff --git a/src/main/java/com/android/tools/metalava/model/MethodItem.kt b/src/main/java/com/android/tools/metalava/model/MethodItem.kt
index ea5489a..e4eb38e 100644
--- a/src/main/java/com/android/tools/metalava/model/MethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/MethodItem.kt
@@ -412,19 +412,6 @@
     }
 
     /**
-     * Check the declared default annotation value and return true if the defaults
-     * are the same. Only defined on two annotation methods; for all other
-     * methods the result is "true".
-     */
-    fun hasSameValue(other: MethodItem): Boolean {
-        if (!containingClass().isAnnotationType() || !other.containingClass().isAnnotationType()) {
-            return true
-        }
-
-        return defaultValue() == other.defaultValue()
-    }
-
-    /**
      * Returns true if this method is a signature match for the given method (e.g. can
      * be overriding). This checks that the name and parameter lists match, but ignores
      * differences in parameter names, return value types and throws list types.
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
index 5c8b1e3..499ca7f 100644
--- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
@@ -787,7 +787,6 @@
                 src/test/pkg/Parent.java:6: error: Field test.pkg.Parent.field3 has changed type from int to char [ChangedType]
                 src/test/pkg/Parent.java:7: error: Field test.pkg.Parent.field4 has added 'final' qualifier [AddedFinal]
                 src/test/pkg/Parent.java:8: error: Field test.pkg.Parent.field5 has changed 'static' qualifier [ChangedStatic]
-                src/test/pkg/Parent.java:9: error: Field test.pkg.Parent.field6 has changed 'transient' qualifier [ChangedTransient]
                 src/test/pkg/Parent.java:10: error: Field test.pkg.Parent.field7 has changed 'volatile' qualifier [ChangedVolatile]
                 src/test/pkg/Parent.java:20: error: Field test.pkg.Parent.field94 has changed value from 1 to 42 [ChangedValue]
                 """,
@@ -1104,28 +1103,62 @@
     }
 
     @Test
-    fun `Incompatible class change -- type variables`() {
+    fun `allow adding first type parameter`() {
         check(
-            expectedIssues = """
-                src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 changed number of type parameters from 1 to 2 [ChangedType]
-                """,
             checkCompatibilityApiReleased = """
                 package test.pkg {
-                  public class Class1<X> {
-                  }
-                }
-                """,
-            sourceFiles = arrayOf(
-                java(
-                    """
-                    package test.pkg;
-
-                    public class Class1<X,Y> {
-                        private Class1() {}
+                    public class Foo {
                     }
-                    """
-                )
-            )
+                }
+            """,
+            signatureSource = """
+                package test.pkg {
+                    public class Foo<T> {
+                    }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `disallow removing type parameter`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:2: error: Class test.pkg.Foo changed number of type parameters from 1 to 0 [ChangedType]
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                    public class Foo<T> {
+                    }
+                }
+            """,
+            signatureSource = """
+                package test.pkg {
+                    public class Foo {
+                    }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `disallow changing number of type parameters`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:2: error: Class test.pkg.Foo changed number of type parameters from 1 to 2 [ChangedType]
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                    public class Foo<A> {
+                    }
+                }
+            """,
+            signatureSource = """
+                package test.pkg {
+                    public class Foo<A,B> {
+                    }
+                }
+            """
         )
     }
 
@@ -1341,7 +1374,6 @@
     fun `Incompatible field change -- visibility and removing final`() {
         check(
             expectedIssues = """
-                src/test/pkg/MyClass.java:5: error: Field test.pkg.MyClass.myField1 changed visibility from protected to public [ChangedScope]
                 src/test/pkg/MyClass.java:6: error: Field test.pkg.MyClass.myField2 changed visibility from public to protected [ChangedScope]
                 """,
             checkCompatibilityApiReleased = """
@@ -3600,6 +3632,59 @@
     }
 
     @Test
+    fun `Allow increased field access for classes`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    field public int bar;
+                    field protected int baz;
+                    field protected int spam;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    field protected int bar;
+                    field private int baz;
+                    field internal int spam;
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Block decreased field access in classes`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Field test.pkg.Foo.bar changed visibility from public to protected [ChangedScope]
+                TESTROOT/load-api.txt:4: error: Field test.pkg.Foo.baz changed visibility from protected to private [ChangedScope]
+                TESTROOT/load-api.txt:5: error: Field test.pkg.Foo.spam changed visibility from protected to internal [ChangedScope]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    field protected int bar;
+                    field private int baz;
+                    field internal int spam;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    field public int bar;
+                    field protected int baz;
+                    field protected int spam;
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
     fun `Allow increased access`() {
         check(
             signatureSource = """
@@ -3723,6 +3808,28 @@
         )
     }
 
+    fun `Change default to abstract`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed 'default' qualifier [ChangedDefault]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method abstract public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method default public void bar(Int);
+                    }
+                  }
+            """
+        )
+    }
+
     // TODO: Check method signatures changing incompatibly (look especially out for adding new overloaded
     // methods and comparator getting confused!)
     //   ..equals on the method items should actually be very useful!
diff --git a/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassFieldsTest.kt b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassFieldsTest.kt
new file mode 100644
index 0000000..eba3ab5
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassFieldsTest.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.tools.metalava.binarycompatibility
+
+import com.android.tools.metalava.DriverTest
+import org.junit.Test
+
+class BinaryCompatibilityClassFieldsTest : DriverTest() {
+
+    @Test
+    fun `Change type of API field (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Field test.pkg.Foo.bar has changed type from java.lang.String to int [ChangedType]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field public int bar;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field public String bar;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change value of API field, compile-time constant (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Field test.pkg.Foo.bar has changed value from 8 to 7 [ChangedValue]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field public static final int bar = 7;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field public static final int bar = 8;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Decrease access from protected to default or private, or public to protected, default, or private (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Field test.pkg.Foo.bar changed visibility from protected to private [ChangedScope]
+                TESTROOT/load-api.txt:4: error: Field test.pkg.Foo.baz changed visibility from public to protected [ChangedScope]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field private static final int bar = 8;
+                    field protected static final int baz = 8;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field protected static final int bar = 8;
+                    field public static final int baz = 8;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Increase access, eg from protected to public (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field protected static final int bar = 8;
+                    field public static final int baz = 8;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field private static final int bar = 8;
+                    field protected static final int baz = 8;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change final to non-final, non-static (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field public int bar;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field public final int bar;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change final to non-final, static with compile-time constant value (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Field test.pkg.Foo.bar has removed 'final' qualifier [RemovedFinal]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field public static int bar = 0;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field public static final int bar = 0;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change non-final to final (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Field test.pkg.Foo.bar has added 'final' qualifier [AddedFinal]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field public final int bar;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field public int bar;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change static to non-static (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Field test.pkg.Foo.bar has changed 'static' qualifier [ChangedStatic]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field public int bar = 0;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field public static int bar = 0;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change non-static to static (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Field test.pkg.Foo.bar has changed 'static' qualifier [ChangedStatic]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field public static int bar = 0;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field public int bar = 0;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change transient to non-transient (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field public int bar = 0;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field public transient int bar = 0;
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change non-transient to transient (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  public class Foo {
+                    field public transient int bar = 0;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Foo {
+                    field public int bar = 0;
+                  }
+                }
+            """
+        )
+    }
+}
diff --git a/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassesTest.kt b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassesTest.kt
index 0faf40b..1765f17 100644
--- a/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassesTest.kt
+++ b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassesTest.kt
@@ -552,12 +552,11 @@
         )
     }
 
-    @Ignore("b/217746739")
     @Test
     fun `Delete type parameter (Incompatible)`() {
         check(
             expectedIssues = """
-                (expected issue for class Bar)
+                TESTROOT/load-api.txt:2: error: Class test.pkg.Bar changed number of type parameters from 1 to 0 [ChangedType]
                 TESTROOT/load-api.txt:4: error: Class test.pkg.Foo changed number of type parameters from 2 to 1 [ChangedType]
             """,
             signatureSource = """
diff --git a/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityInterfaceMethodsTest.kt b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityInterfaceMethodsTest.kt
new file mode 100644
index 0000000..168ea59
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityInterfaceMethodsTest.kt
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.tools.metalava.binarycompatibility
+
+import com.android.tools.metalava.DriverTest
+import org.junit.Test
+
+class BinaryCompatibilityInterfaceMethodsTest : DriverTest() {
+
+    @Test
+    // Note: This is reversed from the eclipse wiki because of kotlin named parameters
+    fun `Change formal parameter name (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Attempted to change parameter name from bread to toast in method test.pkg.Foo.bar [ParameterNameChange]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int toast);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int bread);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change method name (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/released-api.txt:3: error: Removed method test.pkg.Foo.bar(int) [RemovedMethod]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method public void baz(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Add or delete formal parameter (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/released-api.txt:3: error: Removed method test.pkg.Foo.bar(int) [RemovedMethod]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar();
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change type of a formal parameter (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/released-api.txt:3: error: Removed method test.pkg.Foo.bar(int) [RemovedMethod]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(Float);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change result type (including void) (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed return type from void to int [ChangedType]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method public int bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Add checked exceptions thrown (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar added thrown exception java.lang.Throwable [ChangedThrows]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int) throws java.lang.Throwable;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Delete checked exceptions thrown (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar no longer throws exception java.lang.Throwable [ChangedThrows]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int) throws java.lang.Throwable;
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Re-order list of exceptions thrown (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int) throws java.lang.Exception, java.lang.Throwable;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int) throws java.lang.Throwable, java.lang.Exception;
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change static to non-static (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed 'static' qualifier [ChangedStatic]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method static public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change non-static to static (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed 'static' qualifier [ChangedStatic]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method static public void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change default to abstract (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed 'default' qualifier [ChangedDefault]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method abstract public void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method default public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change abstract to default (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method default public void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method abstract public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    /*
+    TODO: Fix b/217229076 and uncomment this block of tests
+
+    @Test
+    fun `Add type parameter, no existing type parameters (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Add type parameter, existing type parameters (Incompatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  interface Foo {
+                    method public <T, K> void bar();
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  interface Foo {
+                    method public <T> void bar();
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Delete type parameter (Incompatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Re-order type parameters (Incompatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T, K> void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <K, T> void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Rename type parameter (Compatible)`() {
+         check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <K> void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Add, delete, or change type bounds of type parameter (Incompatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T extends Foo> void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change last parameter from array type T(array) to variable arity T(elipse) (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(T...);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(T[]);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change last parameter from variable arity T(elipse) to array type T(array) (Incompatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(T[]);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(T...);
+                  }
+                }
+            """
+        )
+    }
+
+     */
+
+    @Test
+    fun `Add default clause to annotation type element (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  public @interface Foo {
+                    method public void bar(int) default 0;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public @interface Foo {
+                    method public void bar(int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    /**
+     * Note: While this is technically binary compatible, it's bad API design to allow.
+     * Thus, we continue to flag this as an error.
+     */
+    fun `Change default clause on annotation type element (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed value from 0 to 1 [ChangedValue]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  public @interface Foo {
+                    method public void bar(int) default 1;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public @interface Foo {
+                    method public void bar(int) default 0;
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Delete default clause from annotation type element (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed value from 0 to nothing [ChangedValue]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  public @interface Foo {
+                    method public void bar(int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public @interface Foo {
+                    method public void bar(int) default 0;
+                  }
+                }
+            """
+        )
+    }
+}
diff --git a/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityInterfacesTest.kt b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityInterfacesTest.kt
index 9e335ea..1e416a6 100644
--- a/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityInterfacesTest.kt
+++ b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityInterfacesTest.kt
@@ -412,12 +412,11 @@
         )
     }
 
-    @Ignore("b/217746739")
     @Test
     fun `Delete type parameter (Incompatible)`() {
         check(
             expectedIssues = """
-                (expected issue for interface Foo)
+                TESTROOT/load-api.txt:2: error: Class test.pkg.Foo changed number of type parameters from 1 to 0 [ChangedType]
             """,
             signatureSource = """
                 package test.pkg {