From 942042f9cf19040dabc61378bb50ec484fc39a88 Mon Sep 17 00:00:00 2001
From: Tor Norbye <tnorbye@google.com>
Date: Wed, 2 Jan 2019 10:02:56 -0800
Subject: [PATCH] Add more conversion flags to metalava, etc

First, this CL makes it easier to convert between v1 and v2 versions
of the signature files:

--convert-to-v1 <sig> <sig> Reads in the given signature file and
                            writes it out as a signature file in the
                            original v1/doclava format.

--convert-to-v2 <sig> <sig> Reads in the given signature file and
                            writes it out as a signature file in the
                            new signature format, v2.

--convert-new-to-v2 <old> <new> <sig> Reads in the given old and new
                            api files, computes the difference, and
                            writes out only the new parts of the API
                            in the v2 format.

Second, this cleans up a few issues around compatibility flag handling
(a few things were tweaked by options instead of compatibility flag),
and more importantly, make sure that we enable kotlin-style-nulls
based on the API file header format version, not (just) the command
line options.  Also, during signature file parsing, treat interfaces
as implicitly abstract.  This is important during JDiff export. This
CL also emulates a few more compatibility behaviors with doclava's
JDiff export.

Third, parameter names should not be included in v1 files (they
accidentally were, though only for Kotlin since Java normally does not
have parameter names, unless specified with as special annotation).

Fourth, this CL removes a few unnecessary entries from signature
files: those that differ only by final or deprecated from the parent
signature where the surrounding class is final or deprecated
(b/122358225), or where they differ in synchronization.

Finally, this CL fixes some issues around the handling of type erasure
in throws statements (fixes b/110302703), and removes some special
cases of the Android API which is no longer necessary and removes some
unused compat code (e.g. around native and strictfp.)

Test: Unit test updated
Change-Id: I7b7023149ccbb90b8d30e095c713af9957105023
---
 .idea/dictionaries/metalava.xml               |   2 +
 API-LINT.md                                   |  18 +-
 FORMAT.md                                     |  14 +
 .../com/android/tools/metalava/ApiLint.kt     | 121 +--
 .../com/android/tools/metalava/Baseline.kt    |  11 +-
 .../android/tools/metalava/Compatibility.kt   |  79 +-
 .../tools/metalava/CompatibilityCheck.kt      |  17 +-
 .../java/com/android/tools/metalava/Driver.kt |  57 +-
 .../com/android/tools/metalava/FileFormat.kt  | 147 ++++
 .../android/tools/metalava/JDiffXmlWriter.kt  |  96 ++-
 .../com/android/tools/metalava/Options.kt     | 131 ++-
 .../com/android/tools/metalava/Reporter.kt    |   6 +-
 .../android/tools/metalava/SignatureFormat.kt |  27 -
 .../android/tools/metalava/SignatureWriter.kt |  11 +-
 .../tools/metalava/doclava1/ApiFile.java      |  50 +-
 .../tools/metalava/doclava1/TextCodebase.kt   |   9 +-
 .../android/tools/metalava/model/ClassItem.kt |   8 +-
 .../metalava/model/DefaultModifierList.kt     |  30 +-
 .../tools/metalava/model/MethodItem.kt        |   7 +-
 .../tools/metalava/model/ModifierList.kt      |  32 +-
 .../android/tools/metalava/model/TypeItem.kt  |   3 +-
 .../metalava/model/TypeParameterListOwner.kt  |   3 +
 .../tools/metalava/model/psi/CodePrinter.kt   |  16 +-
 .../metalava/model/psi/PsiBasedCodebase.kt    |   3 +-
 .../tools/metalava/model/psi/PsiClassItem.kt  |   3 +-
 .../metalava/model/text/TextClassItem.kt      |   4 +
 .../metalava/model/text/TextMethodItem.kt     |   4 +
 .../tools/metalava/model/text/TextTypeItem.kt |   1 +
 src/main/resources/version.properties         |   2 +-
 .../com/android/tools/metalava/ApiFileTest.kt | 175 +++-
 .../android/tools/metalava/ApiFromTextTest.kt |  67 +-
 .../android/tools/metalava/BaselineTest.kt    |  79 ++
 .../tools/metalava/CompatibilityCheckTest.kt  |   7 +-
 .../com/android/tools/metalava/ConvertTest.kt | 778 ++++++++++++++++++
 .../com/android/tools/metalava/DriverTest.kt  |  68 +-
 .../tools/metalava/ExtractAnnotationsTest.kt  |  15 +-
 .../android/tools/metalava/FileFormatTest.kt  | 105 +++
 .../android/tools/metalava/JDiffXmlTest.kt    | 637 ++++----------
 .../tools/metalava/NullnessMigrationTest.kt   |   5 +-
 .../com/android/tools/metalava/OptionsTest.kt |   9 +
 .../tools/metalava/ShowAnnotationTest.kt      |   2 +-
 .../com/android/tools/metalava/StubsTest.kt   |  16 +-
 .../tools/metalava/model/TypeItemTest.kt      |   4 +-
 .../metalava/model/text/TextTypeItemTest.kt   |   6 +-
 .../model/text/TextTypeParameterItemTest.kt   |   2 +-
 45 files changed, 2008 insertions(+), 879 deletions(-)
 create mode 100644 src/main/java/com/android/tools/metalava/FileFormat.kt
 delete mode 100644 src/main/java/com/android/tools/metalava/SignatureFormat.kt
 create mode 100644 src/test/java/com/android/tools/metalava/ConvertTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/FileFormatTest.kt

diff --git a/.idea/dictionaries/metalava.xml b/.idea/dictionaries/metalava.xml
index ce2b5a0..e458e6a 100644
--- a/.idea/dictionaries/metalava.xml
+++ b/.idea/dictionaries/metalava.xml
@@ -22,6 +22,7 @@
       <w>doclet</w>
       <w>docletpath</w>
       <w>doconly</w>
+      <w>doesnt</w>
       <w>dokka</w>
       <w>droiddoc</w>
       <w>federationapi</w>
@@ -60,6 +61,7 @@
       <w>nullable</w>
       <w>nullness</w>
       <w>offlinemode</w>
+      <w>parcelable</w>
       <w>parsecomments</w>
       <w>prebuilts</w>
       <w>proguard</w>
diff --git a/API-LINT.md b/API-LINT.md
index f0ab779..76b2d09 100644
--- a/API-LINT.md
+++ b/API-LINT.md
@@ -66,6 +66,22 @@ diff to make sure you're really only marking the issues you intended to include.
 
           baseline.txt
 
+## Copying File Manually
+
+In the near future the build system will not allow source files to be modified
+by the build. At that point you'll need to manually copy in the file instead.
+During the build, before failing, metalava will emit a message like this:
+
+    ...
+    metalava wrote updated baseline to out/something/baseline.txt
+    ...
+
+At that point you can copy the file to where you need:
+
+```sh
+$ cp out/something/baseline.txt frameworks/base/api/baseline.txt
+```
+
 ## Existing Issues
 
 You can view the exact set of existing issues (current APIs that get flagged by
@@ -146,4 +162,4 @@ here's the existing distribution as of early 2019:
     --------------------------------------------------
      4902
 
-(This is generated when metalava is invoked with both --verbose and --update-baseline.)
\ No newline at end of file
+(This is generated when metalava is invoked with both --verbose and --update-baseline.)
diff --git a/FORMAT.md b/FORMAT.md
index efe73b9..d27c867 100644
--- a/FORMAT.md
+++ b/FORMAT.md
@@ -480,6 +480,20 @@ java.lang.reflect.Method will **not** be shortened to reflect.Method.
 In v3, "type use annotations" are supported which means annotations can appear
 within types.
 
+### Skipping some signatures
+
+If a method overrides another method, and the signatures are the same, the
+overriding method is left out of the signature file. This basically compares the
+modifiers, ignoring some that are not API significant (such as "native"). Note
+also that some modifiers are implicit; for example, if a method is implementing
+a method from an interface, the interface method is implicitly abstract, so the
+implementation will be included in the signature file.
+
+In v2, we take this one step further: If a method differs **only** from its
+overridden method by "final", **and** if the containing class is final, then the
+method is not included in the signature file. The same is the case for
+deprecated.
+
 ### Miscellaneous
 
 Some other minor tweaks in v2:
diff --git a/src/main/java/com/android/tools/metalava/ApiLint.kt b/src/main/java/com/android/tools/metalava/ApiLint.kt
index 2290f31..aa1e432 100644
--- a/src/main/java/com/android/tools/metalava/ApiLint.kt
+++ b/src/main/java/com/android/tools/metalava/ApiLint.kt
@@ -143,75 +143,82 @@ import java.util.function.Predicate
  * The [ApiLint] analyzer checks the API against a known set of preferred API practices
  * by the Android API council.
  */
-class ApiLint {
-    fun check(codebase: Codebase) {
-        val prevCount = reporter.totalCount
-
-        // The previous Kotlin interop tests are also part of API lint now (though they can be
-        // run independently as well; therefore, only run them here if not running separately)
-        val kotlinInterop = if (!options.checkKotlinInterop) KotlinInteropChecks() else null
+class ApiLint(private var codebase: Codebase) : ApiVisitor(
+    // Sort by source order such that warnings follow source line number order
+    methodComparator = MethodItem.sourceOrderComparator,
+    fieldComparator = FieldItem.comparator,
+    ignoreShown = options.showUnannotated
+) {
+    private fun report(id: Error, item: Item, message: String) {
+        // Don't flag api warnings on deprecated APIs; these are obviously already known to
+        // be problematic.
+        if (item.deprecated) {
+            return
+        }
 
-        codebase.accept(object : ApiVisitor(
-            // Sort by source order such that warnings follow source line number order
-            methodComparator = MethodItem.sourceOrderComparator,
-            fieldComparator = FieldItem.comparator
-        ) {
-            override fun skip(item: Item): Boolean {
-                return super.skip(item) || item is ClassItem && !isInteresting(item)
-            }
-
-            private var isKotlin = false
-
-            override fun visitClass(cls: ClassItem) {
-                val methods = cls.filteredMethods(filterEmit).asSequence()
-                val fields = cls.filteredFields(filterEmit, showUnannotated).asSequence()
-                val constructors = cls.filteredConstructors(filterEmit)
-                val superClass = cls.filteredSuperclass(filterReference)
-                val interfaces = cls.filteredInterfaceTypes(filterReference).asSequence()
-                val allMethods = methods.asSequence() + constructors.asSequence()
-                checkClass(
-                    cls, methods, constructors, allMethods, fields, superClass, interfaces,
-                    filterReference
-                )
+        // With show annotations we might be flagging API that is filtered out: hide these here
+        val testItem = if (item is ParameterItem) item.containingMethod() else item
+        if (!filterEmit.test(testItem)) {
+            return
+        }
 
-                isKotlin = cls.isKotlin()
-            }
+        reporter.report(id, item, message)
+    }
 
-            override fun visitMethod(method: MethodItem) {
-                checkMethod(method, filterReference)
-                val returnType = method.returnType()
-                if (returnType != null) {
-                    checkType(returnType, method)
-                }
-                for (parameter in method.parameters()) {
-                    checkType(parameter.type(), parameter)
-                }
-                kotlinInterop?.checkMethod(method, isKotlin)
-            }
+    private fun check() {
+        val prevCount = reporter.totalCount
 
-            override fun visitField(field: FieldItem) {
-                checkField(field)
-                checkType(field.type(), field)
-                kotlinInterop?.checkField(field, isKotlin)
-            }
-        })
+        codebase.accept(this)
 
         val apiLintIssues = reporter.totalCount - prevCount
         if (apiLintIssues > 0) {
             // We've reported API lint violations; emit some verbiage to explain
             // how to suppress the error rules.
-            options.stdout.println("$apiLintIssues new API lint issues were found. See tools/metalava/API-LINT.md for how to handle these.")
+            options.stdout.println("\n$apiLintIssues new API lint issues were found. See tools/metalava/API-LINT.md for how to handle these.")
         }
     }
 
-    private fun report(id: Error, item: Item, message: String) {
-        // Don't flag api warnings on deprecated APIs; these are obviously already known to
-        // be problematic.
-        if (item.deprecated) {
-            return
+    override fun skip(item: Item): Boolean {
+        return super.skip(item) || item is ClassItem && !isInteresting(item)
+    }
+
+    // The previous Kotlin interop tests are also part of API lint now (though they can be
+    // run independently as well; therefore, only run them here if not running separately)
+    private val kotlinInterop = if (!options.checkKotlinInterop) KotlinInteropChecks() else null
+
+    private var isKotlin = false
+
+    override fun visitClass(cls: ClassItem) {
+        val methods = cls.filteredMethods(filterReference).asSequence()
+        val fields = cls.filteredFields(filterReference, showUnannotated).asSequence()
+        val constructors = cls.filteredConstructors(filterReference)
+        val superClass = cls.filteredSuperclass(filterReference)
+        val interfaces = cls.filteredInterfaceTypes(filterReference).asSequence()
+        val allMethods = methods.asSequence() + constructors.asSequence()
+        checkClass(
+            cls, methods, constructors, allMethods, fields, superClass, interfaces,
+            filterReference
+        )
+
+        isKotlin = cls.isKotlin()
+    }
+
+    override fun visitMethod(method: MethodItem) {
+        checkMethod(method, filterReference)
+        val returnType = method.returnType()
+        if (returnType != null) {
+            checkType(returnType, method)
+        }
+        for (parameter in method.parameters()) {
+            checkType(parameter.type(), parameter)
         }
+        kotlinInterop?.checkMethod(method, isKotlin)
+    }
 
-        reporter.report(id, item, message)
+    override fun visitField(field: FieldItem) {
+        checkField(field)
+        checkType(field.type(), field)
+        kotlinInterop?.checkField(field, isKotlin)
     }
 
     private fun checkType(type: TypeItem, item: Item) {
@@ -3288,5 +3295,9 @@ class ApiLint {
                 }
             }
         }
+
+        fun check(codebase: Codebase) {
+            ApiLint(codebase).check()
+        }
     }
 }
diff --git a/src/main/java/com/android/tools/metalava/Baseline.kt b/src/main/java/com/android/tools/metalava/Baseline.kt
index 885e99a..6867bf7 100644
--- a/src/main/java/com/android/tools/metalava/Baseline.kt
+++ b/src/main/java/com/android/tools/metalava/Baseline.kt
@@ -37,7 +37,13 @@ import java.io.PrintWriter
 
 const val DEFAULT_BASELINE_NAME = "baseline.txt"
 
-class Baseline(val file: File, var create: Boolean = !file.isFile) {
+class Baseline(
+    val file: File,
+    var create: Boolean = !file.isFile,
+    private var format: FileFormat = FileFormat.BASELINE,
+    private var headerComment: String = ""
+) {
+
     /** Map from issue id to element id to message */
     private val map = HashMap<Errors.Error, MutableMap<String, String>>()
 
@@ -193,6 +199,9 @@ class Baseline(val file: File, var create: Boolean = !file.isFile) {
     private fun write() {
         if (!map.isEmpty()) {
             val sb = StringBuilder()
+            sb.append(format.header())
+            sb.append(headerComment)
+
             map.keys.asSequence().sortedBy { it.name ?: it.code.toString() }.forEach { error ->
                 val idMap = map[error]
                 idMap?.keys?.sorted()?.forEach { elementId ->
diff --git a/src/main/java/com/android/tools/metalava/Compatibility.kt b/src/main/java/com/android/tools/metalava/Compatibility.kt
index 5594e4a..32f5a1e 100644
--- a/src/main/java/com/android/tools/metalava/Compatibility.kt
+++ b/src/main/java/com/android/tools/metalava/Compatibility.kt
@@ -31,9 +31,6 @@ class Compatibility(
     val compat: Boolean = COMPAT_MODE_BY_DEFAULT
 ) {
 
-    /** Whether to inline fields from implemented interfaces into concrete classes */
-    var inlineInterfaceFields: Boolean = compat
-
     /** In signature files, use "implements" instead of "extends" for the super class of
      * an interface */
     var extendsForInterfaceSuperClass: Boolean = compat
@@ -57,12 +54,6 @@ class Compatibility(
      * `@Deprecated` annotation before the modifier list */
     var nonstandardModifierOrder: Boolean = compat
 
-    /** In signature files, skip the native modifier from the modifier lists */
-    var skipNativeModifier: Boolean = true
-
-    /** In signature files, skip the strictfp modifier from the modifier lists */
-    var skipStrictFpModifier: Boolean = true
-
     /** Whether to include instance methods in annotation classes for the annotation properties */
     var skipAnnotationInstanceMethods: Boolean = compat
 
@@ -96,47 +87,14 @@ class Compatibility(
      */
     var filterThrowsClasses: Boolean = !compat
 
-    /**
-     * Include a single space in front of package private classes with no other modifiers
-     * (this doesn't align well, but is supported to make the output 100% identical to the
-     * doclava1 format
-     */
-    var extraSpaceForEmptyModifiers: Boolean = compat
-
     /** Format `Map<K,V>` as `Map<K, V>` */
     var spaceAfterCommaInTypes: Boolean = compat
 
-    /**
-     * Doclava1 sorts classes/interfaces by class name instead of qualified name
-     */
-    var sortClassesBySimpleName: Boolean = compat
-
     /**
      * Doclava1 omits type parameters in interfaces (in signature files, not in stubs)
      */
     var omitTypeParametersInInterfaces: Boolean = compat
 
-    /**
-     * Doclava1 sorted the methods like this:
-     *
-     *      public final class RoundingMode extends java.lang.Enum {
-     *          method public static java.math.RoundingMode valueOf(java.lang.String);
-     *          method public static java.math.RoundingMode valueOf(int);
-     *          ...
-     *
-     * Note how the two valueOf methods are out of order. With this compatibility mode,
-     * we try to perform the same sorting.
-     */
-    var sortEnumValueOfMethodFirst: Boolean = compat
-
-    /**
-     * Whether packages should be treated as recursive for documentation. In other words,
-     * if a directory has a `packages.html` file containing a `@hide` comment, then
-     * all "sub" packages (directories below this one) will also inherit the same comment.
-     * Java packages aren't supposed to work that way, but doclava does.
-     */
-    var inheritPackageDocs: Boolean = compat
-
     /** Force methods named "values" in enums to be marked final. This was done by
      * doclava1 with this comment:
      *
@@ -175,7 +133,7 @@ class Compatibility(
     /**
      * Whether to include parameter names in the signature file
      */
-    var parameterNames: Boolean = true
+    var parameterNames: Boolean = !compat
 
     /**
      * *Some* signatures for doclava1 wrote "<?>" as "<? extends java.lang.Object>",
@@ -184,6 +142,21 @@ class Compatibility(
      */
     var includeExtendsObjectInWildcard = compat
 
+    /**
+     * Whether deprecation should be shown in signature files as an annotation
+     * instead of a pseudo-modifier
+     */
+    var deprecatedAsAnnotation = !compat
+
+    /** Whether synchronized should be part of the output */
+    var includeSynchronized = compat
+
+    /** Whether we should omit common packages such as java.lang.* and kotlin.* from signature output */
+    var omitCommonPackages = !compat
+
+    /** Whether we should explicitly include retention when class even if not explicitly defined */
+    var explicitlyListClassRetention = !compat
+
     /**
      * If true, a @Deprecated class will automatically deprecate all its inner classes
      * as well.
@@ -196,6 +169,12 @@ class Compatibility(
      */
     var propagateDeprecatedMembers = !compat
 
+    /**
+     * If an overriding method differs from its super method only by final or deprecated
+     * and the containing class is final or deprecated, skip it in the signature file
+     */
+    var hideDifferenceImplicit = !compat
+
     /** Whether inner enums should be listed as static in the signature file. */
     var staticEnums = compat
 
@@ -224,5 +203,19 @@ class Compatibility(
      */
     var xmlSkipEnumFields = compat
 
+    /**
+     * Doclava was missing annotation instance methods in JDiff reports
+     */
+    var xmlSkipAnnotationMethods = compat
+
+    /** Doclava lists character field values as integers instead of chars */
+    var xmlCharAsInt = compat
+
+    /**
+     * Doclava listed the superclass of annotations as
+     * java.lang.Object.
+     */
+    var xmlAnnotationAsObject = compat
+
     // Other examples: sometimes we sort by qualified name, sometimes by full name
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
index dfef0ff..22d359f 100644
--- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
+++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
@@ -77,7 +77,7 @@ class CompatibilityCheck(
      * so in these cases we want to ignore certain changes such as considering
      * StringBuilder.setLength a newly added method.
      */
-    private val comparingWithPartialSignatures = oldCodebase is TextCodebase && oldCodebase.format.major < 2
+    private val comparingWithPartialSignatures = oldCodebase is TextCodebase && oldCodebase.format == FileFormat.V1
 
     var foundProblems = false
 
@@ -710,19 +710,6 @@ class CompatibilityCheck(
         val error = if (new.isInterface()) {
             Errors.ADDED_INTERFACE
         } else {
-            if (options.compatOutput &&
-                new.qualifiedName() == "android.telephony.ims.feature.ImsFeature.Capabilities"
-            ) {
-                // Special case: Doclava and metalava signature files for the system api
-                // differ in only one way: Metalava believes ImsFeature.Capabilities should
-                // be in the signature file for @SystemApi, and doclava does not. However,
-                // this API is referenced from other system APIs that doclava does include
-                // (MmTelFeature.MmTelCapabilities's constructor) so it is clearly part of the
-                // API even if it's not listed in the signature file and we should not list
-                // this as an incompatible, added API.
-                return
-            }
-
             Errors.ADDED_CLASS
         }
         handleAdded(error, new)
@@ -769,7 +756,7 @@ class CompatibilityCheck(
         }
 
         // In old signature files, annotation methods are missing! This will show up as an added method.
-        if (new.containingClass().isAnnotationType() && oldCodebase is TextCodebase && oldCodebase.format.major == 1) {
+        if (new.containingClass().isAnnotationType() && oldCodebase is TextCodebase && oldCodebase.format == FileFormat.V1) {
             return
         }
 
diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt
index e48ae23..0bcc1c9 100644
--- a/src/main/java/com/android/tools/metalava/Driver.kt
+++ b/src/main/java/com/android/tools/metalava/Driver.kt
@@ -155,8 +155,11 @@ fun run(
         exitValue = false
     }
 
-    if (options.verbose && options.updateBaseline) {
-        options.baseline?.dumpStats(options.stdout)
+    if (options.updateBaseline) {
+        if (options.verbose) {
+            options.baseline?.dumpStats(options.stdout)
+        }
+        stdout.println("$PROGRAM_NAME wrote updated baseline to ${options.baseline?.file}")
     }
     options.baseline?.close()
 
@@ -482,23 +485,50 @@ fun processNonCodebaseFlags() {
                     kotlinStyleNulls = options.inputKotlinStyleNulls
                 )
 
-                TextCodebase.computeDelta(baseFile, baseApi, signatureApi)
+                val includeFields =
+                    if (convert.outputFormat == FileFormat.V2) true else compatibility.includeFieldsInApiDiff
+                TextCodebase.computeDelta(baseFile, baseApi, signatureApi, includeFields)
             } else {
                 signatureApi
             }
 
-        if (outputApi.isEmpty() && baseFile != null) {
+        if (outputApi.isEmpty() && baseFile != null && compatibility.compat) {
             // doclava compatibility: emits error warning instead of emitting empty <api/> element
             options.stdout.println("No API change detected, not generating diff")
         } else {
-            val output = convert.toXmlFile
-            if (output.path.endsWith(DOT_TXT)) {
-                createReportFile(outputApi, output, "Diff API File") { printWriter ->
-                    SignatureWriter(printWriter, apiEmit, apiReference, signatureApi.preFiltered && !strip)
+            val output = convert.outputFile
+            if (convert.outputFormat == FileFormat.JDIFF) {
+                // See JDiff's XMLToAPI#nameAPI
+                val apiName = convert.outputFile.nameWithoutExtension.replace(' ', '_')
+                createReportFile(outputApi, output, "JDiff File") { printWriter ->
+                    JDiffXmlWriter(printWriter, apiEmit, apiReference, signatureApi.preFiltered && !strip, apiName)
                 }
             } else {
-                createReportFile(outputApi, output, "JDiff File") { printWriter ->
-                    JDiffXmlWriter(printWriter, apiEmit, apiReference, signatureApi.preFiltered && !strip)
+                val prevOptions = options
+                val prevCompatibility = compatibility
+                try {
+                    when (convert.outputFormat) {
+                        FileFormat.V1 -> {
+                            compatibility = Compatibility(true)
+                            options = Options(emptyArray(), options.stdout, options.stderr)
+                            FileFormat.V1.configureOptions(options, compatibility)
+                        }
+                        FileFormat.V2 -> {
+                            compatibility = Compatibility(false)
+                            options = Options(emptyArray(), options.stdout, options.stderr)
+                            FileFormat.V2.configureOptions(options, compatibility)
+                        }
+                        else -> error("Unsupported format ${convert.outputFormat}")
+                    }
+
+                    createReportFile(outputApi, output, "Diff API File") { printWriter ->
+                        SignatureWriter(
+                            printWriter, apiEmit, apiReference, signatureApi.preFiltered && !strip
+                        )
+                    }
+                } finally {
+                    options = prevOptions
+                    compatibility = prevCompatibility
                 }
             }
         }
@@ -525,7 +555,7 @@ fun checkCompatibility(
             )
         }
 
-    if (current is TextCodebase && current.format.major > 1 && options.outputFormat < 1) {
+    if (current is TextCodebase && current.format > FileFormat.V1 && options.outputFormat == FileFormat.V1) {
         throw DriverException("Cannot perform compatibility check of signature file $signatureFile in format ${current.format} without analyzing current codebase with $ARG_FORMAT=${current.format}")
     }
 
@@ -734,7 +764,7 @@ private fun loadFromSources(): Codebase {
 
     if (options.checkApi) {
         val localTimer = Stopwatch.createStarted()
-        ApiLint().check(codebase)
+        ApiLint.check(codebase)
         progress("\n$PROGRAM_NAME ran api-lint in ${localTimer.elapsed(SECONDS)} seconds")
     }
 
@@ -881,9 +911,6 @@ private fun createStubFiles(stubDir: File, codebase: Codebase, docStubs: Boolean
     val localTimer = Stopwatch.createStarted()
     val prevCompatibility = compatibility
     if (compatibility.compat) {
-        // if (!options.quiet) {
-        //    options.stderr.println("Warning: Turning off compat mode when generating stubs")
-        // }
         compatibility = Compatibility(false)
         // But preserve the setting for whether we want to erase throws signatures (to ensure the API
         // stays compatible)
diff --git a/src/main/java/com/android/tools/metalava/FileFormat.kt b/src/main/java/com/android/tools/metalava/FileFormat.kt
new file mode 100644
index 0000000..7e895e0
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/FileFormat.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 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
+
+import com.android.SdkConstants.DOT_TXT
+import com.android.SdkConstants.DOT_XML
+
+/** File formats that metalava can emit APIs to */
+enum class FileFormat(val description: String, val version: String? = null) {
+    UNKNOWN("?"),
+    JDIFF("JDiff"),
+    BASELINE("Metalava baseline file", "1.0"),
+    SINCE_XML("Metalava API-level file", "1.0"),
+
+    // signature formats should be last to make comparisons work (for example in [configureOptions])
+    V1("Doclava signature file", "1.0"),
+    V2("Metalava signature file", "2.0"),
+    V3("Metalava signature file", "3.0");
+
+    /** Configures the option object such that the output format will be the given format */
+    fun configureOptions(options: Options, compatibility: Compatibility) {
+        if (this == JDIFF) {
+            return
+        }
+        options.outputFormat = this
+        options.compatOutput = this == V1
+        options.outputKotlinStyleNulls = this >= V3
+        options.outputDefaultValues = this >= V2
+        compatibility.omitCommonPackages = this >= V2
+        options.includeSignatureFormatVersion = this >= V2
+    }
+
+    fun useKotlinStyleNulls(): Boolean {
+        return this >= V3
+    }
+
+    fun signatureFormatAsInt(): Int {
+        return when (this) {
+            V1 -> 1
+            V2 -> 2
+            V3 -> 3
+
+            BASELINE,
+            JDIFF,
+            SINCE_XML,
+            UNKNOWN -> error("this method is only allowed on signature formats, was $this")
+        }
+    }
+
+    fun outputFlag(): String {
+        return if (isSignatureFormat()) {
+            "$ARG_FORMAT=v${signatureFormatAsInt()}"
+        } else {
+            ""
+        }
+    }
+
+    fun preferredExtension(): String {
+        return when (this) {
+            V1,
+            V2,
+            V3 -> DOT_TXT
+
+            BASELINE -> DOT_TXT
+
+            JDIFF, SINCE_XML -> DOT_XML
+
+            UNKNOWN -> ""
+        }
+    }
+
+    fun header(): String? {
+        val prefix = headerPrefix() ?: return null
+        return prefix + version + "\n"
+    }
+
+    fun headerPrefix(): String? {
+        return when (this) {
+            V1 -> null
+            V2, V3 -> "// Signature format: "
+            BASELINE -> "// Baseline format: "
+            JDIFF, SINCE_XML -> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+            UNKNOWN -> null
+        }
+    }
+
+    fun isSignatureFormat(): Boolean {
+        return this == V1 || this == V2 || this == V3
+    }
+
+    companion object {
+        private fun firstLine(s: String): String {
+            val index = s.indexOf('\n')
+            if (index == -1) {
+                return s
+            }
+            // Chop off \r if a Windows \r\n file
+            val end = if (index > 0 && s[index - 1] == '\r') index - 1 else index
+            return s.substring(0, end)
+        }
+
+        fun parseHeader(fileContents: String): FileFormat {
+            val firstLine = firstLine(fileContents)
+            for (format in values()) {
+                val header = format.header()
+                if (header == null) {
+                    if (firstLine.startsWith("package ")) {
+                        // Old signature files
+                        return V1
+                    } else if (firstLine.startsWith("<api")) {
+                        return JDIFF
+                    }
+                } else if (header.startsWith(firstLine)) {
+                    if (format == JDIFF) {
+                        if (!fileContents.contains("<api")) {
+                            // The JDIFF header is the general XML header: don't accept XML documents that
+                            // don't contain an empty API definition
+                            return UNKNOWN
+                        }
+                        // Both JDiff and API-level files use <api> as the root tag (unfortunate but too late to
+                        // change) so distinguish on whether the file contains any since elementss
+                        if (fileContents.contains("since=")) {
+                            return SINCE_XML
+                        }
+                    }
+                    return format
+                }
+            }
+
+            return UNKNOWN
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt b/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
index 5f9b323..d4dbe0b 100644
--- a/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
+++ b/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
@@ -44,7 +44,8 @@ class JDiffXmlWriter(
     private val writer: PrintWriter,
     filterEmit: Predicate<Item>,
     filterReference: Predicate<Item>,
-    private val preFiltered: Boolean
+    private val preFiltered: Boolean,
+    private val apiName: String? = null
 ) : ApiVisitor(
     visitConstructorsAsMethods = false,
     nestInnerClasses = false,
@@ -56,7 +57,16 @@ class JDiffXmlWriter(
     showUnannotated = options.showUnannotated
 ) {
     override fun visitCodebase(codebase: Codebase) {
-        writer.println("<api>")
+        writer.print("<api")
+
+        if (apiName != null && !options.compatOutput) {
+            // See JDiff's XMLToAPI#nameAPI
+            writer.print(" name=\"")
+            writer.print(apiName)
+            writer.print("\"")
+        }
+
+        writer.println(">")
     }
 
     override fun afterVisitCodebase(codebase: Codebase) {
@@ -102,6 +112,36 @@ class JDiffXmlWriter(
         writer.println("\"\n>")
 
         writeInterfaceList(cls)
+
+        if (cls.isEnum() && compatibility.defaultEnumMethods) {
+            writer.println(
+                """
+                <method name="valueOf"
+                 return="${cls.qualifiedName()}"
+                 abstract="false"
+                 native="false"
+                 synchronized="false"
+                 static="true"
+                 final="false"
+                 deprecated="not deprecated"
+                 visibility="public"
+                >
+                <parameter name="null" type="java.lang.String">
+                </parameter>
+                </method>
+                <method name="values"
+                 return="${cls.qualifiedName()}[]"
+                 abstract="false"
+                 native="false"
+                 synchronized="false"
+                 static="true"
+                 final="true"
+                 deprecated="not deprecated"
+                 visibility="public"
+                >
+                </method>""".trimIndent()
+            )
+        }
     }
 
     fun deprecation(item: Item): String {
@@ -154,7 +194,11 @@ class JDiffXmlWriter(
         val modifiers = field.modifiers
         val initialValue = field.initialValue(true)
         val value = if (initialValue != null) {
-            escapeAttributeValue(CodePrinter.constantToSource(initialValue))
+            if (initialValue is Char && compatibility.xmlCharAsInt) {
+                initialValue.toInt().toString()
+            } else {
+                escapeAttributeValue(CodePrinter.constantToSource(initialValue))
+            }
         } else null
 
         val fullTypeName = escapeAttributeValue(field.type().toTypeString())
@@ -170,9 +214,10 @@ class JDiffXmlWriter(
         if (value != null) {
             writer.print("\"\n value=\"")
             writer.print(value)
-        } else if (compatibility.xmlShowArrayFieldsAsNull && field.type().isArray()) {
+        } else if (compatibility.xmlShowArrayFieldsAsNull && (field.type().isArray())) {
             writer.print("\"\n value=\"null")
         }
+
         writer.print("\"\n static=\"")
         writer.print(modifiers.isStatic())
         writer.print("\"\n final=\"")
@@ -193,6 +238,10 @@ class JDiffXmlWriter(
     override fun visitMethod(method: MethodItem) {
         val modifiers = method.modifiers
 
+        if (method.containingClass().isAnnotationType() && compatibility.xmlSkipAnnotationMethods) {
+            return
+        }
+
         // Note - to match doclava we don't write out the type parameter list
         // (method.typeParameterList()) in JDiff files!
 
@@ -200,7 +249,7 @@ class JDiffXmlWriter(
         writer.print(method.name())
         method.returnType()?.let {
             writer.print("\"\n return=\"")
-            writer.print(escapeAttributeValue(it.toTypeString()))
+            writer.print(escapeAttributeValue(formatType(it)))
         }
         writer.print("\"\n abstract=\"")
         writer.print(modifiers.isAbstract())
@@ -224,12 +273,22 @@ class JDiffXmlWriter(
     }
 
     private fun writeSuperClassAttribute(cls: ClassItem) {
+        if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) {
+            // Written in the interface section instead
+            return
+        }
+
         val superClass = if (preFiltered)
             cls.superClassType()
         else cls.filteredSuperClassType(filterReference)
 
         val superClassString =
             when {
+                cls.isAnnotationType() -> if (compatibility.xmlAnnotationAsObject) {
+                    JAVA_LANG_OBJECT
+                } else {
+                    JAVA_LANG_ANNOTATION
+                }
                 superClass != null -> {
                     // doclava seems to include java.lang.Object for classes but not interfaces
                     if (!cls.isClass() && superClass.isJavaLangObject()) {
@@ -242,7 +301,6 @@ class JDiffXmlWriter(
                         )
                     )
                 }
-                cls.isAnnotationType() -> JAVA_LANG_ANNOTATION
                 cls.isEnum() -> JAVA_LANG_ENUM
                 else -> return
             }
@@ -252,10 +310,17 @@ class JDiffXmlWriter(
     }
 
     private fun writeInterfaceList(cls: ClassItem) {
-        val interfaces = if (preFiltered)
+        var interfaces = if (preFiltered)
             cls.interfaceTypes().asSequence()
         else cls.filteredInterfaceTypes(filterReference).asSequence()
 
+        if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) {
+            val superClassType = cls.superClassType()
+            if (superClassType?.isJavaLangObject() == false) {
+                interfaces += superClassType
+            }
+        }
+
         if (interfaces.any()) {
             interfaces.sortedWith(TypeItem.comparator).forEach { item ->
                 writer.print("<implements name=\"")
@@ -272,12 +337,21 @@ class JDiffXmlWriter(
             // NOTE: We report parameter name as "null" rather than the real name to match
             // doclava's behavior
             writer.print("<parameter name=\"null\" type=\"")
-            writer.print(escapeAttributeValue(parameter.type().toTypeString()))
+            writer.print(escapeAttributeValue(formatType(parameter.type())))
             writer.println("\">")
             writer.println("</parameter>")
         }
     }
 
+    private fun formatType(type: TypeItem): String {
+        val typeString = type.toTypeString()
+        return if (compatibility.spaceAfterCommaInTypes) {
+            typeString.replace(",", ", ").replace(",  ", ", ")
+        } else {
+            typeString
+        }
+    }
+
     private fun writeThrowsList(method: MethodItem) {
         val throws = when {
             preFiltered -> method.throwsTypes().asSequence()
@@ -287,7 +361,11 @@ class JDiffXmlWriter(
         if (throws.any()) {
             throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEach { type ->
                 writer.print("<exception name=\"")
-                writer.print(type.fullName())
+                if (options.compatOutput) {
+                    writer.print(type.simpleName())
+                } else {
+                    writer.print(type.fullName())
+                }
                 writer.print("\" type=\"")
                 writer.print(type.qualifiedName())
                 writer.println("\">")
diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt
index 147be0b..b2ab95c 100644
--- a/src/main/java/com/android/tools/metalava/Options.kt
+++ b/src/main/java/com/android/tools/metalava/Options.kt
@@ -30,6 +30,7 @@ import java.io.IOException
 import java.io.OutputStreamWriter
 import java.io.PrintWriter
 import java.io.StringWriter
+import java.util.Locale
 import kotlin.reflect.KMutableProperty1
 import kotlin.reflect.full.memberProperties
 
@@ -51,6 +52,10 @@ const val ARG_API = "--api"
 const val ARG_XML_API = "--api-xml"
 const val ARG_CONVERT_TO_JDIFF = "--convert-to-jdiff"
 const val ARG_CONVERT_NEW_TO_JDIFF = "--convert-new-to-jdiff"
+const val ARG_CONVERT_TO_V1 = "--convert-to-v1"
+const val ARG_CONVERT_TO_V2 = "--convert-to-v2"
+const val ARG_CONVERT_NEW_TO_V1 = "--convert-new-to-v1"
+const val ARG_CONVERT_NEW_TO_V2 = "--convert-new-to-v2"
 const val ARG_PRIVATE_API = "--private-api"
 const val ARG_DEX_API = "--dex-api"
 const val ARG_PRIVATE_DEX_API = "--private-dex-api"
@@ -133,7 +138,6 @@ const val ARG_DEX_API_MAPPING = "--dex-api-mapping"
 const val ARG_GENERATE_DOCUMENTATION = "--generate-documentation"
 const val ARG_BASELINE = "--baseline"
 const val ARG_UPDATE_BASELINE = "--update-baseline"
-const val ARG_CONVERT_XML = "--convert-signature-to-xml"
 
 class Options(
     args: Array<String>,
@@ -241,16 +245,13 @@ class Options(
     var compatOutput = useCompatMode(args)
 
     /** Whether nullness annotations should be displayed as ?/!/empty instead of with @NonNull/@Nullable. */
-    var outputKotlinStyleNulls = !compatOutput
+    var outputKotlinStyleNulls = false // requires v3
 
     /** Whether default values should be included in signature files */
     var outputDefaultValues = !compatOutput
 
-    /** Whether we should omit common packages such as java.lang.* and kotlin.* from signature output */
-    var omitCommonPackages = !compatOutput
-
     /** The output format version being used */
-    var outputFormat: Int = 1
+    var outputFormat: FileFormat = if (compatOutput) FileFormat.V1 else FileFormat.V2
 
     /**
      * Whether reading signature files should assume the input is formatted as Kotlin-style nulls
@@ -471,7 +472,7 @@ class Options(
     /** Level to include for javadoc */
     var docLevel = DocLevel.PROTECTED
 
-    /** Whether to include the signature file format version number ([CURRENT_SIGNATURE_FORMAT]) in signature files */
+    /** Whether to include the signature file format version header in signature files */
     var includeSignatureFormatVersion: Boolean = !compatOutput
 
     /** A baseline to check against */
@@ -501,12 +502,13 @@ class Options(
     /** List of signature files to export as JDiff files */
     val convertToXmlFiles: List<ConvertFile> = mutableConvertToXmlFiles
 
-    /** JDiff file conversion candidates */
+    /** File conversion tasks */
     data class ConvertFile(
         val fromApiFile: File,
-        val toXmlFile: File,
+        val outputFile: File,
         val baseApiFile: File? = null,
-        val strip: Boolean
+        val strip: Boolean = false,
+        val outputFormat: FileFormat = FileFormat.JDIFF
     )
 
     /** Temporary folder to use instead of the JDK default, if any */
@@ -701,9 +703,14 @@ class Options(
                 }
 
                 ARG_BASELINE -> {
-                    val file = stringToNewOrExistingFile(getValue(args, ++index))
+                    val relative = getValue(args, ++index)
+                    val file = stringToNewOrExistingFile(relative)
                     assert(baseline == null) { "Only one baseline is allowed; found both ${baseline!!.file} and $file" }
-                    baseline = Baseline(file, updateBaseline || !file.isFile)
+                    val headerComment = if (relative.contains("frameworks/base/"))
+                        "// See tools/metalava/API-LINT.md for how to update this file.\n\n"
+                    else
+                        ""
+                    baseline = Baseline(file, updateBaseline || !file.isFile, FileFormat.BASELINE, headerComment)
                 }
 
                 ARG_UPDATE_BASELINE -> {
@@ -866,8 +873,8 @@ class Options(
                     // Already processed above but don't flag it here as invalid
                 }
 
-                ARG_OMIT_COMMON_PACKAGES, "$ARG_OMIT_COMMON_PACKAGES=yes" -> omitCommonPackages = true
-                "$ARG_OMIT_COMMON_PACKAGES=no" -> omitCommonPackages = false
+                ARG_OMIT_COMMON_PACKAGES, "$ARG_OMIT_COMMON_PACKAGES=yes" -> compatibility.omitCommonPackages = true
+                "$ARG_OMIT_COMMON_PACKAGES=no" -> compatibility.omitCommonPackages = false
 
                 ARG_SKIP_JAVA_IN_COVERAGE_REPORT -> omitRuntimePackageStats = true
 
@@ -956,23 +963,45 @@ class Options(
                     artifactRegistrations.register(artifactId, descriptor)
                 }
 
-                ARG_CONVERT_TO_JDIFF, "-convert2xml", "-convert2xmlnostrip" -> {
-                    val signatureFile = stringToExistingFile(getValue(args, ++index))
-                    val jDiffFile = stringToNewFile(getValue(args, ++index))
+                ARG_CONVERT_TO_JDIFF,
+                ARG_CONVERT_TO_V1,
+                ARG_CONVERT_TO_V2,
+                // doclava compatibility:
+                "-convert2xml",
+                "-convert2xmlnostrip" -> {
                     val strip = arg == "-convert2xml"
-                    mutableConvertToXmlFiles.add(ConvertFile(signatureFile, jDiffFile, null, strip))
-                }
+                    val format = when (arg) {
+                        ARG_CONVERT_TO_V1 -> FileFormat.V1
+                        ARG_CONVERT_TO_V2 -> FileFormat.V2
+                        else -> FileFormat.JDIFF
+                    }
 
-                ARG_CONVERT_NEW_TO_JDIFF, "-new_api", "-new_api_no_strip" -> {
-                    val baseFile = stringToExistingFile(getValue(args, ++index))
                     val signatureFile = stringToExistingFile(getValue(args, ++index))
-                    val jDiffFile = stringToNewFile(getValue(args, ++index))
+                    val outputFile = stringToNewFile(getValue(args, ++index))
+                    mutableConvertToXmlFiles.add(ConvertFile(signatureFile, outputFile, null, strip, format))
+                }
+
+                ARG_CONVERT_NEW_TO_JDIFF,
+                ARG_CONVERT_NEW_TO_V1,
+                ARG_CONVERT_NEW_TO_V2,
+                // doclava compatibility:
+                "-new_api",
+                "-new_api_no_strip" -> {
+                    val format = when (arg) {
+                        ARG_CONVERT_NEW_TO_V1 -> FileFormat.V1
+                        ARG_CONVERT_NEW_TO_V2 -> FileFormat.V2
+                        else -> FileFormat.JDIFF
+                    }
                     val strip = arg == "-new_api"
                     if (arg != ARG_CONVERT_NEW_TO_JDIFF) {
                         // Using old doclava flags: Compatibility behavior: don't include fields in the output
                         compatibility.includeFieldsInApiDiff = false
                     }
-                    mutableConvertToXmlFiles.add(ConvertFile(signatureFile, jDiffFile, baseFile, strip))
+
+                    val baseFile = stringToExistingFile(getValue(args, ++index))
+                    val signatureFile = stringToExistingFile(getValue(args, ++index))
+                    val jDiffFile = stringToNewFile(getValue(args, ++index))
+                    mutableConvertToXmlFiles.add(ConvertFile(signatureFile, jDiffFile, baseFile, strip, format))
                 }
 
                 "--write-android-jar-signatures" -> {
@@ -1138,7 +1167,7 @@ class Options(
                             yesNo(arg.substring(ARG_OUTPUT_DEFAULT_VALUES.length + 1))
                         }
                     } else if (arg.startsWith(ARG_OMIT_COMMON_PACKAGES)) {
-                        omitCommonPackages = if (arg == ARG_OMIT_COMMON_PACKAGES) {
+                        compatibility.omitCommonPackages = if (arg == ARG_OMIT_COMMON_PACKAGES) {
                             true
                         } else {
                             yesNo(arg.substring(ARG_OMIT_COMMON_PACKAGES.length + 1))
@@ -1153,9 +1182,15 @@ class Options(
                         else yesNo(arg.substring(ARG_INCLUDE_SIG_VERSION.length + 1))
                     } else if (arg.startsWith(ARG_FORMAT)) {
                         when (arg) {
-                            "$ARG_FORMAT=v1" -> setFormat(1)
-                            "$ARG_FORMAT=v2" -> setFormat(2)
-                            "$ARG_FORMAT=v3" -> setFormat(3)
+                            "$ARG_FORMAT=v1" -> {
+                                FileFormat.V1.configureOptions(this, compatibility)
+                            }
+                            "$ARG_FORMAT=v2" -> {
+                                FileFormat.V2.configureOptions(this, compatibility)
+                            }
+                            "$ARG_FORMAT=v3" -> {
+                                FileFormat.V3.configureOptions(this, compatibility)
+                            }
                             else -> throw DriverException(stderr = "Unexpected signature format; expected v1, v2 or v3")
                         }
                     } else if (arg.startsWith("-")) {
@@ -1231,9 +1266,9 @@ class Options(
             artifactRegistrations.clear()
         }
 
-        if (baseline == null && sourcePath.isNotEmpty() && !sourcePath[0].path.isBlank()) {
-            val defaultBaseline = File(sourcePath[0], DEFAULT_BASELINE_NAME)
-            if (defaultBaseline.isFile || updateBaseline) {
+        if (baseline == null) {
+            val defaultBaseline = getDefaultBaselineFile()
+            if (defaultBaseline != null && (defaultBaseline.isFile || updateBaseline)) {
                 baseline = Baseline(defaultBaseline, !defaultBaseline.isFile || updateBaseline)
             }
         }
@@ -1241,15 +1276,6 @@ class Options(
         checkFlagConsistency()
     }
 
-    private fun setFormat(format: Int) {
-        outputFormat = format
-        compatOutput = format == 1
-        outputKotlinStyleNulls = format >= 3
-        outputDefaultValues = format >= 2
-        omitCommonPackages = format >= 2
-        includeSignatureFormatVersion = format >= 2
-    }
-
     private fun findCompatibilityFlag(arg: String): KMutableProperty1<Compatibility, Boolean>? {
         val index = arg.indexOf('=')
         val name = arg
@@ -1264,6 +1290,27 @@ class Options(
             }
     }
 
+    /**
+     * Produce a default file name for the baseline. It's normally "baseline.txt", but can
+     * be prefixed by show annotations; e.g. @TestApi -> test-baseline.txt, @SystemApi -> system-baseline.txt,
+     * etc.
+     */
+    private fun getDefaultBaselineFile(): File? {
+        if (sourcePath.isNotEmpty() && !sourcePath[0].path.isBlank()) {
+            fun annotationToPrefix(qualifiedName: String): String {
+                val name = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1)
+                return name.toLowerCase(Locale.US).removeSuffix("api") + "-"
+            }
+            val sb = StringBuilder()
+            showAnnotations.forEach { sb.append(annotationToPrefix(it)).append('-') }
+            showSingleAnnotations.forEach { sb.append(annotationToPrefix(it)).append('-') }
+            sb.append(DEFAULT_BASELINE_NAME)
+            return File(sourcePath[0], sb.toString())
+        } else {
+            return null
+        }
+    }
+
     private fun findAndroidJars(
         androidJarPatterns: List<String>,
         currentApiLevel: Int,
@@ -1739,6 +1786,12 @@ class Options(
                 "in the JDiff XML format. Can be specified multiple times.",
             "$ARG_CONVERT_NEW_TO_JDIFF <old> <new> <xml>", "Reads in the given old and new api files, " +
                 "computes the difference, and writes out only the new parts of the API in the JDiff XML format.",
+            "$ARG_CONVERT_TO_V1 <sig> <sig>", "Reads in the given signature file and writes it out as a " +
+                "signature file in the original v1/doclava format.",
+            "$ARG_CONVERT_TO_V2 <sig> <sig>", "Reads in the given signature file and writes it out as a " +
+                "signature file in the new signature format, v2.",
+            "$ARG_CONVERT_NEW_TO_V2 <old> <new> <sig>", "Reads in the given old and new api files, " +
+                "computes the difference, and writes out only the new parts of the API in the v2 format.",
 
             "", "\nStatistics:",
             ARG_ANNOTATION_COVERAGE_STATS, "Whether $PROGRAM_NAME should emit coverage statistics for " +
diff --git a/src/main/java/com/android/tools/metalava/Reporter.kt b/src/main/java/com/android/tools/metalava/Reporter.kt
index 939913b..56009b7 100644
--- a/src/main/java/com/android/tools/metalava/Reporter.kt
+++ b/src/main/java/com/android/tools/metalava/Reporter.kt
@@ -133,7 +133,7 @@ open class Reporter(private val rootFolder: File? = null) {
                 report(severity, item.psi(), message, id)
             }
             is TextItem -> report(severity, (item as? TextItem)?.position.toString(), message, id)
-            else -> report(severity, "<unknown location>", message, id)
+            else -> report(severity, null as String?, message, id)
         }
     }
 
@@ -294,7 +294,9 @@ open class Reporter(private val rootFolder: File? = null) {
         if (color) {
             sb.append(terminalAttributes(bold = true))
             if (!options.omitLocations) {
-                location?.let { sb.append(it).append(": ") }
+                location?.let {
+                    sb.append(it).append(": ")
+                }
             }
             when (effectiveSeverity) {
                 LINT -> sb.append(terminalAttributes(foreground = TerminalColor.CYAN)).append("lint: ")
diff --git a/src/main/java/com/android/tools/metalava/SignatureFormat.kt b/src/main/java/com/android/tools/metalava/SignatureFormat.kt
deleted file mode 100644
index 794eedb..0000000
--- a/src/main/java/com/android/tools/metalava/SignatureFormat.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2018 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
-
-/** Current signature format. */
-const val CURRENT_SIGNATURE_FORMAT = "2.0"
-
-/** Marker comment at the beginning of the signature file */
-const val SIGNATURE_FORMAT_PREFIX = "// Signature format: "
-
-fun useKotlinStyleNulls(format: Int): Boolean {
-    return format >= 3
-}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/SignatureWriter.kt b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
index 1b9ee50..c38a1a1 100644
--- a/src/main/java/com/android/tools/metalava/SignatureWriter.kt
+++ b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
@@ -50,8 +50,7 @@ class SignatureWriter(
 ) {
     init {
         if (options.includeSignatureFormatVersion) {
-            writer.print(SIGNATURE_FORMAT_PREFIX)
-            writer.println(CURRENT_SIGNATURE_FORMAT)
+            writer.print(options.outputFormat.header())
         }
     }
 
@@ -139,10 +138,6 @@ class SignatureWriter(
     override fun visitClass(cls: ClassItem) {
         writer.print("  ")
 
-        if (compatibility.extraSpaceForEmptyModifiers && cls.isPackagePrivate && cls.isPackagePrivate) {
-            writer.print(" ")
-        }
-
         writeModifiers(cls)
 
         if (cls.isAnnotationType()) {
@@ -185,7 +180,7 @@ class SignatureWriter(
             includeDeprecated = true,
             includeAnnotations = compatibility.annotationsInSignatures,
             skipNullnessAnnotations = options.outputKotlinStyleNulls,
-            omitCommonPackages = options.omitCommonPackages
+            omitCommonPackages = compatibility.omitCommonPackages
         )
     }
 
@@ -317,7 +312,7 @@ class SignatureWriter(
         )
 
         // Strip java.lang. prefix?
-        if (options.omitCommonPackages) {
+        if (compatibility.omitCommonPackages) {
             typeString = TypeItem.shortenTypes(typeString)
         }
 
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
index 5189d34..f1d672e 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
@@ -16,9 +16,8 @@
 
 package com.android.tools.metalava.doclava1;
 
-import com.android.ide.common.repository.GradleVersion;
 import com.android.tools.lint.checks.infrastructure.ClassNameKt;
-import com.android.tools.metalava.SignatureFormatKt;
+import com.android.tools.metalava.FileFormat;
 import com.android.tools.metalava.model.AnnotationItem;
 import com.android.tools.metalava.model.DefaultModifierList;
 import com.android.tools.metalava.model.TypeParameterList;
@@ -37,6 +36,7 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Charsets;
 import com.google.common.io.Files;
 import kotlin.Pair;
+import kotlin.text.StringsKt;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
@@ -49,7 +49,6 @@ import static com.android.tools.metalava.ConstantsKt.ANDROIDX_NULLABLE;
 import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_ANNOTATION;
 import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_ENUM;
 import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_STRING;
-import static com.android.tools.metalava.SignatureFormatKt.SIGNATURE_FORMAT_PREFIX;
 import static com.android.tools.metalava.model.FieldItemKt.javaUnescapeString;
 
 //
@@ -71,25 +70,20 @@ public class ApiFile {
         }
     }
 
+    @SuppressWarnings("StatementWithEmptyBody")
     @VisibleForTesting
     public static TextCodebase parseApi(String filename, String apiText,
                                         Boolean kotlinStyleNulls) throws ApiParseException {
-        GradleVersion format = null;
-        if (apiText.startsWith(SIGNATURE_FORMAT_PREFIX)) {
-            int begin = SIGNATURE_FORMAT_PREFIX.length();
-            int end = apiText.indexOf('\n', begin);
-            if (end == -1) {
-                end = apiText.length();
-            } else if (end > 0 && apiText.charAt(end - 1) == '\r') {
-                end--;
-            }
-            String formatString = apiText.substring(begin, end).trim();
-            if (!formatString.isEmpty()) {
-                format = GradleVersion.tryParse(formatString);
-                if (kotlinStyleNulls == null) {
-                    kotlinStyleNulls = SignatureFormatKt.useKotlinStyleNulls(format.getMajor());
-                }
+        FileFormat format = FileFormat.Companion.parseHeader(apiText);
+        if (format.isSignatureFormat()) {
+            if (kotlinStyleNulls == null || !kotlinStyleNulls) {
+                kotlinStyleNulls = format.useKotlinStyleNulls();
             }
+        } else if (StringsKt.isBlank(apiText)) {
+            // Signature files are sometimes blank, particularly with show annotations
+            kotlinStyleNulls = false;
+        } else {
+            throw new ApiParseException("Unknown file format of " + filename);
         }
 
         if (apiText.contains("/*")) {
@@ -99,12 +93,8 @@ public class ApiFile {
         final Tokenizer tokenizer = new Tokenizer(filename, apiText.toCharArray());
         final TextCodebase api = new TextCodebase(new File(filename));
         api.setDescription("Codebase loaded from " + filename);
-        if (format != null) {
-            api.setFormat(format);
-        }
-        if (kotlinStyleNulls != null) {
-            api.setKotlinStyleNulls(kotlinStyleNulls);
-        }
+        api.setFormat(format);
+        api.setKotlinStyleNulls(kotlinStyleNulls);
 
         while (true) {
             String token = tokenizer.getToken();
@@ -180,6 +170,7 @@ public class ApiFile {
             token = tokenizer.requireToken();
         } else if ("interface".equals(token)) {
             isInterface = true;
+            modifiers.setAbstract(true);
             token = tokenizer.requireToken();
         } else if ("@interface".equals(token)) {
             // Annotation
@@ -242,6 +233,10 @@ public class ApiFile {
         }
         if (JAVA_LANG_ENUM.equals(ext)) {
             cl.setIsEnum(true);
+            // Above we marked all enums as static but for a top level class it's implicit
+            if (!cl.fullName().contains(".")) {
+                cl.getModifiers().setStatic(false);
+            }
         } else if (isAnnotation) {
             api.mapClassToInterface(cl, JAVA_LANG_ANNOTATION);
         } else if (api.implementsInterface(cl, JAVA_LANG_ANNOTATION)) {
@@ -423,6 +418,9 @@ public class ApiFile {
         name = token;
         method = new TextMethodItem(api, name, cl, modifiers, returnType, tokenizer.pos());
         method.setDeprecated(modifiers.isDeprecated());
+        if (cl.isInterface() && !modifiers.isDefault()) {
+            modifiers.setAbstract(true);
+        }
         method.setTypeParameterList(typeParameterList);
         if (typeParameterList instanceof TextTypeParameterList) {
             ((TextTypeParameterList) typeParameterList).setOwner(method);
@@ -754,7 +752,9 @@ public class ApiFile {
             if (typeString.endsWith("...")) {
                 modifiers.setVarArg(true);
             }
-            TextTypeItem typeInfo = api.obtainTypeFromString(typeString);
+            TextTypeItem typeInfo = api.obtainTypeFromString(typeString,
+                (TextClassItem) method.containingClass(),
+                method.typeParameterList());
 
             String name;
             String publicName;
diff --git a/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt b/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt
index 94c055e..f90daf5 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt
+++ b/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt
@@ -16,10 +16,10 @@
 
 package com.android.tools.metalava.doclava1
 
-import com.android.ide.common.repository.GradleVersion
 import com.android.tools.metalava.ApiType
 import com.android.tools.metalava.CodebaseComparator
 import com.android.tools.metalava.ComparisonVisitor
+import com.android.tools.metalava.FileFormat
 import com.android.tools.metalava.JAVA_LANG_ANNOTATION
 import com.android.tools.metalava.JAVA_LANG_ENUM
 import com.android.tools.metalava.JAVA_LANG_OBJECT
@@ -77,7 +77,7 @@ class TextCodebase(location: File) : DefaultCodebase(location) {
      * Signature file format version, if found. Type "GradleVersion" is misleading; it's just a convenient
      * version class.
      */
-    var format: GradleVersion = GradleVersion.parse("1.0") // not specifying format: assumed to be doclava, 1.0
+    var format: FileFormat = FileFormat.V1 // not specifying format: assumed to be doclava, 1.0
 
     override fun getPackages(): PackageList {
         val list = ArrayList<PackageItem>(mPackages.values)
@@ -340,7 +340,8 @@ class TextCodebase(location: File) : DefaultCodebase(location) {
         fun computeDelta(
             baseFile: File,
             baseApi: Codebase,
-            signatureApi: Codebase
+            signatureApi: Codebase,
+            includeFieldsInApiDiff: Boolean = compatibility.includeFieldsInApiDiff
         ): TextCodebase {
             // Compute just the delta
             val delta = TextCodebase(baseFile)
@@ -367,7 +368,7 @@ class TextCodebase(location: File) : DefaultCodebase(location) {
                 }
 
                 override fun added(new: FieldItem) {
-                    if (!compatibility.includeFieldsInApiDiff) {
+                    if (!includeFieldsInApiDiff) {
                         return
                     }
                     val cls = getOrAddClass(new.containingClass())
diff --git a/src/main/java/com/android/tools/metalava/model/ClassItem.kt b/src/main/java/com/android/tools/metalava/model/ClassItem.kt
index e1746c9..e06784c 100644
--- a/src/main/java/com/android/tools/metalava/model/ClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/ClassItem.kt
@@ -21,7 +21,6 @@ import com.android.tools.metalava.ApiAnalyzer
 import com.android.tools.metalava.JAVA_LANG_ANNOTATION
 import com.android.tools.metalava.JAVA_LANG_ENUM
 import com.android.tools.metalava.JAVA_LANG_OBJECT
-import com.android.tools.metalava.compatibility
 import com.android.tools.metalava.model.visitors.ApiVisitor
 import com.android.tools.metalava.model.visitors.ItemVisitor
 import com.android.tools.metalava.model.visitors.TypeVisitor
@@ -375,12 +374,7 @@ interface ClassItem : Item {
             a.qualifiedName().compareTo(b.qualifiedName())
         }
 
-        fun classNameSorter(): Comparator<in ClassItem> =
-            if (compatibility.sortClassesBySimpleName) {
-                ClassItem.comparator
-            } else {
-                ClassItem.qualifiedComparator
-            }
+        fun classNameSorter(): Comparator<in ClassItem> = ClassItem.qualifiedComparator
     }
 
     fun findMethod(
diff --git a/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt b/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt
index 65ebd48..21a328f 100644
--- a/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt
+++ b/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt
@@ -16,6 +16,7 @@
 
 package com.android.tools.metalava.model
 
+import com.android.tools.metalava.compatibility
 import com.android.tools.metalava.model.psi.PsiModifierItem
 
 open class DefaultModifierList(
@@ -245,13 +246,24 @@ open class DefaultModifierList(
     override fun equivalentTo(other: ModifierList): Boolean {
         if (other is PsiModifierItem) {
             val flags2 = other.flags
-            val mask = EQUIVALENCE_MASK
-
-            // Skipping the "default" flag
-            // TODO: Compatibility: skipNativeModifier and skipStrictFpModifier modifier flags!
-            // if (!compatibility.skipNativeModifier && isNative() != other.isNative()) return false
-            // if (!compatibility.skipStrictFpModifier && isStrictFp() != other.isStrictFp()) return false
-            return flags and mask == flags2 and mask
+            val mask = if (compatibility.includeSynchronized) COMPAT_EQUIVALENCE_MASK else EQUIVALENCE_MASK
+
+            val masked1 = flags and mask
+            val masked2 = flags2 and mask
+            val same = masked1 xor masked2
+            if (same == 0) {
+                return true
+            } else if (compatibility.hideDifferenceImplicit) {
+                if (same == FINAL &&
+                    // Only differ in final: not significant if implied by containing class
+                    isFinal() && (owner as? MethodItem)?.containingClass()?.modifiers?.isFinal() == true) {
+                    return true
+                } else if (same == DEPRECATED &&
+                    // Only differ in deprecated: not significant if implied by containing class
+                    isDeprecated() && (owner as? MethodItem)?.containingClass()?.deprecated == true) {
+                    return true
+                }
+            }
         }
         return false
     }
@@ -309,7 +321,9 @@ open class DefaultModifierList(
          * to consider whether an override of a method is different from its super implementation
          */
         private const val EQUIVALENCE_MASK = PUBLIC or PROTECTED or PRIVATE or STATIC or ABSTRACT or
-            FINAL or TRANSIENT or VOLATILE or SYNCHRONIZED or DEPRECATED or VARARG or
+            FINAL or TRANSIENT or VOLATILE or DEPRECATED or VARARG or
             SEALED or INTERNAL or INFIX or OPERATOR or SUSPEND
+
+        private const val COMPAT_EQUIVALENCE_MASK = EQUIVALENCE_MASK or SYNCHRONIZED
     }
 }
\ No newline at end of file
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 173842f..bdb3b89 100644
--- a/src/main/java/com/android/tools/metalava/model/MethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/MethodItem.kt
@@ -16,6 +16,7 @@
 
 package com.android.tools.metalava.model
 
+import com.android.tools.metalava.compatibility
 import com.android.tools.metalava.doclava1.TextCodebase
 import com.android.tools.metalava.model.visitors.ItemVisitor
 import com.android.tools.metalava.model.visitors.TypeVisitor
@@ -111,7 +112,7 @@ interface MethodItem : MemberItem {
     ): LinkedHashSet<ClassItem> {
 
         for (cls in throwsTypes()) {
-            if (predicate.test(cls)) {
+            if (predicate.test(cls) || cls.isTypeParameter && !compatibility.useErasureInThrows) {
                 classes.add(cls)
             } else {
                 // Excluded, but it may have super class throwables that are included; if so, include those
@@ -276,8 +277,8 @@ interface MethodItem : MemberItem {
                 return false
             }
 
-            // IntentService#onStart - is it here because they vary in deprecation status?
-            if (method.deprecated != superMethod.deprecated) {
+            if (method.deprecated != superMethod.deprecated &&
+                (!compatibility.hideDifferenceImplicit || !method.deprecated)) {
                 return false
             }
 
diff --git a/src/main/java/com/android/tools/metalava/model/ModifierList.kt b/src/main/java/com/android/tools/metalava/model/ModifierList.kt
index 3ce5d30..d645edf 100644
--- a/src/main/java/com/android/tools/metalava/model/ModifierList.kt
+++ b/src/main/java/com/android/tools/metalava/model/ModifierList.kt
@@ -69,10 +69,8 @@ interface ModifierList {
 
         if (isStatic() != other.isStatic()) return false
         if (isAbstract() != other.isAbstract()) return false
-        if (isFinal() != other.isFinal()) return false
-        if (!compatibility.skipNativeModifier && isNative() != other.isNative()) return false
-        if (isSynchronized() != other.isSynchronized()) return false
-        if (!compatibility.skipStrictFpModifier && isStrictFp() != other.isStrictFp()) return false
+        if (isFinal() != other.isFinal()) { return false }
+        if (compatibility.includeSynchronized && isSynchronized() != other.isSynchronized()) return false
         if (isTransient() != other.isTransient()) return false
         if (isVolatile() != other.isVolatile()) return false
 
@@ -270,10 +268,6 @@ interface ModifierList {
                 return
             }
 
-            if (compatibility.extraSpaceForEmptyModifiers && item.isPackagePrivate && item is MemberItem) {
-                writer.write(" ")
-            }
-
             // Kotlin order:
             //   https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers
 
@@ -342,22 +336,18 @@ interface ModifierList {
                     writer.write("abstract ")
                 }
 
-                if (list.isNative() && (target.isStubsFile() || !compatibility.skipNativeModifier)) {
+                if (list.isNative() && target.isStubsFile()) {
                     writer.write("native ")
                 }
 
-                if (item.deprecated && includeDeprecated && !target.isStubsFile() && options.compatOutput) {
+                if (item.deprecated && includeDeprecated && !target.isStubsFile() && !compatibility.deprecatedAsAnnotation) {
                     writer.write("deprecated ")
                 }
 
-                if (list.isSynchronized() && (options.compatOutput || target.isStubsFile())) {
+                if (list.isSynchronized() && (compatibility.includeSynchronized || target.isStubsFile())) {
                     writer.write("synchronized ")
                 }
 
-                if (!compatibility.skipStrictFpModifier && list.isStrictFp()) {
-                    writer.write("strictfp ")
-                }
-
                 if (list.isTransient()) {
                     writer.write("transient ")
                 }
@@ -366,7 +356,7 @@ interface ModifierList {
                     writer.write("volatile ")
                 }
             } else {
-                if (item.deprecated && includeDeprecated && !target.isStubsFile() && options.compatOutput) {
+                if (item.deprecated && includeDeprecated && !target.isStubsFile() && !compatibility.deprecatedAsAnnotation) {
                     writer.write("deprecated ")
                 }
 
@@ -434,17 +424,13 @@ interface ModifierList {
                     writer.write("volatile ")
                 }
 
-                if (list.isSynchronized() && (options.compatOutput || target.isStubsFile())) {
+                if (list.isSynchronized() && (compatibility.includeSynchronized || target.isStubsFile())) {
                     writer.write("synchronized ")
                 }
 
-                if (list.isNative() && (target.isStubsFile() || !compatibility.skipNativeModifier)) {
+                if (list.isNative() && target.isStubsFile()) {
                     writer.write("native ")
                 }
-
-                if (!compatibility.skipStrictFpModifier && list.isStrictFp()) {
-                    writer.write("strictfp ")
-                }
             }
         }
 
@@ -463,7 +449,7 @@ interface ModifierList {
             //  unless runtimeOnly is false, in which case we'd include it too
             // e.g. emit @Deprecated if includeDeprecated && !runtimeOnly
             if (item.deprecated &&
-                (!options.compatOutput || target.isStubsFile()) &&
+                (compatibility.deprecatedAsAnnotation || target.isStubsFile()) &&
                 (runtimeAnnotationsOnly || includeDeprecated)
             ) {
                 writer.write("@Deprecated")
diff --git a/src/main/java/com/android/tools/metalava/model/TypeItem.kt b/src/main/java/com/android/tools/metalava/model/TypeItem.kt
index 1469e91..7ca5ff9 100644
--- a/src/main/java/com/android/tools/metalava/model/TypeItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/TypeItem.kt
@@ -21,7 +21,6 @@ import com.android.tools.metalava.JAVA_LANG_OBJECT
 import com.android.tools.metalava.JAVA_LANG_PREFIX
 import com.android.tools.metalava.JAVA_LANG_STRING
 import com.android.tools.metalava.compatibility
-import com.android.tools.metalava.options
 import java.util.function.Predicate
 
 /**
@@ -168,7 +167,7 @@ interface TypeItem {
     companion object {
         /** Shortens types, if configured */
         fun shortenTypes(type: String): String {
-            if (options.omitCommonPackages) {
+            if (compatibility.omitCommonPackages) {
                 var cleaned = type
                 if (cleaned.contains("@androidx.annotation.")) {
                     cleaned = cleaned.replace("@androidx.annotation.", "@")
diff --git a/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt b/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt
index bcd526b..7fac61b 100644
--- a/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt
+++ b/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt
@@ -20,4 +20,7 @@ interface TypeParameterListOwner {
     fun typeParameterList(): TypeParameterList
     /** Given a variable in this owner, resolves to a type parameter item */
     fun resolveParameter(variable: String): TypeParameterItem?
+
+    /** Parent type parameter list owner */
+    fun typeParameterListOwnerParent(): TypeParameterListOwner?
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt b/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt
index 9eb7b85..bbe8f86 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt
@@ -29,6 +29,7 @@ import com.intellij.psi.PsiAnnotationMemberValue
 import com.intellij.psi.PsiArrayInitializerMemberValue
 import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiClassObjectAccessExpression
+import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiField
 import com.intellij.psi.PsiLiteral
 import com.intellij.psi.PsiReference
@@ -40,6 +41,7 @@ import org.jetbrains.uast.UBinaryExpression
 import org.jetbrains.uast.UBinaryExpressionWithType
 import org.jetbrains.uast.UBlockExpression
 import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UExpression
 import org.jetbrains.uast.ULambdaExpression
 import org.jetbrains.uast.ULiteralExpression
@@ -62,8 +64,12 @@ open class CodePrinter(
     /** An optional filter to use to determine if we should emit a reference to an item */
     private val filterReference: Predicate<Item>? = null
 ) {
-    open fun warning(message: String) {
-        reporter.report(Errors.INTERNAL_ERROR, null as Item?, message)
+    open fun warning(message: String, psiElement: PsiElement? = null) {
+        reporter.report(Errors.INTERNAL_ERROR, psiElement, message)
+    }
+
+    open fun warning(message: String, uElement: UElement) {
+        warning(message, uElement.sourcePsi ?: uElement.javaPsi)
     }
 
     /** Given an annotation member value, returns the corresponding Java source expression */
@@ -186,7 +192,7 @@ open class CodePrinter(
 
                     val declaringClass = field.containingClass
                     if (declaringClass == null) {
-                        warning("No containing class found for " + field.name)
+                        warning("No containing class found for " + field.name, field)
                         return false
                     }
                     val qualifiedName = declaringClass.qualifiedName
@@ -225,7 +231,7 @@ open class CodePrinter(
                 }
                 else -> {
                     if (skipUnknown) {
-                        warning("Unexpected reference to $expression")
+                        warning("Unexpected reference to $expression", expression)
                         return false
                     }
                     sb.append(expression.asSourceString())
@@ -361,7 +367,7 @@ open class CodePrinter(
             }
         }
 
-        warning("Unexpected annotation expression of type ${expression.javaClass} and is $expression")
+        warning("Unexpected annotation expression of type ${expression.javaClass} and is $expression", expression)
 
         return false
     }
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt
index d1872c3..1bc4bf6 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt
@@ -17,7 +17,6 @@
 package com.android.tools.metalava.model.psi
 
 import com.android.SdkConstants
-import com.android.tools.metalava.compatibility
 import com.android.tools.metalava.doclava1.Errors
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.DefaultCodebase
@@ -414,7 +413,7 @@ open class PsiBasedCodebase(location: File, override var description: String = "
                     }
                 }
                 val last = pkg.lastIndexOf('.')
-                if (last == -1 || !compatibility.inheritPackageDocs) {
+                if (last == -1) {
                     hiddenPackages[packageName] = false
                     break
                 } else {
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
index 0915864..f3efc40 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
@@ -27,7 +27,6 @@ import com.android.tools.metalava.model.PackageItem
 import com.android.tools.metalava.model.PropertyItem
 import com.android.tools.metalava.model.TypeItem
 import com.android.tools.metalava.model.TypeParameterList
-import com.android.tools.metalava.options
 import com.intellij.lang.jvm.types.JvmReferenceType
 import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiClassType
@@ -408,7 +407,7 @@ open class PsiClassItem(
 
             if (classType == ClassType.ENUM) {
                 addEnumMethods(codebase, item, psiClass, methods)
-            } else if (classType == ClassType.ANNOTATION_TYPE && !options.compatOutput &&
+            } else if (classType == ClassType.ANNOTATION_TYPE && compatibility.explicitlyListClassRetention &&
                 modifiers.findAnnotation("java.lang.annotation.Retention") == null
             ) {
                 // By policy, include explicit retention policy annotation if missing
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
index 86c72e5..f78e825 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
@@ -142,6 +142,10 @@ open class TextClassItem(
         return typeParameterList!!
     }
 
+    override fun typeParameterListOwnerParent(): TypeParameterListOwner? {
+        return containingClass
+    }
+
     override fun resolveParameter(variable: String): TypeParameterItem? {
         if (hasTypeVariables()) {
             for (t in typeParameterList().typeParameters()) {
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
index e2a6699..a1a38d2 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
@@ -120,6 +120,10 @@ open class TextMethodItem(
 
     override fun typeParameterList(): TypeParameterList = typeParameterList
 
+    override fun typeParameterListOwnerParent(): TypeParameterListOwner? {
+        return containingClass() as TextClassItem?
+    }
+
     override fun resolveParameter(variable: String): TypeParameterItem? {
         for (t in typeParameterList.typeParameters()) {
             if (t.simpleName() == variable) {
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt
index 08ef2ee..d74415b 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt
@@ -34,6 +34,7 @@ class TextTypeItem(
     val codebase: TextCodebase,
     val type: String
 ) : TypeItem {
+
     override fun toString(): String = type
 
     override fun toErasedTypeString(context: Item?): String {
diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties
index 2836f16..7495ade 100644
--- a/src/main/resources/version.properties
+++ b/src/main/resources/version.properties
@@ -2,4 +2,4 @@
 # Version definition
 # This file is read by gradle build scripts, but also packaged with metalava
 # as a resource for the Version classes to read.
-metalavaVersion=1.2.1
+metalavaVersion=1.2.2
diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
index 8998f8c..c96c46d 100644
--- a/src/test/java/com/android/tools/metalava/ApiFileTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
@@ -84,6 +84,7 @@ class ApiFileTest : DriverTest() {
     fun `Parameter Names in Java`() {
         // Java code which explicitly specifies parameter names
         check(
+            compatibilityMode = false, // parameter names only in v2
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -115,7 +116,7 @@ class ApiFileTest : DriverTest() {
     fun `Default Values Names in Java`() {
         // Java code which explicitly specifies parameter names
         check(
-            compatibilityMode = false,
+            format = FileFormat.V3,
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -134,6 +135,7 @@ class ApiFileTest : DriverTest() {
                 supportDefaultValue
             ),
             api = """
+                // Signature format: 3.0
                 package test.pkg {
                   public class Foo {
                     ctor public Foo();
@@ -150,6 +152,7 @@ class ApiFileTest : DriverTest() {
     fun `Default Values and Names in Kotlin`() {
         // Kotlin code which explicitly specifies parameter names
         check(
+            format = FileFormat.V3,
             compatibilityMode = false,
             sourceFiles = *arrayOf(
                 kotlin(
@@ -193,7 +196,7 @@ class ApiFileTest : DriverTest() {
                 )
             ),
             api = """
-                // Signature format: $CURRENT_SIGNATURE_FORMAT
+                // Signature format: 3.0
                 package test.pkg {
                   public final class Foo {
                     ctor public Foo();
@@ -221,7 +224,7 @@ class ApiFileTest : DriverTest() {
         // Testing trickier default values; regression test for problem
         // observed in androidx.core.util with LruCache
         check(
-            compatibilityMode = false,
+            format = FileFormat.V3,
             sourceFiles = *arrayOf(
                 kotlin(
                     """
@@ -278,7 +281,7 @@ class ApiFileTest : DriverTest() {
                 androidxNonNullSource
             ),
             api = """
-                // Signature format: $CURRENT_SIGNATURE_FORMAT
+                // Signature format: 3.0
                 package androidx.core.util {
                   public final class TestKt {
                     ctor public TestKt();
@@ -295,6 +298,8 @@ class ApiFileTest : DriverTest() {
     @Test
     fun `Basic Kotlin class`() {
         check(
+            format = FileFormat.V1,
+            extraArguments = arrayOf("--parameter-names=true"),
             sourceFiles = *arrayOf(
                 kotlin(
                     """
@@ -431,6 +436,7 @@ class ApiFileTest : DriverTest() {
     @Test
     fun `Kotlin Reified Methods 2`() {
         check(
+            compatibilityMode = false,
             sourceFiles = *arrayOf(
                 kotlin(
                     """
@@ -465,6 +471,7 @@ class ApiFileTest : DriverTest() {
     @Test
     fun `Suspend functions`() {
         check(
+            compatibilityMode = false,
             sourceFiles = *arrayOf(
                 kotlin(
                     """
@@ -477,7 +484,7 @@ class ApiFileTest : DriverTest() {
                 package test.pkg {
                   public final class TestKt {
                     ctor public TestKt();
-                    method public static suspend inline java.lang.Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p);
+                    method public static suspend inline Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p);
                   }
                 }
                 """,
@@ -488,6 +495,7 @@ class ApiFileTest : DriverTest() {
     @Test
     fun `Kotlin Generics`() {
         check(
+            format = FileFormat.V3,
             sourceFiles = *arrayOf(
                 kotlin(
                     """
@@ -500,7 +508,9 @@ class ApiFileTest : DriverTest() {
                     """
                 )
             ),
+            compatibilityMode = false,
             api = """
+                // Signature format: 3.0
                 package test.pkg {
                   public final class Bar {
                     ctor public Bar();
@@ -731,6 +741,7 @@ class ApiFileTest : DriverTest() {
     fun `JvmOverloads`() {
         // Regression test for https://github.com/android/android-ktx/issues/366
         check(
+            format = FileFormat.V3,
             compatibilityMode = false,
             sourceFiles = *arrayOf(
                 kotlin(
@@ -762,6 +773,7 @@ class ApiFileTest : DriverTest() {
                 )
             ),
             api = """
+                // Signature format: 3.0
                 package androidx.content {
                   public final class TestKt {
                     ctor public TestKt();
@@ -2715,26 +2727,26 @@ class ApiFileTest : DriverTest() {
             privateApi = """
                 package test.pkg {
                   public class Class1 implements test.pkg.MyInterface {
-                    ctor  Class1(int);
+                    ctor Class1(int);
                     method public void method1();
-                    method  void method2();
+                    method void method2();
                     method private void method3();
-                    method  void myVarargsMethod(int, java.lang.String...);
-                    field  int field3;
-                    field  float[][] field4;
-                    field  long[] field5;
+                    method void myVarargsMethod(int, java.lang.String...);
+                    field int field3;
+                    field float[][] field4;
+                    field long[] field5;
                     field private int field6;
                   }
-                   class Class2 {
-                    ctor  Class2();
+                  class Class2 {
+                    ctor Class2();
                     method public void method4();
                   }
                   private class Class2.Class3 {
                     ctor private Class2.Class3();
                     method public void method5();
                   }
-                   class Class4 {
-                    ctor  Class4();
+                  class Class4 {
+                    ctor Class4();
                     method public void method5();
                   }
                   public abstract interface MyInterface {
@@ -2835,7 +2847,7 @@ class ApiFileTest : DriverTest() {
             privateApi = """
                 package test.pkg {
                   public class Class1 extends test.pkg.PrivateParent implements test.pkg.MyInterface {
-                    ctor  Class1(int);
+                    ctor Class1(int);
                   }
                   private abstract class Class1.AmsTask extends java.util.concurrent.FutureTask {
                   }
@@ -2847,9 +2859,9 @@ class ApiFileTest : DriverTest() {
                     enum_constant public static final test.pkg.MyEnum BAR;
                     enum_constant public static final test.pkg.MyEnum FOO;
                   }
-                   class PrivateParent {
-                    ctor  PrivateParent();
-                    method  final java.lang.String getValue();
+                  class PrivateParent {
+                    ctor PrivateParent();
+                    method final java.lang.String getValue();
                   }
                 }
                 """,
@@ -2979,4 +2991,129 @@ class ApiFileTest : DriverTest() {
             checkDoclava1 = false // doclava is unaware of @suppress
         )
     }
+
+    @Test
+    fun `Check skipping implicit final or deprecated override`() {
+        // Regression test for 122358225
+        check(
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public class Parent {
+                        public void foo1() { }
+                        public void foo2() { }
+                        public void foo3() { }
+                        public void foo4() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    public final class Child1 extends Parent {
+                        private Child1() { }
+                        public final void foo1() { }
+                        public void foo2() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    /** @deprecated */
+                    @Deprecated
+                    public final class Child2 extends Parent {
+                        private Child2() { }
+                        /** @deprecated */
+                        @Deprecated
+                        public void foo3() { }
+                        public void foo4() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    /** @deprecated */
+                    @Deprecated
+                    public final class Child3 extends Parent {
+                        private Child3() { }
+                        public final void foo1() { }
+                        public void foo2() { }
+                        /** @deprecated */
+                        @Deprecated
+                        public void foo3() { }
+                        /** @deprecated */
+                        @Deprecated
+                        public final void foo4() { }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public final class Child1 extends test.pkg.Parent {
+                  }
+                  @Deprecated public final class Child2 extends test.pkg.Parent {
+                  }
+                  @Deprecated public final class Child3 extends test.pkg.Parent {
+                  }
+                  public class Parent {
+                    ctor public Parent();
+                    method public void foo1();
+                    method public void foo2();
+                    method public void foo3();
+                    method public void foo4();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Ignore synchronized differences`() {
+        check(
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg2;
+
+                    public class Parent {
+                        public void foo1() { }
+                        public synchronized void foo2() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg2;
+
+                    public class Child1 extends Parent {
+                        private Child1() { }
+                        public synchronized void foo1() { }
+                        public void foo2() { }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg2 {
+                  public class Child1 extends test.pkg2.Parent {
+                  }
+                  public class Parent {
+                    ctor public Parent();
+                    method public void foo1();
+                    method public void foo2();
+                  }
+                }
+                """
+        )
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
index 1b0fc39..deb072b 100644
--- a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
@@ -45,7 +45,7 @@ class ApiFromTextTest : DriverTest() {
     @Test
     fun `Handle lambas as default values`() {
         val source = """
-            // Signature format: $CURRENT_SIGNATURE_FORMAT
+            // Signature format: 3.0
             package androidx.collection {
               public final class LruCacheKt {
                 ctor public LruCacheKt();
@@ -55,6 +55,7 @@ class ApiFromTextTest : DriverTest() {
         """
 
         check(
+            format = FileFormat.V3,
             compatibilityMode = false,
             inputKotlinStyleNulls = true,
             signatureSource = source,
@@ -66,7 +67,7 @@ class ApiFromTextTest : DriverTest() {
     @Test
     fun `Handle enum constants as default values`() {
         val source = """
-            // Signature format: $CURRENT_SIGNATURE_FORMAT
+            // Signature format: 3.0
             package test.pkg {
               public final class Foo {
                 ctor public Foo();
@@ -89,6 +90,7 @@ class ApiFromTextTest : DriverTest() {
             """
 
         check(
+            format = FileFormat.V3,
             compatibilityMode = false,
             inputKotlinStyleNulls = true,
             signatureSource = source,
@@ -100,7 +102,7 @@ class ApiFromTextTest : DriverTest() {
     @Test
     fun `Handle complex expressions as default values`() {
         val source = """
-            // Signature format: $CURRENT_SIGNATURE_FORMAT
+            // Signature format: 3.0
             package androidx.paging {
               public final class PagedListConfigKt {
                 ctor public PagedListConfigKt();
@@ -122,6 +124,7 @@ class ApiFromTextTest : DriverTest() {
         """
 
         check(
+            format = FileFormat.V3,
             compatibilityMode = false,
             inputKotlinStyleNulls = true,
             signatureSource = source,
@@ -286,7 +289,7 @@ class ApiFromTextTest : DriverTest() {
                 package test.pkg {
                   public deprecated class MyTest {
                     ctor public deprecated MyTest(int, int);
-                    method public static final deprecated void edit(android.content.SharedPreferences, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action);
+                    method public static final deprecated void edit(android.content.SharedPreferences, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit>);
                     field public static deprecated java.util.List<java.lang.String> LIST;
                   }
                 }
@@ -387,7 +390,7 @@ class ApiFromTextTest : DriverTest() {
                   }
                   protected static abstract deprecated interface Foo.Inner3 {
                     method public default void method3();
-                    method public static void method4(int);
+                    method public static abstract void method4(int);
                   }
                 }
                 """
@@ -421,20 +424,19 @@ class ApiFromTextTest : DriverTest() {
     fun `Loading a signature file with annotations on classes, fields, methods and parameters`() {
         @Language("TEXT")
         val source = """
+                // Signature format: 3.0
                 package test.pkg {
-                  @androidx.annotation.UiThread public class MyTest {
+                  @UiThread public class MyTest {
                     ctor public MyTest();
-                    method @androidx.annotation.IntRange(from=10, to=20) public int clamp(int);
-                    method public java.lang.Double? convert(java.lang.Float myPublicName);
-                    field public java.lang.Number? myNumber;
+                    method @IntRange(from=10, to=20) public int clamp(int);
+                    method public Double? convert(Float myPublicName);
+                    field public Number? myNumber;
                   }
                 }
                 """
 
         check(
-            compatibilityMode = false,
-            inputKotlinStyleNulls = true,
-            omitCommonPackages = false,
+            format = FileFormat.V3,
             signatureSource = source,
             api = source
         )
@@ -509,7 +511,7 @@ class ApiFromTextTest : DriverTest() {
                   }
                 }
                 package test.pkg {
-                  public static final class Foo extends java.lang.Enum {
+                  public final class Foo extends java.lang.Enum {
                     enum_constant public static final test.pkg.Foo A;
                     enum_constant public static final test.pkg.Foo B;
                   }
@@ -551,22 +553,21 @@ class ApiFromTextTest : DriverTest() {
     fun `Loading a signature file with default values`() {
         @Language("TEXT")
         val source = """
+                // Signature format: 3.0
                 package test.pkg {
                   public final class Foo {
                     ctor public Foo();
-                    method public final void error(int p = 42, java.lang.Integer? int2 = null);
+                    method public final void error(int p = 42, Integer? int2 = null);
                   }
                   public class Foo2 {
                     ctor public Foo2();
-                    method public void foo(java.lang.String! = null, java.lang.String! = "(Hello) World", int = 42);
+                    method public void foo(String! = null, String! = "(Hello) World", int = 42);
                   }
                 }
                 """
 
         check(
-            compatibilityMode = false,
-            inputKotlinStyleNulls = true,
-            omitCommonPackages = false,
+            format = FileFormat.V3,
             signatureSource = source,
             api = source
         )
@@ -575,6 +576,7 @@ class ApiFromTextTest : DriverTest() {
     @Test
     fun `Signatures with default annotation method values`() {
         val source = """
+                // Signature format: 3.0
                 package libcore.util {
                   public @interface NonNull {
                     method public abstract int from() default java.lang.Integer.MIN_VALUE;
@@ -586,8 +588,7 @@ class ApiFromTextTest : DriverTest() {
                 """
 
         check(
-            inputKotlinStyleNulls = true,
-            compatibilityMode = false,
+            format = FileFormat.V3,
             signatureSource = source,
             api = source
         )
@@ -596,6 +597,7 @@ class ApiFromTextTest : DriverTest() {
     @Test
     fun `Signatures with many annotations`() {
         val source = """
+            // Signature format: 2.0
             package libcore.util {
               @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface NonNull {
                 method public abstract int from() default java.lang.Integer.MIN_VALUE;
@@ -611,9 +613,9 @@ class ApiFromTextTest : DriverTest() {
         """
 
         check(
+            format = FileFormat.V2,
             compatibilityMode = false,
             signatureSource = source,
-            outputKotlinStyleNulls = false,
             api = source
         )
     }
@@ -621,19 +623,20 @@ class ApiFromTextTest : DriverTest() {
     @Test
     fun `Kotlin Properties`() {
         val source = """
+                // Signature format: 2.0
                 package test.pkg {
                   public final class Kotlin {
-                    ctor public Kotlin(java.lang.String property1, int arg2);
-                    method public java.lang.String getProperty1();
-                    method public java.lang.String getProperty2();
-                    method public void setProperty2(java.lang.String p);
-                    property public final java.lang.String property2;
+                    ctor public Kotlin(String property1, int arg2);
+                    method public String getProperty1();
+                    method public String getProperty2();
+                    method public void setProperty2(String p);
+                    property public final String property2;
                   }
                 }
                 """
 
         check(
-            compatibilityMode = true,
+            format = FileFormat.V2,
             signatureSource = source,
             api = source
         )
@@ -716,10 +719,10 @@ class ApiFromTextTest : DriverTest() {
                 package test.pkg {
                   public final class TestKt {
                     ctor public TestKt();
-                    method public static inline <T> void a(T t);
-                    method public static inline <reified T> void b(T t);
-                    method public static inline <reified T> void e(T t);
-                    method public static inline <reified T> void f(T, T t);
+                    method public static inline <T> void a(T);
+                    method public static inline <reified T> void b(T);
+                    method public static inline <reified T> void e(T);
+                    method public static inline <reified T> void f(T, T);
                   }
                 }
                 """
@@ -737,7 +740,7 @@ class ApiFromTextTest : DriverTest() {
                 package test.pkg {
                   public final class TestKt {
                     ctor public TestKt();
-                    method public static suspend inline java.lang.Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p);
+                    method public static suspend inline java.lang.Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit>);
                   }
                 }
                 """
diff --git a/src/test/java/com/android/tools/metalava/BaselineTest.kt b/src/test/java/com/android/tools/metalava/BaselineTest.kt
index fed625a..c836c69 100644
--- a/src/test/java/com/android/tools/metalava/BaselineTest.kt
+++ b/src/test/java/com/android/tools/metalava/BaselineTest.kt
@@ -35,6 +35,7 @@ class BaselineTest : DriverTest() {
                 "ReferencesHidden"
             ),
             baseline = """
+                // Baseline format: 1.0
                 BothPackageInfoAndHtml: test/visible/package-info.java:
                     It is illegal to provide both a package-info.java file and a package.html file for the same package
                 IgnoringSymlink: test/pkg/sub1/sub2/sub3:
@@ -154,4 +155,82 @@ class BaselineTest : DriverTest() {
             checkDoclava1 = false
         )
     }
+
+    @Test
+    fun `Check baseline with show annotations`() {
+        // When using show annotations we should only reference errors that are present in the delta
+        check(
+            includeSystemApiAnnotations = true,
+            extraArguments = arrayOf(
+                ARG_SHOW_ANNOTATION, "android.annotation.TestApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation",
+                ARG_API_LINT
+            ),
+            baseline = """
+                // Baseline format: 1.0
+                PairedRegistration: android.pkg.RegistrationMethods#registerUnpaired2Callback(Runnable):
+                    Found registerUnpaired2Callback but not unregisterUnpaired2Callback in android.pkg.RegistrationMethods
+            """,
+            updateBaseline = true,
+            warnings = "",
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package android.pkg;
+                    import android.annotation.TestApi;
+
+                    public class RegistrationMethods {
+                        // Here we have 3 sets of correct registrations: both register and unregister are
+                        // present from the full API that is part of the Test API.
+                        // There is also 2 incorrect registrations: one in the base API and one in the test
+                        // API.
+                        //
+                        // In the test API, we expect to only see the single missing test API one. The missing
+                        // base one should only be in the base baseline.
+                        //
+                        // Furthermore, this test makes sure that when the set of methods to be consulted spans
+                        // the two APIs (one hidden, one not) the code correctly finds both; e.g. iteration of
+                        // the test API does see the full set of methods even if they're left out of the
+                        // signature files and baselines.
+
+                        public void registerOk1Callback(Runnable r) { }
+                        public void unregisterOk1Callback(Runnable r) { }
+
+                        /** @hide */
+                        @TestApi
+                        public void registerOk2Callback(Runnable r) { }
+                        /** @hide */
+                        @TestApi
+                        public void unregisterOk2Callback(Runnable r) { }
+
+                        // In the Test API, both methods are present
+                        public void registerOk3Callback(Runnable r) { }
+                        /** @hide */
+                        @TestApi
+                        public void unregisterOk3Callback(Runnable r) { }
+
+                        public void registerUnpaired1Callback(Runnable r) { }
+
+                        /** @hide */
+                        @TestApi
+                        public void registerUnpaired2Callback(Runnable r) { }
+                    }
+                    """
+                ),
+                testApiSource
+            ),
+            api = """
+                package android.pkg {
+                  public class RegistrationMethods {
+                    method public void registerOk2Callback(java.lang.Runnable);
+                    method public void registerUnpaired2Callback(java.lang.Runnable);
+                    method public void unregisterOk2Callback(java.lang.Runnable);
+                    method public void unregisterOk3Callback(java.lang.Runnable);
+                  }
+                }
+                """,
+            checkDoclava1 = false
+        )
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
index 1853c57..d1aa931 100644
--- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
@@ -2115,14 +2115,13 @@ CompatibilityCheckTest : DriverTest() {
             compatibilityMode = true,
             inputKotlinStyleNulls = true,
             checkCompatibilityApi = """
-                // Signature format: 2.0
                 package androidx.annotation {
-                  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface RestrictTo {
+                  public abstract class RestrictTo implements java.lang.annotation.Annotation {
                     method public abstract androidx.annotation.RestrictTo.Scope[] value();
                   }
 
-                  public static enum RestrictTo.Scope {
-                    enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
+                  public static final class RestrictTo.Scope extends java.lang.Enum {
+                    enum_constant deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
                     enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
                     enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
                     enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES;
diff --git a/src/test/java/com/android/tools/metalava/ConvertTest.kt b/src/test/java/com/android/tools/metalava/ConvertTest.kt
new file mode 100644
index 0000000..e4a71f7
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/ConvertTest.kt
@@ -0,0 +1,778 @@
+/*
+ * Copyright (C) 2018 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
+
+import org.junit.Test
+
+class ConvertTest : DriverTest() {
+
+    @Test
+    fun `Test conversion flag`() {
+        check(
+            compatibilityMode = true,
+            convertToJDiff = listOf(
+                ConvertData(
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                      }
+                    }
+                    """,
+                    outputFile =
+                    """
+                    <api>
+                    <package name="test.pkg"
+                    >
+                    <class name="MyTest1"
+                     extends="java.lang.Object"
+                     abstract="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <constructor name="MyTest1"
+                     type="test.pkg.MyTest1"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </constructor>
+                    </class>
+                    </package>
+                    </api>
+                    """
+                ),
+                ConvertData(
+                    fromApi =
+                    """
+                    package test.pkg {
+                      public class MyTest2 {
+                      }
+                    }
+                    """,
+                    outputFile =
+                    """
+                    <api>
+                    <package name="test.pkg"
+                    >
+                    <class name="MyTest2"
+                     extends="java.lang.Object"
+                     abstract="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </class>
+                    </package>
+                    </api>
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test convert new with compat mode and api strip`() {
+        check(
+            compatibilityMode = true,
+            convertToJDiff = listOf(
+                ConvertData(
+                    strip = true,
+                    fromApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        method public java.lang.Double convert(java.lang.Float);
+                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public java.lang.Double convert(java.lang.Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public interface MyInterface {
+                      }
+                      public abstract class MyTest3 implements java.util.List {
+                      }
+                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
+                      }
+                    }
+                    """,
+                    baseApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                    }
+                    """,
+                    outputFile =
+                    """
+                    <api>
+                    <package name="test.pkg"
+                    >
+                    <class name="MyTest1"
+                     extends="java.lang.Object"
+                     abstract="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <method name="convert"
+                     return="java.lang.Double"
+                     abstract="false"
+                     native="false"
+                     synchronized="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <parameter name="null" type="java.lang.Float">
+                    </parameter>
+                    </method>
+                    </class>
+                    <class name="MyTest2"
+                     extends="java.lang.Object"
+                     abstract="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <constructor name="MyTest2"
+                     type="test.pkg.MyTest2"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </constructor>
+                    <method name="convert"
+                     return="java.lang.Double"
+                     abstract="false"
+                     native="false"
+                     synchronized="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <parameter name="null" type="java.lang.Float">
+                    </parameter>
+                    </method>
+                    </class>
+                    </package>
+                    <package name="test.pkg.new"
+                    >
+                    <interface name="MyInterface"
+                     abstract="true"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </interface>
+                    <class name="MyTest3"
+                     extends="java.lang.Object"
+                     abstract="true"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </class>
+                    <class name="MyTest4"
+                     extends="java.lang.Object"
+                     abstract="true"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <implements name="test.pkg.new.MyInterface">
+                    </implements>
+                    </class>
+                    </package>
+                    </api>
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test convert new without compat mode and no strip`() {
+        check(
+            compatibilityMode = false,
+            convertToJDiff = listOf(
+                ConvertData(
+                    strip = false,
+                    fromApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        method public java.lang.Double convert(java.lang.Float);
+                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public java.lang.Double convert(java.lang.Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public interface MyInterface {
+                      }
+                      public abstract class MyTest3 implements java.util.List {
+                      }
+                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
+                      }
+                    }
+                    """,
+                    baseApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                    }
+                    """,
+                    outputFile =
+                    """
+                    <api name="convert-output1">
+                    <package name="test.pkg"
+                    >
+                    <class name="MyTest1"
+                     extends="java.lang.Object"
+                     abstract="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <method name="convert"
+                     return="java.lang.Double"
+                     abstract="false"
+                     native="false"
+                     synchronized="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <parameter name="null" type="java.lang.Float">
+                    </parameter>
+                    </method>
+                    <field name="ANY_CURSOR_ITEM_TYPE"
+                     type="java.lang.String"
+                     transient="false"
+                     volatile="false"
+                     value="&quot;vnd.android.cursor.item/*&quot;"
+                     static="true"
+                     final="true"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </field>
+                    </class>
+                    <class name="MyTest2"
+                     extends="java.lang.Object"
+                     abstract="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <constructor name="MyTest2"
+                     type="test.pkg.MyTest2"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </constructor>
+                    <method name="convert"
+                     return="java.lang.Double"
+                     abstract="false"
+                     native="false"
+                     synchronized="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <parameter name="null" type="java.lang.Float">
+                    </parameter>
+                    </method>
+                    </class>
+                    </package>
+                    <package name="test.pkg.new"
+                    >
+                    <interface name="MyInterface"
+                     abstract="true"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </interface>
+                    <class name="MyTest3"
+                     extends="java.lang.Object"
+                     abstract="true"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <implements name="java.util.List">
+                    </implements>
+                    </class>
+                    <class name="MyTest4"
+                     extends="java.lang.Object"
+                     abstract="true"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <implements name="test.pkg.new.MyInterface">
+                    </implements>
+                    </class>
+                    </package>
+                    </api>
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test convert nothing new`() {
+        check(
+            expectedOutput = "No API change detected, not generating diff",
+            compatibilityMode = true,
+            convertToJDiff = listOf(
+                ConvertData(
+                    fromApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        method public java.lang.Double convert(java.lang.Float);
+                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public java.lang.Double convert(java.lang.Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public class MyTest3 {
+                      }
+                    }
+                    """,
+                    baseApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        method public java.lang.Double convert(java.lang.Float);
+                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public java.lang.Double convert(java.lang.Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public class MyTest3 {
+                      }
+                    }
+                    """,
+                    outputFile =
+                    """
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test doclava compat`() {
+        // A few more differences
+        check(
+            compatibilityMode = true,
+            convertToJDiff = listOf(
+                ConvertData(
+                    fromApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public void method(java.util.List<String>);
+                        field protected static final java.lang.String CRLF = "\r\n";
+                        field protected static final byte[] CRLF_BYTES;
+                      }
+                    }
+                    """,
+                    outputFile =
+                    """
+                    <api>
+                    <package name="test.pkg"
+                    >
+                    <class name="MyTest1"
+                     extends="java.lang.Object"
+                     abstract="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <constructor name="MyTest1"
+                     type="test.pkg.MyTest1"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    </constructor>
+                    <method name="method"
+                     return="void"
+                     abstract="false"
+                     native="false"
+                     synchronized="false"
+                     static="false"
+                     final="false"
+                     deprecated="not deprecated"
+                     visibility="public"
+                    >
+                    <parameter name="null" type="java.util.List&lt;String&gt;">
+                    </parameter>
+                    </method>
+                    <field name="CRLF"
+                     type="java.lang.String"
+                     transient="false"
+                     volatile="false"
+                     value="&quot;\r\n&quot;"
+                     static="true"
+                     final="true"
+                     deprecated="not deprecated"
+                     visibility="protected"
+                    >
+                    </field>
+                    <field name="CRLF_BYTES"
+                     type="byte[]"
+                     transient="false"
+                     volatile="false"
+                     value="null"
+                     static="true"
+                     final="true"
+                     deprecated="not deprecated"
+                     visibility="protected"
+                    >
+                    </field>
+                    </class>
+                    </package>
+                    </api>
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test convert new to v2 without compat mode and no strip`() {
+        check(
+            compatibilityMode = false,
+            convertToJDiff = listOf(
+                ConvertData(
+                    strip = false,
+                    format = FileFormat.V2,
+                    fromApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        method public java.lang.Double convert(java.lang.Float);
+                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public java.lang.Double convert(java.lang.Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public interface MyInterface {
+                      }
+                      public abstract class MyTest3 implements java.util.List {
+                      }
+                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
+                      }
+                    }
+                    """,
+                    baseApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                    }
+                    """,
+                    outputFile =
+                    """
+                    // Signature format: 2.0
+                    package test.pkg {
+                      public class MyTest1 {
+                        method public Double convert(Float);
+                        field public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public Double convert(Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public interface MyInterface {
+                      }
+                      public abstract class MyTest3 implements java.util.List {
+                      }
+                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
+                      }
+                    }
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test convert new to v1 signatures with compat mode and no strip`() {
+        // Output is similar to the v2 format, but with fully qualified java.lang types
+        // and fields not included
+        check(
+            compatibilityMode = false,
+            convertToJDiff = listOf(
+                ConvertData(
+                    strip = false,
+                    format = FileFormat.V1,
+                    fromApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        method public java.lang.Double convert(java.lang.Float);
+                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public java.lang.Double convert(java.lang.Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public abstract interface MyInterface {
+                      }
+                      public abstract class MyTest3 implements java.util.List {
+                      }
+                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
+                      }
+                    }
+                    """,
+                    baseApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                    }
+                    """,
+                    outputFile =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        method public java.lang.Double convert(java.lang.Float);
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public java.lang.Double convert(java.lang.Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public abstract interface MyInterface {
+                      }
+                      public abstract class MyTest3 implements java.util.List {
+                      }
+                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
+                      }
+                    }
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test convert v2 to v1`() {
+        check(
+            compatibilityMode = false,
+            convertToJDiff = listOf(
+                ConvertData(
+                    strip = false,
+                    format = FileFormat.V1,
+                    fromApi =
+                    """
+                    // Signature format: 2.0
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method @Deprecated public int clamp(int);
+                        method public Double convert(Float);
+                        field public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                        field @Deprecated public Number myNumber;
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public Double convert(Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public interface MyInterface {
+                      }
+                      public abstract class MyTest3 implements java.util.List {
+                      }
+                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
+                      }
+                    }
+                    """,
+                    outputFile =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        method public java.lang.Double convert(java.lang.Float);
+                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public java.lang.Double convert(java.lang.Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public abstract interface MyInterface {
+                      }
+                      public abstract class MyTest3 implements java.util.List {
+                      }
+                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
+                      }
+                    }
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test convert v1 to v2`() {
+        check(
+            compatibilityMode = false,
+            convertToJDiff = listOf(
+                ConvertData(
+                    strip = false,
+                    format = FileFormat.V2,
+                    fromApi =
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method public deprecated int clamp(int);
+                        method public java.lang.Double convert(java.lang.Float);
+                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                        field public deprecated java.lang.Number myNumber;
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public java.lang.Double convert(java.lang.Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public abstract interface MyInterface {
+                      }
+                      public abstract class MyTest3 implements java.util.List {
+                      }
+                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
+                      }
+                    }
+                    """,
+                    outputFile =
+                    """
+                    // Signature format: 2.0
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                        method @Deprecated public int clamp(int);
+                        method public Double convert(Float);
+                        field public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                        field @Deprecated public Number myNumber;
+                      }
+                      public class MyTest2 {
+                        ctor public MyTest2();
+                        method public Double convert(Float);
+                      }
+                    }
+                    package test.pkg.new {
+                      public interface MyInterface {
+                      }
+                      public abstract class MyTest3 implements java.util.List {
+                      }
+                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
+                      }
+                    }
+                    """
+                )
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt
index 431e4f8..39a11b1 100644
--- a/src/test/java/com/android/tools/metalava/DriverTest.kt
+++ b/src/test/java/com/android/tools/metalava/DriverTest.kt
@@ -57,6 +57,7 @@ import java.net.URL
 import kotlin.text.Charsets.UTF_8
 
 const val CHECK_OLD_DOCLAVA_TOO = false
+const val CHECK_JDIFF = true
 const val CHECK_STUB_COMPILATION = false
 
 abstract class DriverTest {
@@ -180,12 +181,13 @@ abstract class DriverTest {
         return System.getenv("JAVA_HOME")
     }
 
-    /** JDiff file conversion candidates */
+    /** File conversion tasks */
     data class ConvertData(
         val fromApi: String,
-        val toXml: String,
+        val outputFile: String,
         val baseApi: String? = null,
-        val strip: Boolean = true
+        val strip: Boolean = true,
+        val format: FileFormat = FileFormat.JDIFF
     )
 
     protected fun check(
@@ -220,18 +222,18 @@ abstract class DriverTest {
         /** Whether the stubs should be written as documentation stubs instead of plain stubs. Decides
          * whether the stubs include @doconly elements, uses rewritten/migration annotations, etc */
         docStubs: Boolean = false,
+        /** Signature file format */
+        format: FileFormat? = null,
         /** Whether to run in doclava1 compat mode */
-        compatibilityMode: Boolean = true,
+        compatibilityMode: Boolean = format == null || format == FileFormat.V1,
         /** Whether to trim the output (leading/trailing whitespace removal) */
         trim: Boolean = true,
         /** Whether to remove blank lines in the output (the signature file usually contains a lot of these) */
         stripBlankLines: Boolean = true,
         /** Warnings expected to be generated when analyzing these sources */
         warnings: String? = "",
-        /** Signature file format */
-        format: Int? = null,
         /** Whether to run doclava1 on the test output and assert that the output is identical */
-        checkDoclava1: Boolean = compatibilityMode && (format == null || format < 2),
+        checkDoclava1: Boolean = compatibilityMode && (format == null || format == FileFormat.V1),
         checkCompilation: Boolean = false,
         /** Annotations to merge in (in .xml format) */
         @Language("XML") mergeXmlAnnotations: String? = null,
@@ -266,7 +268,7 @@ abstract class DriverTest {
         /** Additional arguments to supply */
         extraArguments: Array<String> = emptyArray(),
         /** Whether we should emit Kotlin-style null signatures */
-        outputKotlinStyleNulls: Boolean = !compatibilityMode,
+        outputKotlinStyleNulls: Boolean = format != null && format.useKotlinStyleNulls(),
         /** Whether we should interpret API files being read as having Kotlin-style nullness types */
         inputKotlinStyleNulls: Boolean = false,
         /** Whether we should omit java.lang. etc from signature files */
@@ -716,31 +718,45 @@ abstract class DriverTest {
             emptyArray()
         }
 
-        val convertToJDiffFiles = mutableListOf<Options.ConvertFile>()
-        val convertToJDiffArgs = if (convertToJDiff.isNotEmpty()) {
+        val convertFiles = mutableListOf<Options.ConvertFile>()
+        val convertArgs = if (convertToJDiff.isNotEmpty()) {
             val args = mutableListOf<String>()
             var index = 1
             for (convert in convertToJDiff) {
                 val signature = convert.fromApi
                 val base = convert.baseApi
-                val convertSig = temporaryFolder.newFile("jdiff-signatures$index.txt")
+                val convertSig = temporaryFolder.newFile("convert-signatures$index.txt")
                 convertSig.writeText(signature.trimIndent(), Charsets.UTF_8)
-                val output = temporaryFolder.newFile("jdiff-output$index.xml")
+                val extension = convert.format.preferredExtension()
+                val output = temporaryFolder.newFile("convert-output$index$extension")
                 val baseFile = if (base != null) {
-                    val baseFile = temporaryFolder.newFile("jdiff-signatures$index-base.txt")
+                    val baseFile = temporaryFolder.newFile("convert-signatures$index-base.txt")
                     baseFile.writeText(base.trimIndent(), Charsets.UTF_8)
                     baseFile
                 } else {
                     null
                 }
-                convertToJDiffFiles += Options.ConvertFile(convertSig, output, baseFile, strip = true)
+                convertFiles += Options.ConvertFile(convertSig, output, baseFile,
+                    strip = true, outputFormat = convert.format)
                 index++
 
                 if (baseFile != null) {
-                    args += if (convert.strip) "-new_api" else ARG_CONVERT_NEW_TO_JDIFF
+                    args +=
+                        when {
+                            convert.format == FileFormat.V1 -> ARG_CONVERT_NEW_TO_V1
+                            convert.format == FileFormat.V2 -> ARG_CONVERT_NEW_TO_V2
+                            convert.strip -> "-new_api"
+                            else -> ARG_CONVERT_NEW_TO_JDIFF
+                        }
                     args += baseFile.path
                 } else {
-                    args += if (convert.strip) "-convert2xml" else ARG_CONVERT_TO_JDIFF
+                    args +=
+                        when {
+                            convert.format == FileFormat.V1 -> ARG_CONVERT_TO_V1
+                            convert.format == FileFormat.V2 -> ARG_CONVERT_TO_V2
+                            convert.strip -> "-convert2xml"
+                            else -> ARG_CONVERT_TO_JDIFF
+                        }
                 }
                 args += convertSig.path
                 args += output.path
@@ -882,7 +898,7 @@ abstract class DriverTest {
         }
 
         val signatureFormatArgs = if (format != null) {
-            arrayOf("$ARG_FORMAT=v$format")
+            arrayOf(format.outputFlag())
         } else {
             emptyArray()
         }
@@ -945,7 +961,7 @@ abstract class DriverTest {
             *checkCompatibilityRemovedReleasedArguments,
             *proguardKeepArguments,
             *manifestFileArgs,
-            *convertToJDiffArgs,
+            *convertArgs,
             *applyApiLevelsXmlArgs,
             *baselineArgs,
             *showAnnotationArguments,
@@ -997,11 +1013,12 @@ abstract class DriverTest {
             assertEquals(stripComments(baseline, stripLineComments = false).trimIndent(), actualText)
         }
 
-        if (convertToJDiffFiles.isNotEmpty()) {
+        if (convertFiles.isNotEmpty()) {
             for (i in 0 until convertToJDiff.size) {
-                val expected = convertToJDiff[i].toXml
-                val converted = convertToJDiffFiles[i].toXmlFile
+                val expected = convertToJDiff[i].outputFile
+                val converted = convertFiles[i].outputFile
                 if (convertToJDiff[i].baseApi != null &&
+                    compatibilityMode &&
                     actualOutput.contains("No API change detected, not generating diff")) {
                     continue
                 }
@@ -1278,11 +1295,18 @@ abstract class DriverTest {
             assertEquals(stripComments(apiXml, stripLineComments = false).trimIndent(), actualText)
         }
 
+        if (CHECK_JDIFF && apiXmlFile != null && convertToJDiff.isNotEmpty()) {
+            // Parse the XML file with jdiff too
+        }
+
         if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && convertToJDiff.isNotEmpty()) {
             var index = 1
             for (convert in convertToJDiff) {
+                if (convert.format != FileFormat.JDIFF) {
+                    continue
+                }
                 val signature = convert.fromApi
-                val expectedXml = convert.toXml
+                val expectedXml = convert.outputFile
                 val base = convert.baseApi
                 val strip = convert.strip
                 val convertSig = temporaryFolder.newFile("doclava-jdiff-signatures$index.txt")
diff --git a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
index 9dcdd21..bad1733 100644
--- a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
+++ b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.tools.metalava
 
-import org.junit.Ignore
 import org.junit.Test
 
 @SuppressWarnings("ALL") // Sample code
@@ -343,7 +342,6 @@ class ExtractAnnotationsTest : DriverTest() {
         )
     }
 
-    @Ignore("Not working reliably -- fails when run and passes when debugged...")
     @Test
     fun `Include merged annotations in exported source annotations`() {
         check(
@@ -352,7 +350,7 @@ class ExtractAnnotationsTest : DriverTest() {
             outputKotlinStyleNulls = false,
             includeSystemApiAnnotations = false,
             omitCommonPackages = false,
-            warnings = "error: Unexpected reference to Nonexistent.Field [AnnotationExtraction]",
+            warnings = "error: Unexpected reference to Nonexistent.Field [InternalError]",
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -361,6 +359,17 @@ class ExtractAnnotationsTest : DriverTest() {
                     public class MyTest {
                         public void test(int arg) { }
                     }"""
+                ),
+                java(
+                    """
+                        package java.util;
+                        public class Calendar {
+                            public static final int ERA = 1;
+                            public static final int YEAR = 2;
+                            public static final int MONTH = 3;
+                            public static final int WEEK_OF_YEAR = 4;
+                        }
+                    """
                 )
             ),
             mergeXmlAnnotations = """<?xml version="1.0" encoding="UTF-8"?>
diff --git a/src/test/java/com/android/tools/metalava/FileFormatTest.kt b/src/test/java/com/android/tools/metalava/FileFormatTest.kt
new file mode 100644
index 0000000..98de4d2
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/FileFormatTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 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
+
+import org.junit.Assert.assertSame
+import org.junit.Test
+
+class FileFormatTest {
+    @Test
+    fun `Check format parsing`() {
+        assertSame(FileFormat.V1, FileFormat.parseHeader("""
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                  }
+                }
+        """.trimIndent()))
+
+        assertSame(FileFormat.V2, FileFormat.parseHeader("""
+            // Signature format: 2.0
+            package libcore.util {
+              @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface NonNull {
+                method public abstract int from() default java.lang.Integer.MIN_VALUE;
+                method public abstract int to() default java.lang.Integer.MAX_VALUE;
+              }
+            }
+        """.trimIndent()))
+
+        assertSame(FileFormat.V3, FileFormat.parseHeader("""
+            // Signature format: 3.0
+            package androidx.collection {
+              public final class LruCacheKt {
+                ctor public LruCacheKt();
+              }
+            }
+        """.trimIndent()))
+
+        assertSame(FileFormat.V2, FileFormat.parseHeader(
+            "// Signature format: 2.0\r\n" +
+                "package libcore.util {\\r\n" +
+                "  @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface NonNull {\r\n" +
+                "    method public abstract int from() default java.lang.Integer.MIN_VALUE;\r\n" +
+                "    method public abstract int to() default java.lang.Integer.MAX_VALUE;\r\n" +
+                "  }\r\n" +
+                "}\r\n"))
+
+        assertSame(FileFormat.BASELINE, FileFormat.parseHeader("""
+                // Baseline format: 1.0
+                BothPackageInfoAndHtml: test/visible/package-info.java:
+                    It is illegal to provide both a package-info.java file and a package.html file for the same package
+                IgnoringSymlink: test/pkg/sub1/sub2/sub3:
+                    Ignoring symlink during package.html discovery directory traversal
+        """.trimIndent()))
+
+        assertSame(FileFormat.JDIFF, FileFormat.parseHeader("""
+            <api>
+            <package name="test.pkg"
+            >
+            </api>
+        """.trimIndent()))
+
+        assertSame(FileFormat.JDIFF, FileFormat.parseHeader("""
+            <?xml version="1.0" encoding="utf-8"?>
+            <api>
+            <package name="test.pkg"
+            >
+            </api>
+        """.trimIndent()))
+
+        assertSame(FileFormat.SINCE_XML, FileFormat.parseHeader("""
+            <?xml version="1.0" encoding="utf-8"?>
+            <api version="2">
+                <class name="android/hardware/Camera" since="1" deprecated="21">
+                    <method name="&lt;init>()V"/>
+                    <method name="addCallbackBuffer([B)V" since="8"/>
+                    <method name="getLogo()Landroid/graphics/drawable/Drawable;"/>
+                    <field name="ACTION_NEW_VIDEO" since="14" deprecated="25"/>
+                </class>
+            </api>
+        """.trimIndent()))
+
+        assertSame(FileFormat.UNKNOWN, FileFormat.parseHeader("""
+            blah blah
+        """.trimIndent()))
+
+        assertSame(FileFormat.UNKNOWN, FileFormat.parseHeader("""
+            <?xml version="1.0" encoding="utf-8"?>
+            <manifest />
+        """.trimIndent()))
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt b/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt
index 2118a92..efc9315 100644
--- a/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt
+++ b/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt
@@ -111,6 +111,54 @@ class JDiffXmlTest : DriverTest() {
         )
     }
 
+    @Test
+    fun `Abstract interfaces`() {
+        check(
+            compatibilityMode = true,
+            format = FileFormat.V2,
+            signatureSource =
+            """
+            // Signature format: 2.0
+            package test.pkg {
+              public interface MyBaseInterface {
+                method public abstract void fun(int, String);
+              }
+            }
+            """,
+            apiXml =
+            """
+            <api>
+            <package name="test.pkg"
+            >
+            <interface name="MyBaseInterface"
+             abstract="true"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <method name="fun"
+             return="void"
+             abstract="true"
+             native="false"
+             synchronized="false"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <parameter name="null" type="int">
+            </parameter>
+            <parameter name="null" type="java.lang.String">
+            </parameter>
+            </method>
+            </interface>
+            </package>
+            </api>
+            """
+        )
+    }
+
     @Test
     fun `Test generics, superclasses and interfaces`() {
         val source = """
@@ -170,6 +218,30 @@ class JDiffXmlTest : DriverTest() {
              deprecated="not deprecated"
              visibility="public"
             >
+            <method name="valueOf"
+             return="test.pkg.Foo"
+             abstract="false"
+             native="false"
+             synchronized="false"
+             static="true"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <parameter name="null" type="java.lang.String">
+            </parameter>
+            </method>
+            <method name="values"
+             return="test.pkg.Foo[]"
+             abstract="false"
+             native="false"
+             synchronized="false"
+             static="true"
+             final="true"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </method>
             <constructor name="Foo"
              type="test.pkg.Foo"
              static="false"
@@ -437,6 +509,30 @@ class JDiffXmlTest : DriverTest() {
              deprecated="not deprecated"
              visibility="public"
             >
+            <method name="valueOf"
+             return="test.pkg.Foo"
+             abstract="false"
+             native="false"
+             synchronized="false"
+             static="true"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <parameter name="null" type="java.lang.String">
+            </parameter>
+            </method>
+            <method name="values"
+             return="test.pkg.Foo[]"
+             abstract="false"
+             native="false"
+             synchronized="false"
+             static="true"
+             final="true"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </method>
             <constructor name="Foo"
              type="test.pkg.Foo"
              static="false"
@@ -559,75 +655,6 @@ class JDiffXmlTest : DriverTest() {
         )
     }
 
-    @Test
-    fun `Test conversion flag`() {
-        check(
-            compatibilityMode = true,
-            convertToJDiff = listOf(
-                ConvertData(
-                    """
-                    package test.pkg {
-                      public class MyTest1 {
-                        ctor public MyTest1();
-                      }
-                    }
-                    """,
-                    toXml =
-                    """
-                    <api>
-                    <package name="test.pkg"
-                    >
-                    <class name="MyTest1"
-                     extends="java.lang.Object"
-                     abstract="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <constructor name="MyTest1"
-                     type="test.pkg.MyTest1"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    </constructor>
-                    </class>
-                    </package>
-                    </api>
-                    """
-                ),
-                ConvertData(
-                    fromApi =
-                    """
-                    package test.pkg {
-                      public class MyTest2 {
-                      }
-                    }
-                    """,
-                    toXml =
-                    """
-                    <api>
-                    <package name="test.pkg"
-                    >
-                    <class name="MyTest2"
-                     extends="java.lang.Object"
-                     abstract="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    </class>
-                    </package>
-                    </api>
-                    """
-                )
-            )
-        )
-    }
-
     @Test
     fun `Generics in interfaces`() {
         check(
@@ -685,7 +712,7 @@ class JDiffXmlTest : DriverTest() {
             >
             <interface name="AbstractList"
              extends="test.pkg.List&lt;A,B,C>"
-             abstract="false"
+             abstract="true"
              static="false"
              final="false"
              deprecated="not deprecated"
@@ -694,7 +721,7 @@ class JDiffXmlTest : DriverTest() {
             </interface>
             <interface name="ConcreteList"
              extends="test.pkg.AbstractList&lt;D,E,F>"
-             abstract="false"
+             abstract="true"
              static="false"
              final="false"
              deprecated="not deprecated"
@@ -702,7 +729,7 @@ class JDiffXmlTest : DriverTest() {
             >
             </interface>
             <interface name="List"
-             abstract="false"
+             abstract="true"
              static="false"
              final="false"
              deprecated="not deprecated"
@@ -820,427 +847,67 @@ class JDiffXmlTest : DriverTest() {
     }
 
     @Test
-    fun `Test convert new with compat mode and api strip`() {
+    fun `Interface extends, compat mode`() {
         check(
             compatibilityMode = true,
-            convertToJDiff = listOf(
-                ConvertData(
-                    strip = true,
-                    fromApi =
-                    """
-                    package test.pkg {
-                      public class MyTest1 {
-                        ctor public MyTest1();
-                        method public deprecated int clamp(int);
-                        method public java.lang.Double convert(java.lang.Float);
-                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
-                        field public deprecated java.lang.Number myNumber;
-                      }
-                      public class MyTest2 {
-                        ctor public MyTest2();
-                        method public java.lang.Double convert(java.lang.Float);
-                      }
-                    }
-                    package test.pkg.new {
-                      public interface MyInterface {
-                      }
-                      public abstract class MyTest3 implements java.util.List {
-                      }
-                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
-                      }
-                    }
-                    """,
-                    baseApi =
-                    """
-                    package test.pkg {
-                      public class MyTest1 {
-                        ctor public MyTest1();
-                        method public deprecated int clamp(int);
-                        field public deprecated java.lang.Number myNumber;
-                      }
-                    }
-                    """,
-                    toXml =
-                    """
-                    <api>
-                    <package name="test.pkg"
-                    >
-                    <class name="MyTest1"
-                     extends="java.lang.Object"
-                     abstract="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <method name="convert"
-                     return="java.lang.Double"
-                     abstract="false"
-                     native="false"
-                     synchronized="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <parameter name="null" type="java.lang.Float">
-                    </parameter>
-                    </method>
-                    </class>
-                    <class name="MyTest2"
-                     extends="java.lang.Object"
-                     abstract="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <constructor name="MyTest2"
-                     type="test.pkg.MyTest2"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    </constructor>
-                    <method name="convert"
-                     return="java.lang.Double"
-                     abstract="false"
-                     native="false"
-                     synchronized="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <parameter name="null" type="java.lang.Float">
-                    </parameter>
-                    </method>
-                    </class>
-                    </package>
-                    <package name="test.pkg.new"
-                    >
-                    <interface name="MyInterface"
-                     abstract="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    </interface>
-                    <class name="MyTest3"
-                     extends="java.lang.Object"
-                     abstract="true"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    </class>
-                    <class name="MyTest4"
-                     extends="java.lang.Object"
-                     abstract="true"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <implements name="test.pkg.new.MyInterface">
-                    </implements>
-                    </class>
-                    </package>
-                    </api>
-                    """
-                )
-            )
+            format = FileFormat.V1,
+            signatureSource = """
+            // Signature format: 2.0
+            package android.companion {
+              public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable {
+              }
+            }
+            """,
+            apiXml =
+            """
+            <api>
+            <package name="android.companion"
+            >
+            <interface name="DeviceFilter"
+             abstract="true"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <implements name="android.os.Parcelable">
+            </implements>
+            </interface>
+            </package>
+            </api>
+            """
         )
     }
 
     @Test
-    fun `Test convert new without compat mode and no strip`() {
+    fun `Interface extends, non-compat mode`() {
         check(
             compatibilityMode = false,
-            convertToJDiff = listOf(
-                ConvertData(
-                    strip = false,
-                    fromApi =
-                    """
-                    package test.pkg {
-                      public class MyTest1 {
-                        ctor public MyTest1();
-                        method public deprecated int clamp(int);
-                        method public java.lang.Double convert(java.lang.Float);
-                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
-                        field public deprecated java.lang.Number myNumber;
-                      }
-                      public class MyTest2 {
-                        ctor public MyTest2();
-                        method public java.lang.Double convert(java.lang.Float);
-                      }
-                    }
-                    package test.pkg.new {
-                      public interface MyInterface {
-                      }
-                      public abstract class MyTest3 implements java.util.List {
-                      }
-                      public abstract class MyTest4 implements test.pkg.new.MyInterface {
-                      }
-                    }
-                    """,
-                    baseApi =
-                    """
-                    package test.pkg {
-                      public class MyTest1 {
-                        ctor public MyTest1();
-                        method public deprecated int clamp(int);
-                        field public deprecated java.lang.Number myNumber;
-                      }
-                    }
-                    """,
-                    toXml =
-                    """
-                    <api>
-                    <package name="test.pkg"
-                    >
-                    <class name="MyTest1"
-                     extends="java.lang.Object"
-                     abstract="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <method name="convert"
-                     return="java.lang.Double"
-                     abstract="false"
-                     native="false"
-                     synchronized="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <parameter name="null" type="java.lang.Float">
-                    </parameter>
-                    </method>
-                    <field name="ANY_CURSOR_ITEM_TYPE"
-                     type="java.lang.String"
-                     transient="false"
-                     volatile="false"
-                     value="&quot;vnd.android.cursor.item/*&quot;"
-                     static="true"
-                     final="true"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    </field>
-                    </class>
-                    <class name="MyTest2"
-                     extends="java.lang.Object"
-                     abstract="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <constructor name="MyTest2"
-                     type="test.pkg.MyTest2"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    </constructor>
-                    <method name="convert"
-                     return="java.lang.Double"
-                     abstract="false"
-                     native="false"
-                     synchronized="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <parameter name="null" type="java.lang.Float">
-                    </parameter>
-                    </method>
-                    </class>
-                    </package>
-                    <package name="test.pkg.new"
-                    >
-                    <interface name="MyInterface"
-                     abstract="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    </interface>
-                    <class name="MyTest3"
-                     extends="java.lang.Object"
-                     abstract="true"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <implements name="java.util.List">
-                    </implements>
-                    </class>
-                    <class name="MyTest4"
-                     extends="java.lang.Object"
-                     abstract="true"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <implements name="test.pkg.new.MyInterface">
-                    </implements>
-                    </class>
-                    </package>
-                    </api>
-                    """
-                )
-            )
-        )
-    }
-
-    @Test
-    fun `Test convert nothing new`() {
-        check(
-            expectedOutput = "No API change detected, not generating diff",
-            compatibilityMode = true,
-            convertToJDiff = listOf(
-                ConvertData(
-                    fromApi =
-                    """
-                    package test.pkg {
-                      public class MyTest1 {
-                        ctor public MyTest1();
-                        method public deprecated int clamp(int);
-                        method public java.lang.Double convert(java.lang.Float);
-                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
-                        field public deprecated java.lang.Number myNumber;
-                      }
-                      public class MyTest2 {
-                        ctor public MyTest2();
-                        method public java.lang.Double convert(java.lang.Float);
-                      }
-                    }
-                    package test.pkg.new {
-                      public class MyTest3 {
-                      }
-                    }
-                    """,
-                    baseApi =
-                    """
-                    package test.pkg {
-                      public class MyTest1 {
-                        ctor public MyTest1();
-                        method public deprecated int clamp(int);
-                        method public java.lang.Double convert(java.lang.Float);
-                        field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
-                        field public deprecated java.lang.Number myNumber;
-                      }
-                      public class MyTest2 {
-                        ctor public MyTest2();
-                        method public java.lang.Double convert(java.lang.Float);
-                      }
-                    }
-                    package test.pkg.new {
-                      public class MyTest3 {
-                      }
-                    }
-                    """,
-                    toXml =
-                    """
-                    """
-                )
-            )
-        )
-    }
-
-    @Test
-    fun `Test doclava compat`() {
-        // A few more differences
-        check(
-            compatibilityMode = true,
-            convertToJDiff = listOf(
-                ConvertData(
-                    fromApi =
-                    """
-                    package test.pkg {
-                      public class MyTest1 {
-                        ctor public MyTest1();
-                        method public void method(java.util.List<String>);
-                        field protected static final java.lang.String CRLF = "\r\n";
-                        field protected static final byte[] CRLF_BYTES;
-                      }
-                    }
-                    """,
-                    toXml =
-                    """
-                    <api>
-                    <package name="test.pkg"
-                    >
-                    <class name="MyTest1"
-                     extends="java.lang.Object"
-                     abstract="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <constructor name="MyTest1"
-                     type="test.pkg.MyTest1"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    </constructor>
-                    <method name="method"
-                     return="void"
-                     abstract="false"
-                     native="false"
-                     synchronized="false"
-                     static="false"
-                     final="false"
-                     deprecated="not deprecated"
-                     visibility="public"
-                    >
-                    <parameter name="null" type="java.util.List&lt;String&gt;">
-                    </parameter>
-                    </method>
-                    <field name="CRLF"
-                     type="java.lang.String"
-                     transient="false"
-                     volatile="false"
-                     value="&quot;\r\n&quot;"
-                     static="true"
-                     final="true"
-                     deprecated="not deprecated"
-                     visibility="protected"
-                    >
-                    </field>
-                    <field name="CRLF_BYTES"
-                     type="byte[]"
-                     transient="false"
-                     volatile="false"
-                     value="null"
-                     static="true"
-                     final="true"
-                     deprecated="not deprecated"
-                     visibility="protected"
-                    >
-                    </field>
-                    </class>
-                    </package>
-                    </api>
-                    """
-                )
-            )
+            format = FileFormat.V2,
+            signatureSource = """
+            // Signature format: 2.0
+            package android.companion {
+              public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable {
+              }
+            }
+            """,
+            apiXml =
+            """
+            <api>
+            <package name="android.companion"
+            >
+            <interface name="DeviceFilter"
+             extends="android.os.Parcelable"
+             abstract="true"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </interface>
+            </package>
+            </api>
+            """
         )
     }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
index c43bfc6..90332ec 100644
--- a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
+++ b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
@@ -23,7 +23,7 @@ class NullnessMigrationTest : DriverTest() {
     @Test
     fun `Test Kotlin-style null signatures`() {
         check(
-            compatibilityMode = false,
+            format = FileFormat.V3,
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -42,6 +42,7 @@ class NullnessMigrationTest : DriverTest() {
                 androidxNullableSource
             ),
             api = """
+                // Signature format: 3.0
                 package test.pkg {
                   public class MyTest {
                     ctor public MyTest();
@@ -327,7 +328,7 @@ class NullnessMigrationTest : DriverTest() {
     @Test
     fun `Check type use annotations`() {
         check(
-            format = 2, // compat=false, kotlin-style-nulls=false
+            format = FileFormat.V2, // compat=false, kotlin-style-nulls=false
             sourceFiles = *arrayOf(
                 java(
                     """
diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt
index 8dfcedc..dcaf7ee 100644
--- a/src/test/java/com/android/tools/metalava/OptionsTest.kt
+++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt
@@ -231,6 +231,15 @@ JDiff:
 --convert-new-to-jdiff <old> <new> <xml>  Reads in the given old and new api files,
                                           computes the difference, and writes out only the
                                           new parts of the API in the JDiff XML format.
+--convert-to-v1 <sig> <sig>               Reads in the given signature file and writes it
+                                          out as a signature file in the original
+                                          v1/doclava format.
+--convert-to-v2 <sig> <sig>               Reads in the given signature file and writes it
+                                          out as a signature file in the new signature
+                                          format, v2.
+--convert-new-to-v2 <old> <new> <sig>     Reads in the given old and new api files,
+                                          computes the difference, and writes out only the
+                                          new parts of the API in the v2 format.
 
 Statistics:
 --annotation-coverage-stats               Whether metalava should emit coverage statistics
diff --git a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
index 4c17cd5..c2e6832 100644
--- a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
+++ b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
@@ -246,7 +246,7 @@ class ShowAnnotationTest : DriverTest() {
             ),
             // Empty API: showUnannotated=false
             api = """
-                """,
+                """.trimIndent(),
             includeSystemApiAnnotations = true,
             extraArguments = arrayOf(
                 ARG_SHOW_ANNOTATION, "android.annotation.TestApi",
diff --git a/src/test/java/com/android/tools/metalava/StubsTest.kt b/src/test/java/com/android/tools/metalava/StubsTest.kt
index dbfb6f0..e48c399 100644
--- a/src/test/java/com/android/tools/metalava/StubsTest.kt
+++ b/src/test/java/com/android/tools/metalava/StubsTest.kt
@@ -1441,7 +1441,7 @@ class StubsTest : DriverTest() {
                 package test.pkg {
                   public class Foo {
                     ctor public Foo();
-                    method public void foo(int, java.util.Map<java.lang.String, java.lang.String>!);
+                    method public void foo(int, java.util.Map<java.lang.String, java.lang.String>);
                   }
                 }
                 """
@@ -1450,7 +1450,7 @@ class StubsTest : DriverTest() {
                 package test.pkg {
                   public class Foo {
                     ctor public Foo();
-                    method public void foo(int, java.util.Map<java.lang.String,java.lang.String>!);
+                    method public void foo(int, java.util.Map<java.lang.String,java.lang.String>);
                   }
                 }
                 """
@@ -2242,18 +2242,18 @@ class StubsTest : DriverTest() {
                   }
                   public class Generics.MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> {
                     ctor public Generics.MyClass();
-                    method public java.util.Map<X,java.util.Map<Y,java.lang.String>>! createMap(java.util.List<X>!) throws java.io.IOException;
-                    method public java.util.List<X>! foo();
+                    method public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X>) throws java.io.IOException;
+                    method public java.util.List<X> foo();
                   }
                   public static interface Generics.PublicInterface<A, B> {
-                    method public java.util.Map<A,java.util.Map<B,java.lang.String>>! createMap(java.util.List<A>!) throws java.io.IOException;
+                    method public java.util.Map<A,java.util.Map<B,java.lang.String>> createMap(java.util.List<A>) throws java.io.IOException;
                   }
                   public abstract class Generics.PublicParent<A, B extends java.lang.Number> {
                     ctor public Generics.PublicParent();
-                    method protected abstract java.util.List<A>! foo();
+                    method protected abstract java.util.List<A> foo();
                   }
                 }
-                    """,
+                """,
             source = """
                     package test.pkg;
                     @SuppressWarnings({"unchecked", "deprecation", "all"})
@@ -3635,7 +3635,7 @@ class StubsTest : DriverTest() {
             package test.pkg {
               public class Foo {
                 ctor public Foo();
-                method @Deprecated protected boolean inClass(String!);
+                method @Deprecated protected boolean inClass(String);
               }
             }
             """,
diff --git a/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt b/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt
index 6d5cbc8..112d513 100644
--- a/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt
@@ -17,14 +17,14 @@
 package com.android.tools.metalava.model
 
 import com.android.tools.metalava.JAVA_LANG_STRING
-import com.android.tools.metalava.options
+import com.android.tools.metalava.compatibility
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
 class TypeItemTest {
     @Test
     fun test() {
-        options.omitCommonPackages = true
+        compatibility.omitCommonPackages = true
         assertThat(TypeItem.shortenTypes("@android.support.annotation.Nullable")).isEqualTo("@Nullable")
         assertThat(TypeItem.shortenTypes(JAVA_LANG_STRING)).isEqualTo("String")
         assertThat(TypeItem.shortenTypes("java.lang.reflect.Method")).isEqualTo("java.lang.reflect.Method")
diff --git a/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt b/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt
index d11074f..476b654 100644
--- a/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt
@@ -77,7 +77,7 @@ class TextTypeItemTest {
                 method public D build();
               }
             }
-        """, false
+        """.trimIndent(), false
         )
         val cls = codebase.findClass("androidx.navigation.NavDestinationBuilder")
         val method = cls?.findMethod("build", "") as TextMethodItem
@@ -113,7 +113,7 @@ class TextTypeItemTest {
                 method public D build();
               }
             }
-        """, false
+        """.trimIndent(), false
         )
         val cls = codebase.findClass("test.pkg.TestClass")
         val method = cls?.findMethod("build", "") as TextMethodItem
@@ -145,7 +145,7 @@ class TextTypeItemTest {
                 method public java.util.Set<java.util.Map.Entry<K, V>> entrySet();
               }
             }
-        """, false
+        """.trimIndent(), false
         )
         val cls = codebase.findClass("test.pkg.EnumMap")
         val method = cls?.findMethod("clone", "") as TextMethodItem
diff --git a/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt b/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt
index 9a00d29..8dc559c 100644
--- a/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt
@@ -47,7 +47,7 @@ class TextTypeParameterItemTest {
                 method public D build();
               }
             }
-        """, false
+        """.trimIndent(), false
         )
         val cls = codebase.findClass("androidx.navigation.NavDestinationBuilder")
         val method = cls?.findMethod("build", "") as TextMethodItem
-- 
GitLab