DO NOT MERGE

Merge pie-platform-release (PPRL.181105.017, history only) into master

Bug: 118454372
Change-Id: Ia9f5f43258e329b6dc70b81e5adbd63824dfc4d5
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 4eefbdf..b63075d 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -2,6 +2,10 @@
 <project version="4">
   <component name="CompilerConfiguration">
     <bytecodeTargetLevel>
+      <module name="com.android.metalava.main" target="1.8" />
+      <module name="com.android.metalava.test" target="1.8" />
+      <module name="metalava.stub-annotations.main" target="1.8" />
+      <module name="metalava.stub-annotations.test" target="1.8" />
       <module name="metalava_main" target="1.8" />
       <module name="metalava_test" target="1.8" />
       <module name="stub-annotations_main" target="1.8" />
diff --git a/.idea/dictionaries/metalava.xml b/.idea/dictionaries/metalava.xml
index 6b14ab5..bb6f28f 100644
--- a/.idea/dictionaries/metalava.xml
+++ b/.idea/dictionaries/metalava.xml
@@ -2,43 +2,67 @@
   <dictionary name="metalava">
     <words>
       <w>androidx</w>
+      <w>anim</w>
       <w>apidocsdir</w>
+      <w>apis</w>
       <w>argnum</w>
       <w>bootclasspath</w>
+      <w>bytecode</w>
       <w>canonicalized</w>
+      <w>cherrypick</w>
+      <w>clinit</w>
       <w>codebases</w>
+      <w>compat</w>
       <w>ctor</w>
       <w>dataname</w>
       <w>devsite</w>
+      <w>dimen</w>
       <w>doclava</w>
       <w>doclet</w>
       <w>docletpath</w>
       <w>doconly</w>
       <w>dokka</w>
+      <w>droiddoc</w>
       <w>federationapi</w>
+      <w>filenames</w>
       <w>gcmref</w>
+      <w>genrule</w>
       <w>gitiles</w>
+      <w>gradle</w>
       <w>htmldir</w>
       <w>ide</w>
       <w>ident</w>
       <w>includeable</w>
       <w>inheritdoc</w>
+      <w>initializers</w>
+      <w>inlined</w>
+      <w>innerclass</w>
+      <w>instanceof</w>
       <w>interop</w>
+      <w>intra</w>
       <w>jaif</w>
       <w>javadocs</w>
+      <w>jdiff</w>
       <w>jvmstatic</w>
       <w>knowntags</w>
       <w>kotlinx</w>
       <w>lerror</w>
       <w>libcore</w>
       <w>libraryroot</w>
+      <w>loggable</w>
       <w>metalava</w>
+      <w>mmodule</w>
       <w>navtree</w>
       <w>navtreeonly</w>
       <w>nodefaultassets</w>
       <w>nodocs</w>
+      <w>nullable</w>
+      <w>nullness</w>
       <w>offlinemode</w>
       <w>parsecomments</w>
+      <w>prebuilts</w>
+      <w>proguard</w>
+      <w>quickfix</w>
       <w>realtime</w>
       <w>referenceonly</w>
       <w>resourcesdir</w>
@@ -51,23 +75,31 @@
       <w>skipnative</w>
       <w>skippable</w>
       <w>snackbar</w>
+      <w>soong</w>
       <w>sourcepath</w>
+      <w>spannable</w>
+      <w>srcjar</w>
       <w>staticonly</w>
       <w>strippable</w>
       <w>stubimportpackages</w>
       <w>stubpackages</w>
       <w>stubsourceonly</w>
+      <w>styleable</w>
       <w>templatedir</w>
+      <w>testroot</w>
       <w>throwables</w>
       <w>toroot</w>
       <w>typelist</w>
       <w>typemap</w>
+      <w>unescape</w>
       <w>unhide</w>
+      <w>uninstantiable</w>
       <w>unshorten</w>
+      <w>usecase</w>
       <w>werror</w>
       <w>xmlfile</w>
       <w>yaml</w>
       <w>zipfile</w>
     </words>
   </dictionary>
-</component>
+</component>
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..15a15b2
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" addBOMForNewFiles="with NO BOM" />
+</project>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 42f9f3a..63de93b 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -1,6 +1,7 @@
 <component name="InspectionProjectProfileManager">
   <profile version="1.0">
     <option name="myName" value="Project Default" />
+    <inspection_tool class="GroovyAssignabilityCheck" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="LoopToCallChain" enabled="false" level="INFO" enabled_by_default="false" />
     <inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..a5f05cd
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="BintrayJCenter" />
+      <option name="name" value="BintrayJCenter" />
+      <option name="url" value="https://jcenter.bintray.com/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="Google" />
+      <option name="name" value="Google" />
+      <option name="url" value="https://dl.google.com/dl/android/maven2/" />
+    </remote-repository>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..8b7f4af
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Kotlin2JsCompilerArguments">
+    <option name="sourceMapEmbedSources" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 4ec55f7..2061683 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,9 +27,41 @@
     manifest: "manifest.txt",
 }
 
-java_library_host {
+java_library {
     name: "stub-annotations",
+    host_supported: true,
     srcs: [
         "stub-annotations/src/main/java/**/*.java",
     ],
+    sdk_version: "core_current",
+}
+
+genrule {
+    name: "private-stub-annotations",
+    tools: [
+        "soong_zip",
+        "metalava",
+    ],
+    srcs: [
+        "stub-annotations/src/main/java/**/*.java",
+    ],
+    cmd: "($(location metalava) --no-banner --copy-annotations tools/metalava/stub-annotations " +
+         "$(genDir)/private-stub-annotations) && ($(location soong_zip) -o $(out) -C $(genDir) -D $(genDir))",
+    out: [
+        "private-stub-annotations.srcjar",
+    ],
+}
+
+java_library {
+    name: "private-stub-annotations-jar",
+    host_supported: true,
+    srcs: [
+        ":private-stub-annotations",
+    ],
+    sdk_version: "core_current",
+}
+
+droiddoc_exported_dir {
+    name: "metalava-manual",
+    path: "manual",
 }
diff --git a/FORMAT.md b/FORMAT.md
new file mode 100644
index 0000000..efe73b9
--- /dev/null
+++ b/FORMAT.md
@@ -0,0 +1,503 @@
+# Signature Formats
+
+This document describes the signature file format created and used by metalava,
+doclava, apicheck, etc.
+
+There are currently 3 versions of this format:
+
+1. The format emitted by doclava, and used for Android's signature files up
+   through Android P. Note that this isn't actually a single format; it evolved
+   over time, so older signature files vary a bit (many of these changes were
+   due to bugs getting fixed, such as type parameters missing from classes
+   and methods until they start appearing), and some were deliberate changes,
+   such as dropping the "final" modifier in front of every member if the
+   containing class is final.
+   
+2. The "new" format, which is described below, and is used in Android Q. This
+   format adds new information, such as annotations, parameter names and default
+   values, as well as cleans up a number of things (such as dropping
+   java.lang. prefixes on types, etc)
+   
+3. This is format v2, but will all nullness annotations replaced by a
+   Kotlin-syntax, e.g. "?" for nullable types, "!" for unknown/platform types,
+   and no suffix for non-nullable types. The initial plan was to include this
+   in format v2, but it was deferred since type-use annotations introduces
+   somple complexities in the implementation.
+    
+
+## Motivation
+
+Why did we change from the historical doclava signature format (v1)
+to a new format?
+
+In order to support Kotlin better (though this will also benefit Java
+developers), we'd like to have nullness annotations (as well as some other
+annotations) be a formal part of the SDK.
+
+That means the annotations should be part of the signature files too -- such
+that we can not just record explicitly what the API contract is, but also
+enforce that changes are not only deliberate changes but also compatible
+changes. (For example, you can change the return value of a final method from
+nullable to non null, but not the other way around.)
+
+And if we were going to change the signature format, we might as well make some
+other changes too.
+
+
+### Comments
+
+In v2, line comments (starting with //) are allowed. This allows us to leave
+reminders and other issues with the signature source (though the update-api task
+will generally blow these away, so use sparingly.)
+
+### Header
+
+New signature files (v2+) generally include a file header comment which states
+the version number. This makes it possible for tools to more safely interpret
+signature files. For example, in v3 the type "String" means "@NonNull String",
+but in v2 "String" means "String with unknown nullness".
+
+The header looks like this:
+
+```
+// Signature format: 2.0
+```
+
+Here "2" is the major format version; the .0 allows for compatible minor
+variations of the format.
+
+### Include Annotations
+
+The new signature format now includes annotations; not all annotations (such as
+@Override etc); only those which are significant for the API, such as nullness
+annotations, etc.
+
+Annotations are included on the same line as the class/field/method, right
+before the modifiers.
+
+Here's how this looks:
+
+
+```
+  method @Nullable public static Integer compute1(@Nullable java.util.List<java.lang.String>);
+```
+
+
+(Notice how the annotations are not using fully qualified name; that's discussed
+below.)
+
+The annotations to be included are annotations for annotation types that are not
+hidden, and have class file or runtime retention.
+
+The annotations should be sorted alphabetically by fully qualified name.
+
+
+### Use Special Syntax or Nullness Annotations
+
+(Note: Only in version format 3+)
+
+As a special optimization, since we eventually want **all** APIs to have
+explicit nullness, use Kotlin's syntax for nullness. That means that for
+nullable elements, we add "?" after the type, for unknown nullness we add "!",
+and otherwise there's no suffix. In other words:
+
+
+<table>
+  <tr>
+   <td>
+   </td>
+   <td>Java Type
+   </td>
+   <td>Signature File Type
+   </td>
+  </tr>
+  <tr>
+   <td>Nullable
+   </td>
+   <td>@Nullable String
+   </td>
+   <td>String?
+   </td>
+  </tr>
+  <tr>
+   <td>Not nullable
+   </td>
+   <td>@NonNull String
+   </td>
+   <td>String
+   </td>
+  </tr>
+  <tr>
+   <td>Unknown nullability
+   </td>
+   <td>String
+   </td>
+   <td>String!
+   </td>
+  </tr>
+</table>
+
+
+The above signature line is turned into
+
+
+```
+ method public Integer? compute1(java.util.List<java.lang.String!>?);
+```
+
+
+### Clean Up Terminology 
+
+Format v2 also cleans up some of the terminology used to describe the class
+structure in the signature file. For example, in v1, an interface is called an
+"abstract interface"; an interface extending another interface is said to
+"implement" it instead of "extend"-ing it, etc; enums and annotations are just
+referred to as classes that extend java.lang.Enum, or java.lang.Annotation etc.
+
+With these changes, these lines from v1 signature files:
+
+
+```
+  public abstract interface List<E> implements java.util.Collection { ... }
+  public class TimeUnit extends java.lang.Enum { ... }
+  public abstract class SuppressLint implements java.lang.annotation.Annotation { ... }
+```
+
+
+are replaced by
+
+
+```
+    public interface List<E> extends java.util.Collection<E> { ... }
+    public enum TimeUnit { ... }
+    public @interface SuppressLint { ... }
+```
+
+
+
+### Use Generics Everywhere 
+
+The v1 signature files uses raw types in some places but not others.  Note that
+in the above it was missing from super interface Collection:
+
+
+```
+  public abstract interface List<E> implements java.util.Collection { ... }
+```
+
+
+ whereas in the v2 format it's included:
+
+
+```
+    public interface List<E> extends java.util.Collection<E> { ... }
+```
+
+
+Similarly, v1 used erasure in throws clauses. For example, for this method:
+
+
+```
+    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
+```
+
+v1 used this signature:
+
+
+```
+ method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws java.lang.Throwable;
+```
+
+Note how that's "throws Throwable" instead of "throws X". This results in b/110302703.
+
+In the v2 format we instead use the correct throws type:
+
+```
+ method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws X;
+```
+
+
+### Support Annotations
+
+The old format was completely missing annotation type methods:
+
+```
+  public static abstract class ViewDebug.ExportedProperty implements java.lang.annotation.Annotation {
+  }
+```
+
+We need to include annotation member methods, as well as their default values
+since those are API-significant. Here's how this looks in the v2 file format
+(also applying the @interface terminology change described above) :
+
+
+```
+  public static @interface ViewDebug.ExportedProperty {
+    method public abstract String category() default "";
+    method public abstract boolean deepExport() default false;
+    method public abstract android.view.ViewDebug.FlagToString[] flagMapping() default {};
+    method public abstract boolean formatToHexString() default false;
+    method public abstract boolean hasAdjacentMapping() default false;
+    method public abstract android.view.ViewDebug.IntToString[] indexMapping() default {};
+    method public abstract android.view.ViewDebug.IntToString[] mapping() default {};
+    method public abstract String prefix() default "";
+    method public abstract boolean resolveId() default false;
+  }
+```
+
+
+### Support Kotlin Modifiers 
+
+This doesn't currently apply to the SDK, but the signature files are also used
+in the support library, and some of these are written in Kotlin and exposes
+Kotlin-specific APIs.
+
+That means the v2 format can express API-significant aspects of Kotlin. This
+includes special modifiers, such as sealed, inline, operator, infix, etc:
+
+```
+ method public static operator int get(android.graphics.Bitmap, int x, int y);
+ method public static infix android.graphics.Rect and(android.graphics.Rect, android.graphics.Rect r);
+```
+
+### Support Kotlin Properties 
+
+Kotlin's Java support means that it wil take a Kotlin property and compile it
+into getters and setters which you can call from Java. But you cannot calls
+these getters and setters from Kotlin; you **must** use the property
+syntax. Therefore, we need to also capture properties in the signature files. If
+you have this Kotlin code:
+
+
+```
+         var property2: String? = "initial"
+```
+
+it will get recorded in the signature files like this:
+
+```
+         property public java.lang.String? property2 = "initial";
+         method public java.lang.String? getProperty2();
+         method public void setProperty2(java.lang.String? p);
+```
+
+The last two elements are "redundant"; they could be computed from the property
+name (and included if the property declaration uses special annotations to name
+the getters and setters away from the defaults), but it's helpful to be explicit
+(and this allows us to specify the default value).
+
+### Support Named Parameters 
+
+Kotlin supports default values for parameters, and these are a part of the API
+contract, so we need to include them in the signature format.
+
+Here's an example:
+
+```
+    method public static void edit(android.content.SharedPreferences, boolean commit);
+```
+
+In v1 files we only list type names, but in v2 we allow an optional parameter
+name to be specified; "commit" in the above.
+
+Note that this isn't just for Kotlin. Just like there are special nullness
+annotations to mark up the null contract for an element, we will also have a
+special annotation to explicitly name a Java parameter:
+@android.annotation.ParameterName (which is hidden). This obviously isn't usable
+from Java, but Kotlin client code can now reference the parameter.
+
+Therefore, the following Java code (not signature code) will also produce
+exactly the same signature as the above:
+
+```
+    public static void edit(SharedPreferences prefs, @ParameterName("commit") boolean ct) {…}
+```
+
+(Note how the implementation parameter doesn't have to match the public, API
+name of the parameter.)
+
+### Support Default Values 
+
+In addition to named parameters, Kotlin also supports default values. These are
+also be part of the v2 signature since (as an example) removing a default value
+is a compile-incompatible change.
+
+Therefore, the v2 format allows default values to be specified after the type
+and/or parameter name:
+
+```
+    method public static void edit(SharedPreferences, boolean commit = false);
+```
+
+For Kotlin code, the default parameter values are extracted automatically, and
+for Java, just as with parameter names, you can specify a special annotation to
+record the default value for usage from languages that support default parameter
+values:
+
+```
+    public static void edit(SharedPreferences prefs, @DefaultValue("false") boolean ct) {…}
+```
+
+
+### Include Inherited Methods
+
+Consider a scenario where a public class extends a hidden class, and that hidden
+class defines a public method.
+
+Doclava did not include these methods in the signature files, but they **were**
+present in the stub files (and therefore part of the API). In the v2 signature
+file format, we include these.
+
+An example of this is StringBuilder#setLength. According to the old signature
+files, that method does not exist, but clearly it's there in the SDK. The reason
+this happens is that StringBuilder is a public class which extends hidden class
+AbstractStringBuilder, which defines the public method setLength.
+
+
+### No Hardcoded Enum Methods
+
+Doclava always inserted two special methods in the signature files for every
+enum: values() and valueOf():
+
+```
+  public static final class CursorJoiner.Result extends java.lang.Enum {
+    method public static android.database.CursorJoiner.Result valueOf(java.lang.String);
+    method public static final android.database.CursorJoiner.Result[] values();
+    enum_constant public static final android.database.CursorJoiner.Result BOTH;
+    enum_constant public static final android.database.CursorJoiner.Result LEFT;
+    enum_constant public static final android.database.CursorJoiner.Result RIGHT;
+  }
+```
+
+It didn't do that in stubs, because you can't: those are special methods
+generated by the compiler. There's no reason to list these in the signature
+files since they're entirely implied by the enum, you can't change them, and
+it's just extra noise.
+
+In the new v2 format these are no longer present:
+
+```
+  public static enum CursorJoiner.Result {
+    enum_constant public static final android.database.CursorJoiner.Result BOTH;
+    enum_constant public static final android.database.CursorJoiner.Result LEFT;
+    enum_constant public static final android.database.CursorJoiner.Result RIGHT;
+  }
+```
+
+### Remove "deprecated" Modifier
+
+The old signature file format used "deprecated" as if it was a modifier. In the
+new format, we instead list these using annotations, @Deprecated.
+
+### Standard Modifier Order
+
+Doclava had a "random" (but stable) order of modifiers.
+
+In the new signature format, we're using the standard modifier order for Java
+and Kotlin, wihch more closely mirrors what is done in the source code.
+
+Version format 1 order:
+
+```
+public/protected/private default static final abstract synchronized transient volatile
+```
+
+Version format 2 order:
+
+```
+public/protected/internal/private abstract default static final transient volatile synchronized
+```
+
+The above list doesn't include the Kotlin modifiers, which are inserted
+according to the Kotlin language style guide:
+https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers
+
+### Sort Classes By Fully Qualified Names
+
+In "extends" lists, the signature file can list a comma separated list of
+classes. The classes are listed by fully qualified name, but in v1 it was sorted
+by simple name. In the v2 format, we sort by fully qualified name instead.
+
+### Use Wildcards Consistently
+
+Doclava (v1) would sometimes use the type bound <?> and other times use <?
+extends Object>. These are equivalent. In the v2 format, <? extends Object> is
+always written as <?>.
+
+### Annotation Simple Names
+
+We have a number of annotations which are significant for the API -- not just
+the nullness as deprecation ones (which are specially supported in v3 via the
+?/! Kotlin syntax and the deprecated "modifier"), but annotations for permission
+requirements, range constraints, valid constant values for an integer, and so
+on.
+
+In the codebase, these are typically in the android.annotation. package,
+referencing annotation classes that are generally **not** part of the API. When
+we generate the SDK, we translate these into publicly known annotations,
+androidx.annotation, such that Studio, lint, the Kotlin compiler and others can
+recognize the metadata.
+
+That begs the question: which fully qualified name should we put in the
+signature file? The one that appeared in the source (which is hidden, or in the
+case of Kotlin code, a special JetBrains nullness annotation), or the one that
+it gets translated into?
+
+In v2 we do neither: We use only the simple name of the annotations in the
+signature file, for annotations that are in the well known packages. In other
+words, instead of any of these alternative declarations:
+
+```
+   method public void setTitleTextColor(@android.annotation.ColorInt int);
+   method public void setTitleTextColor(@android.support.annotation.ColorInt int);
+   method public void setTitleTextColor(@androidx.annotation.ColorInt int);
+```
+
+in v2 we have simply 
+
+```
+   method public void setTitleTextColor(@ColorInt int);
+```
+
+### Simple Names in Java.lang
+
+In Java files, you can implicitly reference classes in java.lang without
+importing them. In v2 offer the same thing in signature files. There are several
+classes from java.lang that are used in lots of places in the signature file
+(java.lang.String alone is present in over 11,000 lines of the API file), and
+other common occurrences are java.lang.Class, java.lang.Integer,
+java.lang.Runtime, etc.
+
+This basically builds on the same idea from having an implicit package for
+annotations, and doing the same thing for java.lang: Omitting it when writing
+signature files, and implicitly adding it back when reading in signature files.
+
+This only applies to the java.lang package, not any subpackages, so for example
+java.lang.reflect.Method will **not** be shortened to reflect.Method.
+
+### Type Use Annotations
+
+In v3, "type use annotations" are supported which means annotations can appear
+within types.
+
+### Miscellaneous
+
+Some other minor tweaks in v2:
+
+*   Fix formatting for package private elements. These had two spaces of
+    indentation; this is probably just a bug. The new format aligns their
+    indentation with all other elements.
+*   Don't add spaces in type bounds lists (e.g. Map<X,Y>, not Map<X, Y>.)
+
+## Historical API Files
+
+Metalava can read and write these formats. To switch output formats, invoke it
+with for example --format=v2.
+
+The Android source tree also has checked in versions of the signatures for all
+the previous API levels. Metalava can regenerate these for a new format.
+For example, to update all the signature files to v3, run this command:
+
+```
+$ metalava --write-android-jar-signatures *<android source dir>* --format=v3
+```
diff --git a/README.md b/README.md
index c11db83..608e7a9 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,24 @@
 # Metalava
 
-(Also known as "doclava2", but deliberately not named doclava2 since crucially it
-does not generate docs; it's intended only for **meta**data extraction and generation.)
+(Also known as "doclava2", but deliberately not named doclava2 since crucially
+it does not generate docs; it's intended only for **meta**data extraction and
+generation.)
 
 Metalava is a metadata generator intended for the Android source tree, used for
 a number of purposes:
 
-* Allow extracting the API (into signature text files, into stub API files (which
-  in turn get compiled into android.jar, the Android SDK library)
-  and more importantly to hide code intended to be implementation only, driven
-  by javadoc comments like @hide, @$doconly, @removed, etc, as well as various
+* Allow extracting the API (into signature text files, into stub API files
+  (which in turn get compiled into android.jar, the Android SDK library) and
+  more importantly to hide code intended to be implementation only, driven by
+  javadoc comments like @hide, @$doconly, @removed, etc, as well as various
   annotations.
 
 * Extracting source level annotations into external annotations file (such as
   the typedef annotations, which cannot be stored in the SDK as .class level
   annotations).
 
-* Diffing versions of the API and determining whether a newer version is compatible
-  with the older version.
+* Diffing versions of the API and determining whether a newer version is
+  compatible with the older version.
 
 ## Building and running
 
@@ -48,15 +49,14 @@
     --verbose                             Include extra diagnostic output
 
     ...
+
 (*output truncated*)
 
 Metalava has a new command line syntax, but it also understands the doclava1
-flags and translates them on the fly. Flags that are ignored are listed on
-the command line. If metalava is dropped into an Android framework build for
+flags and translates them on the fly. Flags that are ignored are listed on the
+command line. If metalava is dropped into an Android framework build for
 example, you'll see something like this (unless running with --quiet) :
 
-    metalava: Ignoring unimplemented doclava1 flag -encoding (UTF-8 assumed)
-    metalava: Ignoring unimplemented doclava1 flag -source  (1.8 assumed)
     metalava: Ignoring javadoc-related doclava1 flag -J-Xmx1600m
     metalava: Ignoring javadoc-related doclava1 flag -J-XX:-OmitStackTraceInFastThrow
     metalava: Ignoring javadoc-related doclava1 flag -XDignore.symbol.file
@@ -71,40 +71,40 @@
 * Compatibility with doclava1: in compat mode, metalava spits out the same
   signature files for the framework as doclava1.
 
-* Ability to read in an existing android.jar file instead of from source, which means
-  we can regenerate signature files etc for older versions according to new formats
-  (e.g. to fix past errors in doclava, such as annotation instance methods which were
-  accidentally not included.)
+* Ability to read in an existing android.jar file instead of from source, which
+  means we can regenerate signature files etc for older versions according to
+  new formats (e.g. to fix past errors in doclava, such as annotation instance
+  methods which were accidentally not included.)
 
 * Ability to merge in data (annotations etc) from external sources, such as
   IntelliJ external annotations data as well as signature files containing
   annotations. This isn't just merged at export time, it's merged at codebase
   load time such that it can be part of the API analysis.
 
-* Support for an updated signature file format:
+* Support for an updated signature file format (which is described in FORMAT.md)
 
-  * Address errors in the doclava1 format which for example was missing annotation
-    class instance methods
+  * Address errors in the doclava1 format which for example was missing
+    annotation class instance methods
 
   * Improve the signature format such that it for example labels enums "enum"
-    instead of "abstract class extends java.lang.Enum", annotations as "@interface"
-    instead of "abstract class extends java.lang.Annotation", sorts modifiers in
-    the canonical modifier order, using "extends" instead of "implements" for
-    the superclass of an interface, and many other similar tweaks outlined
-    in the `Compatibility` class. (Metalava also allows (and ignores) block
-    comments in the signature files.)
+    instead of "abstract class extends java.lang.Enum", annotations as
+    "@interface" instead of "abstract class extends java.lang.Annotation", sorts
+    modifiers in the canonical modifier order, using "extends" instead of
+    "implements" for the superclass of an interface, and many other similar
+    tweaks outlined in the `Compatibility` class. (Metalava also allows (and
+    ignores) block comments in the signature files.)
 
   * Add support for writing (and reading) annotations into the signature
-    files. This is vital now that some of these annotations become part of
-    the API contract (in particular nullness contracts, as well as parameter
-    names and default values.)
+    files. This is vital now that some of these annotations become part of the
+    API contract (in particular nullness contracts, as well as parameter names
+    and default values.)
 
-  * Support for a "compact" nullness format -- one based on Kotlin's syntax. Since
-    the goal is to have **all** API elements explicitly state their nullness
-    contract, the signature files would very quickly become bloated with
-    @NonNull and @Nullable annotations everywhere. So instead, the signature
-    format now uses a suffix of `?` for nullable, `!` for not yet annotated, and
-    nothing for non-null.
+  * Support for a "compact" nullness format -- one based on Kotlin's
+    syntax. Since the goal is to have **all** API elements explicitly state
+    their nullness contract, the signature files would very quickly become
+    bloated with @NonNull and @Nullable annotations everywhere. So instead, the
+    signature format now uses a suffix of `?` for nullable, `!` for not yet
+    annotated, and nothing for non-null.
 
     Instead of
 
@@ -116,10 +116,9 @@
         method public java.lang.Double! convert0(java.lang.Float!);
         method public java.lang.Double? convert1(java.lang.Float);
 
-
-  * Other compactness improvements: Skip packages in some cases both for
-    export and reinsert during import. Specifically, drop "java.lang."
-    from package names such that you have
+  * Other compactness improvements: Skip packages in some cases both for export
+    and reinsert during import. Specifically, drop "java.lang."  from package
+    names such that you have
 
         method public void onUpdate(int, String);
 
@@ -128,35 +127,34 @@
         method public void onUpdate(int, java.lang.String);
 
     Similarly, annotations (the ones considered part of the API; unknown
-    annotations are not included in signature files) use just the simple
-    name instead of the full package name, e.g. `@UiThread` instead of
+    annotations are not included in signature files) use just the simple name
+    instead of the full package name, e.g. `@UiThread` instead of
     `@android.annotation.UiThread`.
 
-  * Misc documentation handling; for example, it attempts to fix sentences
-    that javadoc will mistreat, such as sentences that "end" with "e.g. ".
-    It also looks for various common typos and fixes those; here's a sample
-    error message running metalava on master:
-    Enhancing docs:
+  * Misc documentation handling; for example, it attempts to fix sentences that
+    javadoc will mistreat, such as sentences that "end" with "e.g. ".  It also
+    looks for various common typos and fixes those; here's a sample error
+    message running metalava on master: Enhancing docs:
 
         frameworks/base/core/java/android/content/res/AssetManager.java:166: error: Replaced Kitkat with KitKat in documentation for Method android.content.res.AssetManager.getLocales() [Typo]
         frameworks/base/core/java/android/print/PrinterCapabilitiesInfo.java:122: error: Replaced Kitkat with KitKat in documentation for Method android.print.PrinterCapabilitiesInfo.Builder.setColorModes(int, int) [Typo]
 
 * Built-in support for injecting new annotations for use by the Kotlin compiler,
   not just nullness annotations found in the source code and annotations merged
-  in from external sources, but also inferring whether nullness annotations
-  have recently changed and if so marking them as @Migrate (which lets the
-  Kotlin compiler treat errors in the user code as warnings instead of errors.)
+  in from external sources, but also inferring whether nullness annotations have
+  recently changed and if so marking them as @Migrate (which lets the Kotlin
+  compiler treat errors in the user code as warnings instead of errors.)
 
-* Support for generating documentation into the stubs files (so we can run javadoc or
-  [Dokka](https://github.com/Kotlin/dokka) on the stubs files instead of the source
-  code). This means that the documentation tool itself does not need to be able to
-  figure out which parts of the source code is included in the API and which one is
-  implementation; it is simply handed the filtered API stub sources that include
-  documentation.
+* Support for generating documentation into the stubs files (so we can run
+  javadoc or [Dokka](https://github.com/Kotlin/dokka) on the stubs files instead
+  of the source code). This means that the documentation tool itself does not
+  need to be able to figure out which parts of the source code is included in
+  the API and which one is implementation; it is simply handed the filtered API
+  stub sources that include documentation.
 
 * Support for parsing Kotlin files. API files can now be implemented in Kotlin
-  as well and metalava will parse and extract API information from them just
-  as is done for Java files.
+  as well and metalava will parse and extract API information from them just as
+  is done for Java files.
 
 * Like doclava1, metalava can diff two APIs and warn about API compatibility
   problems such as removing API elements. Metalava adds new warnings around
@@ -165,15 +163,15 @@
   but not versa).  It also lets you diff directly on a source tree; it does not
   require you to create two signature files to diff.
 
-* Consistent stubs: In doclava1, the code which iterated over the API and generated
-  the signature files and generated the stubs had diverged, so there was some
-  inconsistency. In metalava the stub files contain **exactly** the same signatures
-  as in the signature files.
+* Consistent stubs: In doclava1, the code which iterated over the API and
+  generated the signature files and generated the stubs had diverged, so there
+  was some inconsistency. In metalava the stub files contain **exactly** the
+  same signatures as in the signature files.
 
   (This turned out to be incredibly important; this revealed for example that
-  StringBuilder.setLength(int) was missing from the API signatures since it is
-  a public method inherited from a package protected super class, which the
-  API extraction code in doclava1 missed, but accidentally included in the SDK
+  StringBuilder.setLength(int) was missing from the API signatures since it is a
+  public method inherited from a package protected super class, which the API
+  extraction code in doclava1 missed, but accidentally included in the SDK
   anyway since it packages package private classes. Metalava strictly applies
   the exact same API as is listed in the signature files, and once this was
   hooked up to the build it immediately became apparent that it was missing
@@ -189,19 +187,19 @@
         2770 out of 47492 parameters were annotated (5%)
 
   More importantly, you can also point it to some existing compiled applications
-  (.class or .jar files) and it will then measure the annotation coverage of
-  the APIs used by those applications. This lets us target the most important
-  APIs that are currently used by a corpus of apps and target our annotation
-  efforts in a targeted way. For example, running the analysis on the current
-  version of framework, and pointing it to the
+  (.class or .jar files) and it will then measure the annotation coverage of the
+  APIs used by those applications. This lets us target the most important APIs
+  that are currently used by a corpus of apps and target our annotation efforts
+  in a targeted way. For example, running the analysis on the current version of
+  framework, and pointing it to the
   [Plaid](https://github.com/nickbutcher/plaid) app's compiled output with
 
       ... --annotation-coverage-of ~/plaid/app/build/intermediates/classes/debug
 
   This produces the following output:
 
-    324 methods and fields were missing nullness annotations out of 650 total API references.
-    API nullness coverage is 50%
+    324 methods and fields were missing nullness annotations out of 650 total
+    API references.  API nullness coverage is 50%
 
     ```
     | Qualified Class Name                                         |      Usage Count |
@@ -224,7 +222,8 @@
     | android.view.ViewGroup.MarginLayoutParams                    |               21 |
     | ... (99 more items                                           |                  |
     ```
-    Top referenced un-annotated members:
+
+Top referenced un-annotated members:
 
     ```
     | Member                                                       |      Usage Count |
@@ -248,30 +247,33 @@
     | ... (309 more items                                          |                  |
     ```
 
-  From this it's clear that it would be useful to start annotating android.os.Parcel
-  and android.view.View for example where there are unannotated APIs that are
-  frequently used, at least by this app.
+  From this it's clear that it would be useful to start annotating
+  android.os.Parcel and android.view.View for example where there are
+  unannotated APIs that are frequently used, at least by this app.
 
-* Built on top of a full, type-resolved AST. Doclava1 was integrated with javadoc,
-  which meant that most of the source tree was opaque. Therefore, as just one example,
-  the code which generated documentation for typedef constants had to require the
-  constants to all share a single prefix it could look for. However, in metalava,
-  annotation references are available at the AST level, so it can resolve references
-  and map them back to the original field references and include those directly.
+* Built on top of a full, type-resolved AST. Doclava1 was integrated with
+  javadoc, which meant that most of the source tree was opaque. Therefore, as
+  just one example, the code which generated documentation for typedef constants
+  had to require the constants to all share a single prefix it could look
+  for. However, in metalava, annotation references are available at the AST
+  level, so it can resolve references and map them back to the original field
+  references and include those directly.
 
-* Support for extracting annotations. Metalava can also generate the external annotation
-  files needed by Studio and lint in Gradle, which captures the typedefs (@IntDef and
-  @StringDef classes) in the source code. Prior to this this was generated manually
-  via the development/tools/extract code. This also merges in manually curated data;
-  some of this is in the manual/ folder in this project.
+* Support for extracting annotations. Metalava can also generate the external
+  annotation files needed by Studio and lint in Gradle, which captures the
+  typedefs (@IntDef and @StringDef classes) in the source code. Prior to this
+  this was generated manually via the development/tools/extract code. This also
+  merges in manually curated data; some of this is in the manual/ folder in this
+  project.
 
-* Support for extracting API levels (api-versions.xml). This was generated by separate
-  code (tools/base/misc/api-generator), invoked during the build. This functionality
-  is now rolled into metalava, which has one very important attribute: metalava
-  will use this information when recording API levels for API usage. (Prior to this,
-  this was based on signature file parsing in doclava, which sometimes generated
-  incorrect results. Metalava uses the android.jar files themselves to ensure that
-  it computes the exact available SDK data for each API level.)
+* Support for extracting API levels (api-versions.xml). This was generated by
+  separate code (tools/base/misc/api-generator), invoked during the build. This
+  functionality is now rolled into metalava, which has one very important
+  attribute: metalava will use this information when recording API levels for
+  API usage. (Prior to this, this was based on signature file parsing in
+  doclava, which sometimes generated incorrect results. Metalava uses the
+  android.jar files themselves to ensure that it computes the exact available
+  SDK data for each API level.)
 
 ## Architecture & Implementation
 
@@ -282,33 +284,33 @@
 
 This is done for multiple reasons:
 
-(1) It allows us to have multiple "back-ends": for example, metalava can read
-    in a model not just from parsing source code, but from reading older SDK
+(1) It allows us to have multiple "back-ends": for example, metalava can read in
+    a model not just from parsing source code, but from reading older SDK
     android.jar files (e.g. backed by bytecode) or reading previous signature
-    files.  Reading in multiple versions of an API lets doclava perform "diffing",
-    such as warning if an API is changing in an incompatible way. It can also
-    generate signature files in the new format (including data that was missing
-    in older signature files, such as annotation methods) without having to
-    parse older source code which may no longer be easy to parse.
+    files.  Reading in multiple versions of an API lets doclava perform
+    "diffing", such as warning if an API is changing in an incompatible way. It
+    can also generate signature files in the new format (including data that was
+    missing in older signature files, such as annotation methods) without having
+    to parse older source code which may no longer be easy to parse.
 
 (2) There's a lot of logic for deciding whether code found in the source tree
     should be included in the API. With the model approach we can build up an
-    API and for example mark a subset of its methods as included. By having
-    a separate hierarchy we can easily perform this work once and pass around
-    our filtered model instead of passing around PsiClass and PsiMethod instances
+    API and for example mark a subset of its methods as included. By having a
+    separate hierarchy we can easily perform this work once and pass around our
+    filtered model instead of passing around PsiClass and PsiMethod instances
     and having to keep the filtered data separately and remembering to always
     consult the filter, not the PSI elements directly.
 
-The basic API element class is "Item". (In doclava1 this was called a "DocInfo".)
-There are several sub interfaces of Item: PackageItem, ClassItem, MemberItem,
-MethodItem, FieldItem, ParameterItem, etc. And then there are several
-implementation hierarchies: One is PSI based, where you point metalava to a
-source tree or a .jar file, and it constructs Items built on top of PSI:
-PsiPackageItem, PsiClassItem, PsiMethodItem, etc. Another is textual, based
-on signature files: TextPackageItem, TextClassItem, and so on.
+The basic API element class is "Item". (In doclava1 this was called a
+"DocInfo".)  There are several sub interfaces of Item: PackageItem, ClassItem,
+MemberItem, MethodItem, FieldItem, ParameterItem, etc. And then there are
+several implementation hierarchies: One is PSI based, where you point metalava
+to a source tree or a .jar file, and it constructs Items built on top of PSI:
+PsiPackageItem, PsiClassItem, PsiMethodItem, etc. Another is textual, based on
+signature files: TextPackageItem, TextClassItem, and so on.
 
-The "Codebase" class captures a complete API snapshot (including classes
-that are hidden, which is why it's called a "Codebase" rather than an "API").
+The "Codebase" class captures a complete API snapshot (including classes that
+are hidden, which is why it's called a "Codebase" rather than an "API").
 
 There are methods to load codebases - from source folders, from a .jar file,
 from a signature file. That's how API diffing is performed: you load two
@@ -329,17 +331,16 @@
     })
 
 Similarly you can visit all items (regardless of type) by overriding
-`visitItem`, or to specifically visit methods, fields and so on
-overriding `visitPackage`, `visitClass`, `visitMethod`, etc.
+`visitItem`, or to specifically visit methods, fields and so on overriding
+`visitPackage`, `visitClass`, `visitMethod`, etc.
 
-There is also an `ApiVisitor`. This is a subclass of the `ItemVisitor`,
-but which limits itself to visiting code elements that are part of the
-API.
+There is also an `ApiVisitor`. This is a subclass of the `ItemVisitor`, but
+which limits itself to visiting code elements that are part of the API.
 
 This is how for example the SignatureWriter and the StubWriter are both
-implemented: they simply extend `ApiVisitor`, which means they'll
-only export the API items in the codebase, and then in each relevant
-method they emit the signature or stub data:
+implemented: they simply extend `ApiVisitor`, which means they'll only export
+the API items in the codebase, and then in each relevant method they emit the
+signature or stub data:
 
     class SignatureWriter(
             private val writer: PrintWriter,
@@ -362,12 +363,12 @@
 
 ### Visiting Types
 
-There is a `TypeVisitor` similar to `ItemVisitor` which you can use
-to visit all types in the codebase.
+There is a `TypeVisitor` similar to `ItemVisitor` which you can use to visit all
+types in the codebase.
 
 When computing the API, all types that are included in the API should be
-included (e.g. if `List<Foo>` is part of the API then `Foo` must be too).
-This is easy to do with the `TypeVisitor`.
+included (e.g. if `List<Foo>` is part of the API then `Foo` must be too).  This
+is easy to do with the `TypeVisitor`.
 
 ### Diffing Codebases
 
@@ -399,11 +400,11 @@
 
 This makes it easy to perform API comparison operations.
 
-For example, metalava has a feature to mark "newly annotated" nullness annotations
-as migrated. To do this, it just extends `ComparisonVisitor`, overrides the
-`compare(old: Item, new: Item)` method, and checks whether the old item
-has no nullness annotations and the new one does, and if so, also marks
-the new annotations as @Migrate.
+For example, metalava has a feature to mark "newly annotated" nullness
+annotations as migrated. To do this, it just extends `ComparisonVisitor`,
+overrides the `compare(old: Item, new: Item)` method, and checks whether the old
+item has no nullness annotations and the new one does, and if so, also marks the
+new annotations as @Migrate.
 
 Similarly, the API Check can simply override
 
@@ -419,31 +420,20 @@
 ### Documentation Generation
 
 As mentioned above, metalava generates documentation directly into the stubs
-files, which can then be processed by Dokka and Javadoc to generate the
-same docs as before.
+files, which can then be processed by Dokka and Javadoc to generate the same
+docs as before.
 
-Doclava1 was integrated with javadoc directly, so the way it generated
-metadata docs (such as documenting permissions, ranges and typedefs from
-annotations) was to insert auxiliary tags (`@range`, `@permission`, etc) and
-then this would get converted into English docs later via `macros_override.cs`.
+Doclava1 was integrated with javadoc directly, so the way it generated metadata
+docs (such as documenting permissions, ranges and typedefs from annotations) was
+to insert auxiliary tags (`@range`, `@permission`, etc) and then this would get
+converted into English docs later via `macros_override.cs`.
 
 This it not how metalava does it; it generates the English documentation
 directly. This was not just convenient for the implementation (since metalava
-does not use javadoc data structures to pass maps like the arguments for
-the typedef macro), but should also help Dokka -- and arguably the Kotlin
-code which generates the documentation is easier to reason about and to
-update when it's handling loop conditionals. (As a result I for example
-improved some of the grammar, e.g. when it's listing a number of possible
-constants the conjunction is usually "or", but if it's a flag, the sentence
-begins with "a combination of " and then the conjunction at the end should
-be "and").
-
-## Current Status
-
-Some things are still missing before this tool can be integrated:
-
-- there are some remaining bugs around type resolution in Kotlin and
-  reified methods (also in Kotlin) are not included
-
-- the code needs cleanup, and some performance optimizations (it's about 3x
-  slower than doclava1)
+does not use javadoc data structures to pass maps like the arguments for the
+typedef macro), but should also help Dokka -- and arguably the Kotlin code which
+generates the documentation is easier to reason about and to update when it's
+handling loop conditionals. (As a result I for example improved some of the
+grammar, e.g. when it's listing a number of possible constants the conjunction
+is usually "or", but if it's a flag, the sentence begins with "a combination of
+" and then the conjunction at the end should be "and").
diff --git a/build.gradle b/build.gradle
index 4456034..916149b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
 buildscript {
-    ext.gradle_version = '3.2.0-alpha16'
-    ext.studio_version = '26.2.0-alpha16'
-    ext.kotlin_version = '1.2.41'
+    ext.gradle_version = '3.2.1'
+    ext.studio_version = '26.2.1'
+    ext.kotlin_version = '1.3.10'
     repositories {
         google()
         jcenter()
@@ -25,9 +25,9 @@
 group = 'com.android'
 def versionPropertyFile = file('src/main/resources/version.properties')
 if (versionPropertyFile.canRead()) {
-   Properties versionProps = new Properties()
-   versionProps.load(new FileInputStream(versionPropertyFile))
-   version = versionProps['metalavaVersion']
+    Properties versionProps = new Properties()
+    versionProps.load(new FileInputStream(versionPropertyFile))
+    version = versionProps['metalavaVersion']
 } else {
     throw new FileNotFoundException("Could not read $versionPropertyFile")
 }
@@ -42,8 +42,8 @@
 
     kotlinOptions {
         jvmTarget = "1.8"
-        apiVersion = "1.2"
-        languageVersion = "1.2"
+        apiVersion = "1.3"
+        languageVersion = "1.3"
     }
 }
 
@@ -55,6 +55,7 @@
     implementation "com.android.tools.lint:lint-gradle:$studio_version"
     implementation "com.android.tools.lint:lint:$studio_version"
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+    implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
     testImplementation "com.android.tools.lint:lint-tests:$studio_version"
     testImplementation 'junit:junit:4.11'
 }
@@ -70,10 +71,10 @@
 }
 apply plugin: 'com.github.johnrengelman.shadow'
 shadowJar {
-   baseName = "metalava-$version-full-SNAPSHOT"
-   classifier = null
-   version = null
-   zip64 = true
+    baseName = "metalava-$version-full-SNAPSHOT"
+    classifier = null
+    version = null
+    zip64 = true
 }
 
 defaultTasks 'clean', 'installDist'
@@ -108,7 +109,7 @@
 }
 
 dependencies {
-    ktlint "com.github.shyiko:ktlint:0.23.1"
+    ktlint "com.github.shyiko:ktlint:0.29.0"
 }
 
 task ktlint(type: JavaExec, group: "verification") {
diff --git a/gradle.properties b/gradle.properties
index 8998b2f..e1841e4 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,4 @@
+# suppress inspection "UnusedProperty" for whole file
 org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m
 org.gradle.daemon=true
 kotlin.incremental.usePreciseJavaTracking=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index efc27cb..7e98c33 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
 #Sat Jan 13 09:12:34 PST 2018
+# suppress inspection "UnusedProperty" for whole file
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-milestone-1-all.zip
diff --git a/manual/master.txt b/manual/master.txt
index b239c51..3b0c5aa 100644
--- a/manual/master.txt
+++ b/manual/master.txt
@@ -67,54 +67,14 @@
   }
 }
 package java.lang {
-  public final class Character implements java.lang.Comparable<java.lang.Character> java.io.Serializable {
-    method @NonNull public static char @NonNull [] toChars(int);
-  }
+  // These are all in //libcore/ojluni/annotations. They are
+  // reproduced here to work around an issue in metalava (b/111116803).
   public final class Class<T> implements java.lang.reflect.AnnotatedElement java.lang.reflect.GenericDeclaration java.io.Serializable java.lang.reflect.Type {
-    method @Nullable public T cast(@Nullable Object);
-    method @NonNull public static Class<?> forName(@NonNull String) throws java.lang.ClassNotFoundException;
-    method @NonNull public static Class<?> forName(@NonNull String, boolean, @Nullable ClassLoader) throws java.lang.ClassNotFoundException;
     method @Nullable public <A extends java.lang.annotation.Annotation> A getAnnotation(@NonNull Class<A>);
-    method @NonNull public java.lang.annotation.Annotation @NonNull [] getAnnotations();
     method @NonNull public <A extends java.lang.annotation.Annotation> A @NonNull [] getAnnotationsByType(@NonNull Class<A>);
-    method @Nullable public String getCanonicalName();
-    method @Nullable public ClassLoader getClassLoader();
-    method @NonNull public Class<?> @NonNull [] getClasses();
-    method @Nullable public Class<?> getComponentType();
-    method @NonNull public java.lang.reflect.Constructor<?> @NonNull [] getConstructors() throws java.lang.SecurityException;
     method @Nullable public native <A extends java.lang.annotation.Annotation> A getDeclaredAnnotation(@NonNull Class<A>);
-    method @NonNull public native java.lang.annotation.Annotation @NonNull [] getDeclaredAnnotations();
-    method @NonNull public native Class<?> @NonNull [] getDeclaredClasses();
-    method @NonNull public java.lang.reflect.Constructor<?> @NonNull [] getDeclaredConstructors() throws java.lang.SecurityException;
-    method @NonNull public native java.lang.reflect.Field getDeclaredField(@NonNull String) throws java.lang.NoSuchFieldException;
-    method @NonNull public native java.lang.reflect.Field @NonNull [] getDeclaredFields();
-    method @NonNull public java.lang.reflect.Method @NonNull [] getDeclaredMethods() throws java.lang.SecurityException;
-    method @Nullable public native Class<?> getDeclaringClass();
-    method @Nullable public native Class<?> getEnclosingClass();
-    method @Nullable public java.lang.reflect.Constructor<?> getEnclosingConstructor();
-    method @Nullable public java.lang.reflect.Method getEnclosingMethod();
-    method @NonNull public T @Nullable [] getEnumConstants();
-    method @NonNull public java.lang.reflect.Field getField(@NonNull String) throws java.lang.NoSuchFieldException;
-    method @NonNull public java.lang.reflect.Field @NonNull [] getFields() throws java.lang.SecurityException;
-    method @NonNull public java.lang.reflect.Type @NonNull [] getGenericInterfaces();
-    method @Nullable public java.lang.reflect.Type getGenericSuperclass();
-    method @NonNull public Class<?> @NonNull [] getInterfaces();
-    method @NonNull public java.lang.reflect.Method @NonNull [] getMethods() throws java.lang.SecurityException;
-    method @NonNull public String getName();
-    method @Nullable public Package getPackage();
     method @Nullable public java.security.ProtectionDomain getProtectionDomain();
     method @Nullable public java.net.URL getResource(@NonNull String);
-    method @Nullable public java.io.InputStream getResourceAsStream(@NonNull String);
-    method @NonNull public Object @Nullable [] getSigners();
-    method @NonNull public String getSimpleName();
-    method @Nullable public Class<? super T> getSuperclass();
-    method @NonNull public synchronized java.lang.reflect.TypeVariable<java.lang.@NonNull Class<T>> @NonNull [] getTypeParameters();
-    method @NonNull public native T newInstance() throws java.lang.IllegalAccessException, java.lang.InstantiationException;
-    method @NonNull public String toGenericString();
-  }
-  public class Object {
-    method @NonNull public final Class<?> getClass();
-    method @NonNull public String toString();
   }
   public final class System {
     method @NonNull public static java.util.Map<java.lang.String,java.lang.String> getenv();
@@ -123,20 +83,3 @@
     method @NonNull public static <S> ThreadLocal<S> withInitial(@NonNull java.util.function.Supplier<? extends S>);
   }
 }
-package java.util {
-  public class ArrayList<E> extends java.util.AbstractList<E> implements java.lang.Cloneable java.util.List<E> java.util.RandomAccess java.io.Serializable {
-    method @NonNull public Object clone();
-  }
-  public class HashMap<K, V> extends java.util.AbstractMap<K,V> implements java.lang.Cloneable java.util.Map<K,V> java.io.Serializable {
-    method @NonNull public Object clone();
-    method @NonNull public java.util.Set<java.util.Map.Entry<K,V>> entrySet();
-  }
-  public interface Map<K, V> {
-    method @Nullable public V get(@Nullable Object);
-    method @Nullable public default V getOrDefault(@Nullable Object, @Nullable V);
-    method @Nullable public V put(K, V);
-    method @Nullable public default V putIfAbsent(K, V);
-    method @Nullable public V remove(@Nullable Object);
-    method @Nullable public default V replace(K, V);
-  }
-}
diff --git a/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt b/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt
index a6b79c5..afe068e 100644
--- a/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt
+++ b/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt
@@ -31,6 +31,7 @@
         className = getClassName(withoutComments)
     }
 
+    @Suppress("unused")
     fun packageNameWithDefault() = packageName ?: ""
 }
 
diff --git a/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt b/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt
index 78ad9c7..62fbcb2 100644
--- a/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt
+++ b/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt
@@ -33,10 +33,9 @@
 class AndroidApiChecks {
     fun check(codebase: Codebase) {
         codebase.accept(object : ApiVisitor(
-            codebase,
             // Sort by source order such that warnings follow source line number order
-            fieldComparator = FieldItem.comparator,
-            methodComparator = MethodItem.sourceOrderComparator
+            methodComparator = MethodItem.sourceOrderComparator,
+            fieldComparator = FieldItem.comparator
         ) {
             override fun skip(item: Item): Boolean {
                 // Limit the checks to the android.* namespace (except for ICU)
diff --git a/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt b/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt
index c229bca..98c332d 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt
@@ -29,7 +29,6 @@
 import com.android.tools.metalava.model.visitors.ApiVisitor
 import com.google.common.io.ByteStreams
 import org.objectweb.asm.ClassReader
-import org.objectweb.asm.Type
 import org.objectweb.asm.tree.AbstractInsnNode
 import org.objectweb.asm.tree.ClassNode
 import org.objectweb.asm.tree.FieldInsnNode
@@ -50,18 +49,29 @@
 const val INCLUDE_HORIZONTAL_EDGES = false
 
 class AnnotationStatistics(val api: Codebase) {
-    private val apiFilter = ApiPredicate(api)
+    private val apiFilter = ApiPredicate()
 
     /** Measure the coverage statistics for the API */
     fun count() {
+        // Count of all methods, fields and parameters
         var allMethods = 0
-        var annotatedMethods = 0
         var allFields = 0
-        var annotatedFields = 0
         var allParameters = 0
+
+        // Count of all methods, fields and parameters that requires annotations
+        // (e.g. not primitives, not constructors, etc)
+        var eligibleMethods = 0
+        var eligibleMethodReturns = 0
+        var eligibleFields = 0
+        var eligibleParameters = 0
+
+        // Count of methods, fields and parameters that were actually annotated
+        var annotatedMethods = 0
+        var annotatedMethodReturns = 0
+        var annotatedFields = 0
         var annotatedParameters = 0
 
-        api.accept(object : ApiVisitor(api) {
+        api.accept(object : ApiVisitor() {
             override fun skip(item: Item): Boolean {
                 if (options.omitRuntimePackageStats && item is PackageItem) {
                     val name = item.qualifiedName()
@@ -77,32 +87,43 @@
             }
 
             override fun visitParameter(parameter: ParameterItem) {
+                allParameters++
                 if (!parameter.requiresNullnessInfo()) {
                     return
                 }
-                allParameters++
-                if (parameter.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) {
+                eligibleParameters++
+                if (parameter.hasNullnessInfo()) {
                     annotatedParameters++
                 }
             }
 
             override fun visitField(field: FieldItem) {
+                allFields++
                 if (!field.requiresNullnessInfo()) {
                     return
                 }
-                allFields++
-                if (field.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) {
+                eligibleFields++
+                if (field.hasNullnessInfo()) {
                     annotatedFields++
                 }
             }
 
             override fun visitMethod(method: MethodItem) {
-                if (!method.requiresNullnessInfo()) {
-                    return
-                }
                 allMethods++
-                if (method.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) {
-                    annotatedMethods++
+                if (method.requiresNullnessInfo()) { // No, this includes parameter requirements
+                    eligibleMethods++
+                    if (method.hasNullnessInfo()) {
+                        annotatedMethods++
+                    }
+                }
+
+                // method.requiresNullnessInfo also checks parameters; here we want to consider
+                // only the method modifier list
+                if (!method.isConstructor() && method.returnType()?.primitive != true) {
+                    eligibleMethodReturns++
+                    if (method.modifiers.hasNullnessInfo()) {
+                        annotatedMethodReturns++
+                    }
                 }
             }
         })
@@ -111,11 +132,21 @@
         options.stdout.println(
             """
             Nullness Annotation Coverage Statistics:
-            $annotatedMethods out of $allMethods methods were annotated (${percent(annotatedMethods, allMethods)}%)
-            $annotatedFields out of $allFields fields were annotated (${percent(annotatedFields, allFields)}%)
-            $annotatedParameters out of $allParameters parameters were annotated (${percent(
+            $annotatedFields out of $eligibleFields eligible fields (out of $allFields total fields) were annotated (${percent(
+                annotatedFields,
+                eligibleFields
+            )}%)
+            $annotatedMethods out of $eligibleMethods eligible methods (out of $allMethods total methods) were fully annotated (${percent(
+                annotatedMethods,
+                eligibleMethods
+            )}%)
+                $annotatedMethodReturns out of $eligibleMethodReturns eligible method returns were annotated (${percent(
+                annotatedMethodReturns,
+                eligibleMethodReturns
+            )}%)
+                $annotatedParameters out of $eligibleParameters eligible parameters were annotated (${percent(
                 annotatedParameters,
-                allParameters
+                eligibleParameters
             )}%)
             """.trimIndent()
         )
@@ -251,12 +282,14 @@
         // Top APIs
         printer.println("\nTop referenced un-annotated classes:\n")
 
-        printTable("Qualified Class Name",
+        printTable(
+            "Qualified Class Name",
             "Usage Count",
             classes,
             { (it as ClassItem).qualifiedName() },
             { classCount[it]!! },
-            printer)
+            printer
+        )
 
         if (reportFile != null) {
             printer.close()
@@ -286,8 +319,8 @@
             sorted,
             {
                 val member = it as MemberItem
-                "${member.containingClass().simpleName()}.${member.name()}${if (member is MethodItem) "(${member.parameters().joinToString {
-                    it.type().toSimpleType()
+                "${member.containingClass().simpleName()}.${member.name()}${if (member is MethodItem) "(${member.parameters().joinToString { parameter ->
+                    parameter.type().toSimpleType()
                 }})" else ""}"
             },
             { used[it]!! },
@@ -406,7 +439,7 @@
                     if (skipJava && isSkippableOwner(call.owner)) {
                         continue
                     }
-                    val item = findMethod(call)
+                    val item = api.findMethod(call, apiFilter)
                     item?.let {
                         val count = used[it]
                         if (count == null) {
@@ -420,7 +453,7 @@
                     if (skipJava && isSkippableOwner(field.owner)) {
                         continue
                     }
-                    val item = findField(field)
+                    val item = api.findField(field, apiFilter)
                     item?.let {
                         val count = used[it]
                         if (count == null) {
@@ -439,48 +472,4 @@
             owner.startsWith("javax/") ||
             owner.startsWith("kotlin") ||
             owner.startsWith("kotlinx/")
-
-    private fun findField(node: FieldInsnNode): FieldItem? {
-        val cls = findClass(node.owner) ?: return null
-        val field = cls.findField(node.name)
-        return if (field != null && apiFilter.test(field)) {
-            field
-        } else {
-            null
-        }
-    }
-
-    private fun findClass(owner: String): ClassItem? {
-        val className = owner.replace('/', '.').replace('$', '.')
-        val cls = api.findClass(className)
-        return if (cls != null && apiFilter.test(cls)) {
-            cls
-        } else {
-            null
-        }
-    }
-
-    private fun findMethod(node: MethodInsnNode): MethodItem? {
-        val cls = findClass(node.owner) ?: return null
-        val types = Type.getArgumentTypes(node.desc)
-        val parameters = if (types.isNotEmpty()) {
-            val sb = StringBuilder()
-            for (type in types) {
-                if (!sb.isEmpty()) {
-                    sb.append(", ")
-                }
-                sb.append(type.className.replace('/', '.').replace('$', '.'))
-            }
-            sb.toString()
-        } else {
-            ""
-        }
-        val methodName = if (node.name == "<init>") cls.simpleName() else node.name
-        val method = cls.findMethod(methodName, parameters)
-        return if (method != null && apiFilter.test(method)) {
-            method
-        } else {
-            null
-        }
-    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt b/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt
index 0bd7d00..d9a276d 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt
@@ -19,7 +19,6 @@
 import com.android.tools.metalava.doclava1.ApiPredicate
 import com.android.tools.metalava.doclava1.Errors
 import com.android.tools.metalava.doclava1.FilterPredicate
-import com.android.tools.metalava.doclava1.TextCodebase
 import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.Item
 import java.io.File
@@ -59,7 +58,7 @@
  */
 class AnnotationsDiffer(
     private val superset: Codebase,
-    private val codebase: Codebase
+    codebase: Codebase
 ) {
     private val relevant = HashSet<Item>(1000)
 
@@ -103,7 +102,7 @@
         }
         val filter =
             if (codebase.supportsDocumentation()) {
-                ApiPredicate(codebase)
+                ApiPredicate()
             } else {
                 Predicate<Item> { true }
             }
@@ -112,8 +111,8 @@
 
     fun writeDiffSignature(apiFile: File) {
         val codebase = superset
-        val apiFilter = FilterPredicate(ApiPredicate(codebase))
-        val apiReference = ApiPredicate(codebase, ignoreShown = true)
+        val apiFilter = FilterPredicate(ApiPredicate())
+        val apiReference = ApiPredicate(ignoreShown = true)
         val apiEmit = apiFilter.and(predicate)
 
         progress("\nWriting annotation diff file: ")
@@ -121,8 +120,7 @@
             val stringWriter = StringWriter()
             val writer = PrintWriter(stringWriter)
             writer.use { printWriter ->
-                val preFiltered = codebase.original != null || codebase is TextCodebase
-                val apiWriter = SignatureWriter(printWriter, apiEmit, apiReference, preFiltered)
+                val apiWriter = SignatureWriter(printWriter, apiEmit, apiReference, codebase.preFiltered)
                 codebase.accept(apiWriter)
             }
 
diff --git a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
index c32c7df..b709614 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
@@ -53,14 +53,14 @@
 import com.android.tools.metalava.model.AnnotationItem
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.DefaultAnnotationItem
 import com.android.tools.metalava.model.DefaultAnnotationValue
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.MethodItem
-import com.android.tools.metalava.model.PackageItem
-import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
+import com.android.tools.metalava.model.parseDocument
 import com.android.tools.metalava.model.psi.PsiAnnotationItem
+import com.android.tools.metalava.model.psi.PsiBasedCodebase
 import com.android.tools.metalava.model.visitors.ApiVisitor
-import com.android.utils.XmlUtils
 import com.google.common.base.Charsets
 import com.google.common.io.ByteStreams
 import com.google.common.io.Closeables
@@ -80,46 +80,92 @@
 class AnnotationsMerger(
     private val codebase: Codebase
 ) {
-    fun merge(mergeAnnotations: List<File>) {
-        mergeAnnotations.forEach { mergeExisting(it) }
+
+    /** Merge annotations which will appear in the output API. */
+    fun mergeQualifierAnnotations(files: List<File>) {
+        mergeAll(
+            files,
+            ::mergeQualifierAnnotationsFromFile,
+            ::mergeAndValidateQualifierAnnotationsFromJavaStubsCodebase
+        )
     }
 
-    private fun mergeExisting(file: File) {
+    /** Merge annotations which control what is included in the output API. */
+    fun mergeInclusionAnnotations(files: List<File>) {
+        mergeAll(
+            files,
+            {
+                throw DriverException(
+                    "External inclusion annotations files must be .java, found ${it.path}"
+                )
+            },
+            ::mergeInclusionAnnotationsFromCodebase
+        )
+    }
+
+    private fun mergeAll(
+        mergeAnnotations: List<File>,
+        mergeFile: (File) -> Unit,
+        mergeJavaStubsCodebase: (PsiBasedCodebase) -> Unit
+    ) {
+        val javaStubFiles = mutableListOf<File>()
+        mergeAnnotations.forEach { it ->
+            mergeFileOrDir(it, mergeFile, javaStubFiles)
+        }
+        if (javaStubFiles.isNotEmpty()) {
+            // TODO: We really want to fail, or at least issue a warning, if there are errors.
+            val javaStubsCodebase = parseSources(javaStubFiles, "Codebase loaded from stubs")
+            mergeJavaStubsCodebase(javaStubsCodebase)
+        }
+    }
+
+    /**
+     * Merges annotations from `file`, or from all the files under it if `file` is a directory.
+     * All files apart from Java stub files are merged using [mergeFile]. Java stub files are not
+     * merged by this method, instead they are added to [javaStubFiles] and should be merged later
+     * (so that all the Java stubs can be loaded as a single codebase).
+     */
+    private fun mergeFileOrDir(
+        file: File,
+        mergeFile: (File) -> Unit,
+        javaStubFiles: MutableList<File>
+    ) {
         if (file.isDirectory) {
             val files = file.listFiles()
             if (files != null) {
                 for (child in files) {
-                    mergeExisting(child)
+                    mergeFileOrDir(child, mergeFile, javaStubFiles)
                 }
             }
         } else if (file.isFile) {
-            if (file.path.endsWith(DOT_JAR) || file.path.endsWith(DOT_ZIP)) {
-                mergeFromJar(file)
-            } else if (file.path.endsWith(DOT_XML)) {
-                try {
-                    val xml = Files.asCharSource(file, Charsets.UTF_8).read()
-                    mergeAnnotationsXml(file.path, xml)
-                } catch (e: IOException) {
-                    error("Aborting: I/O problem during transform: " + e.toString())
-                }
-            } else if (file.path.endsWith(".jaif")) {
-                try {
-                    val jaif = Files.asCharSource(file, Charsets.UTF_8).read()
-                    mergeAnnotationsJaif(file.path, jaif)
-                } catch (e: IOException) {
-                    error("Aborting: I/O problem during transform: " + e.toString())
-                }
-            } else if (file.path.endsWith(".txt") ||
-                file.path.endsWith(".signatures") ||
-                file.path.endsWith(".api")
-            ) {
-                try {
-                    // .txt: Old style signature files
-                    // Others: new signature files (e.g. kotlin-style nullness info)
-                    mergeAnnotationsSignatureFile(file.path)
-                } catch (e: IOException) {
-                    error("Aborting: I/O problem during transform: " + e.toString())
-                }
+            if (file.path.endsWith(".java")) {
+                javaStubFiles.add(file)
+            } else {
+                mergeFile(file)
+            }
+        }
+    }
+
+    private fun mergeQualifierAnnotationsFromFile(file: File) {
+        if (file.path.endsWith(DOT_JAR) || file.path.endsWith(DOT_ZIP)) {
+            mergeFromJar(file)
+        } else if (file.path.endsWith(DOT_XML)) {
+            try {
+                val xml = Files.asCharSource(file, Charsets.UTF_8).read()
+                mergeAnnotationsXml(file.path, xml)
+            } catch (e: IOException) {
+                error("Aborting: I/O problem during transform: " + e.toString())
+            }
+        } else if (file.path.endsWith(".txt") ||
+            file.path.endsWith(".signatures") ||
+            file.path.endsWith(".api")
+        ) {
+            try {
+                // .txt: Old style signature files
+                // Others: new signature files (e.g. kotlin-style nullness info)
+                mergeAnnotationsSignatureFile(file.path)
+            } catch (e: IOException) {
+                error("Aborting: I/O problem during transform: " + e.toString())
             }
         }
     }
@@ -153,7 +199,7 @@
 
     private fun mergeAnnotationsXml(path: String, xml: String) {
         try {
-            val document = XmlUtils.parseDocument(xml, false)
+            val document = parseDocument(xml, false)
             mergeDocument(document)
         } catch (e: Exception) {
             var message = "Failed to merge " + path + ": " + e.toString()
@@ -175,138 +221,82 @@
             val supportsStagedNullability = true
             val signatureCodebase = ApiFile.parseApi(File(path), kotlinStyleNulls, supportsStagedNullability)
             signatureCodebase.description = "Signature files for annotation merger: loaded from $path"
-            val visitor = object : ComparisonVisitor() {
-                override fun compare(old: Item, new: Item) {
-                    val newModifiers = new.modifiers
-                    for (annotation in old.modifiers.annotations()) {
-                        var addAnnotation = false
-                        if (annotation.isNullnessAnnotation()) {
-                            if (!newModifiers.hasNullnessInfo()) {
-                                addAnnotation = true
-                            }
-                        } else {
-                            // TODO: Check for other incompatibilities than nullness?
-                            val qualifiedName = annotation.qualifiedName() ?: continue
-                            if (newModifiers.findAnnotation(qualifiedName) == null) {
-                                addAnnotation = true
-                            }
-                        }
-
-                        if (addAnnotation) {
-                            // Don't map annotation names - this would turn newly non null back into non null
-                            new.mutableModifiers().addAnnotation(
-                                new.codebase.createAnnotation(
-                                    annotation.toSource(),
-                                    new,
-                                    mapName = false
-                                )
-                            )
-                        }
-                    }
-                }
-            }
-            CodebaseComparator().compare(visitor, signatureCodebase, codebase, ApiPredicate(signatureCodebase))
+            mergeQualifierAnnotationsFromCodebase(signatureCodebase)
         } catch (ex: ApiParseException) {
             val message = "Unable to parse signature file $path: ${ex.message}"
             throw DriverException(message)
         }
     }
 
-    private fun mergeAnnotationsJaif(path: String, jaif: String) {
-        var pkgItem: PackageItem? = null
-        var clsItem: ClassItem? = null
-        var methodItem: MethodItem? = null
-        var curr: Item? = null
-
-        for (rawLine in jaif.split("\n")) {
-            val line = rawLine.trim()
-            if (line.isEmpty()) {
-                continue
-            }
-            if (line.startsWith("//")) {
-                continue
-            }
-            if (line.startsWith("package ")) {
-                val pkg = line.substring("package ".length, line.length - 1)
-                pkgItem = codebase.findPackage(pkg)
-                curr = pkgItem
-            } else if (line.startsWith("class ")) {
-                val cls = line.substring("class ".length, line.length - 1)
-                clsItem = if (pkgItem != null)
-                    codebase.findClass(pkgItem.qualifiedName() + "." + cls)
-                else
-                    null
-                curr = clsItem
-            } else if (line.startsWith("annotation ")) {
-                val cls = line.substring("annotation ".length, line.length - 1)
-                clsItem = if (pkgItem != null)
-                    codebase.findClass(pkgItem.qualifiedName() + "." + cls)
-                else
-                    null
-                curr = clsItem
-            } else if (line.startsWith("method ")) {
-                val method = line.substring("method ".length, line.length - 1)
-                methodItem = null
-                if (clsItem != null) {
-                    val index = method.indexOf('(')
-                    if (index != -1) {
-                        val name = method.substring(0, index)
-                        val desc = method.substring(index)
-                        methodItem = clsItem.findMethodByDesc(name, desc, true, true)
-                    }
-                }
-                curr = methodItem
-            } else if (line.startsWith("field ")) {
-                val field = line.substring("field ".length, line.length - 1)
-                val fieldItem = clsItem?.findField(field, true, true)
-                curr = fieldItem
-            } else if (line.startsWith("parameter #")) {
-                val parameterIndex = line.substring("parameter #".length, line.length - 1).toInt()
-                val parameterItem = if (methodItem != null) {
-                    methodItem.parameters()[parameterIndex]
-                } else {
-                    null
-                }
-                curr = parameterItem
-            } else if (line.startsWith("type: ") && SUPPORT_TYPE_USE_ANNOTATIONS) {
-                val typeAnnotation = line.substring("type: ".length)
-                if (curr != null) {
-                    mergeJaifAnnotation(path, curr, typeAnnotation)
-                }
-            } else if (line.startsWith("return: ")) {
-                val annotation = line.substring("return: ".length)
-                if (methodItem != null) {
-                    mergeJaifAnnotation(path, methodItem, annotation)
-                }
-            } else if (line.startsWith("inner-type") && SUPPORT_TYPE_USE_ANNOTATIONS) {
-                warning("$path: Skipping inner-type annotations for now ($line)")
-            } else if (line.startsWith("int ")) {
-                // warning("Skipping int attribute definitions for annotations now ($line)")
-            }
+    private fun mergeAndValidateQualifierAnnotationsFromJavaStubsCodebase(javaStubsCodebase: PsiBasedCodebase) {
+        mergeQualifierAnnotationsFromCodebase(javaStubsCodebase)
+        if (options.validateNullabilityFromMergedStubs) {
+            options.nullabilityAnnotationsValidator?.validateAll(
+                codebase,
+                javaStubsCodebase.getTopLevelClassesFromSource().map(ClassItem::qualifiedName)
+            )
         }
     }
 
-    private fun mergeJaifAnnotation(
-        path: String,
-        item: Item,
-        annotationSource: String
-    ) {
-        if (annotationSource.isEmpty()) {
-            return
-        }
+    private fun mergeQualifierAnnotationsFromCodebase(externalCodebase: Codebase) {
+        val visitor = object : ComparisonVisitor() {
+            override fun compare(old: Item, new: Item) {
+                val newModifiers = new.modifiers
+                for (annotation in old.modifiers.annotations()) {
+                    var addAnnotation = false
+                    if (annotation.isNullnessAnnotation()) {
+                        if (!newModifiers.hasNullnessInfo()) {
+                            addAnnotation = true
+                        }
+                    } else {
+                        // TODO: Check for other incompatibilities than nullness?
+                        val qualifiedName = annotation.qualifiedName() ?: continue
+                        if (newModifiers.findAnnotation(qualifiedName) == null) {
+                            addAnnotation = true
+                        }
+                    }
 
-        if (annotationSource.contains("(")) {
-            warning("$path: Can't merge complex annotations from jaif yet: $annotationSource")
-            return
+                    if (addAnnotation) {
+                        // Don't map annotation names - this would turn newly non null back into non null
+                        new.mutableModifiers().addAnnotation(
+                            new.codebase.createAnnotation(
+                                annotation.toSource(),
+                                new,
+                                mapName = false
+                            )
+                        )
+                    }
+                }
+            }
         }
-        val originalName = annotationSource.substring(1) // remove "@"
-        val qualifiedName = AnnotationItem.mapName(codebase, originalName) ?: originalName
-        if (hasNullnessConflicts(item, qualifiedName)) {
-            return
-        }
+        CodebaseComparator().compare(
+            visitor, externalCodebase, codebase, ApiPredicate()
+        )
+    }
 
-        val annotationItem = codebase.createAnnotation("@$qualifiedName")
-        item.mutableModifiers().addAnnotation(annotationItem)
+    private fun mergeInclusionAnnotationsFromCodebase(externalCodebase: Codebase) {
+        val inclusionAnnotations = options.showAnnotations union options.hideAnnotations
+        if (inclusionAnnotations.isNotEmpty()) {
+            val visitor = object : ComparisonVisitor() {
+                override fun compare(old: Item, new: Item) {
+                    // Transfer any show/hide annotations from the external to the main codebase.
+                    for (annotation in old.modifiers.annotations()) {
+                        if (inclusionAnnotations.contains(annotation.qualifiedName())) {
+                            new.mutableModifiers().addAnnotation(annotation)
+                        }
+                    }
+                    // The hidden field in the main codebase is already initialized. So if the
+                    // element is hidden in the external codebase, hide it in the main codebase too.
+                    if (old.hidden) {
+                        new.hidden = true
+                    }
+                    if (old.originallyHidden) {
+                        new.originallyHidden = true
+                    }
+                }
+            }
+            CodebaseComparator().compare(visitor, externalCodebase, codebase)
+        }
     }
 
     internal fun error(message: String) {
@@ -591,7 +581,7 @@
 
                         // Attempt to sort in reflection order
                         if (!found && reflectionFields != null) {
-                            val filterEmit = ApiVisitor(codebase).filterEmit
+                            val filterEmit = ApiVisitor().filterEmit
 
                             // Attempt with reflection
                             var first = true
@@ -697,7 +687,7 @@
                 }
             }
 
-            isNonNull(name) -> return codebase.createAnnotation("@$ANDROIDX_NOTNULL")
+            isNonNull(name) -> return codebase.createAnnotation("@$ANDROIDX_NONNULL")
 
             isNullable(name) -> return codebase.createAnnotation("@$ANDROIDX_NULLABLE")
 
@@ -723,7 +713,7 @@
     private fun isNonNull(name: String): Boolean {
         return name == IDEA_NOTNULL ||
             name == ANDROID_NOTNULL ||
-            name == ANDROIDX_NOTNULL ||
+            name == ANDROIDX_NONNULL ||
             name == SUPPORT_NOTNULL
     }
 
@@ -759,10 +749,10 @@
 
 // TODO: Replace with usage of DefaultAnnotationAttribute?
 class XmlBackedAnnotationItem(
-    override var codebase: Codebase,
+    codebase: Codebase,
     private val qualifiedName: String,
     private val attributes: List<XmlBackedAnnotationAttribute> = emptyList()
-) : AnnotationItem {
+) : DefaultAnnotationItem(codebase) {
     override fun qualifiedName(): String? = AnnotationItem.mapName(codebase, qualifiedName)
 
     override fun attributes() = attributes
diff --git a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
index 25f1f75..fb528c1 100644
--- a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
@@ -29,6 +29,7 @@
 import com.android.tools.metalava.model.PackageList
 import com.android.tools.metalava.model.ParameterItem
 import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.model.psi.EXPAND_DOCUMENTATION
 import com.android.tools.metalava.model.visitors.ApiVisitor
 import com.android.tools.metalava.model.visitors.ItemVisitor
 import java.util.ArrayList
@@ -398,7 +399,7 @@
         val publicSuperClasses = superClasses.filter { filterEmit.test(it) && !it.isJavaLangObject() }
         for (superClass in publicSuperClasses) {
             for (method in superClass.methods()) {
-                if (!method.modifiers.isAbstract()) {
+                if (!method.modifiers.isAbstract() || !method.modifiers.isPublicOrProtected()) {
                     continue
                 }
                 val name = method.name()
@@ -414,7 +415,7 @@
         // Also add in any concrete public methods from hidden super classes
         for (superClass in hiddenSuperClasses) {
             for (method in superClass.methods()) {
-                if (method.modifiers.isAbstract()) {
+                if (method.modifiers.isAbstract() || !method.modifiers.isPublic()) {
                     continue
                 }
                 val name = method.name()
@@ -491,6 +492,14 @@
                     method.documentation
              */
             method.inheritedMethod = true
+            method.inheritedFrom = it.containingClass()
+
+            // The documentation may use relative references to classes in import statements
+            // in the original class, so expand the documentation to be fully qualified.
+            @Suppress("ConstantConditionIf")
+            if (!EXPAND_DOCUMENTATION) {
+                method.documentation = it.fullyQualifiedDocumentation()
+            }
             cls.addMethod(method)
         }
     }
@@ -512,11 +521,20 @@
         }
     }
 
-    /** Merge in external data from all configured sources */
-    fun mergeExternalAnnotations() {
-        val mergeAnnotations = options.mergeAnnotations
-        if (!mergeAnnotations.isEmpty()) {
-            AnnotationsMerger(codebase).merge(mergeAnnotations)
+    /**
+     * Merge in external qualifier annotations (i.e. ones intended to be included in the API written
+     * from all configured sources.
+     */
+    fun mergeExternalQualifierAnnotations() {
+        if (!options.mergeQualifierAnnotations.isEmpty()) {
+            AnnotationsMerger(codebase).mergeQualifierAnnotations(options.mergeQualifierAnnotations)
+        }
+    }
+
+    /** Merge in external show/hide annotations from all configured sources */
+    fun mergeExternalInclusionAnnotations() {
+        if (!options.mergeInclusionAnnotations.isEmpty()) {
+            AnnotationsMerger(codebase).mergeInclusionAnnotations(options.mergeInclusionAnnotations)
         }
     }
 
@@ -527,14 +545,14 @@
     private fun propagateHiddenRemovedAndDocOnly(includingFields: Boolean) {
         packages.accept(object : ItemVisitor(visitConstructorsAsMethods = true, nestInnerClasses = true) {
             override fun visitPackage(pkg: PackageItem) {
-                if (pkg.modifiers.hasShowAnnotation()) {
-                    pkg.hidden = false
-                } else if (pkg.modifiers.hasHideAnnotations()) {
-                    pkg.hidden = true
+                when {
+                    options.hidePackages.contains(pkg.qualifiedName()) -> pkg.hidden = true
+                    pkg.modifiers.hasShowAnnotation() -> pkg.hidden = false
+                    pkg.modifiers.hasHideAnnotations() -> pkg.hidden = true
                 }
                 val containingPackage = pkg.containingPackage()
                 if (containingPackage != null) {
-                    if (containingPackage.hidden) {
+                    if (containingPackage.hidden && !containingPackage.isDefault) {
                         pkg.hidden = true
                     }
                     if (containingPackage.docOnly) {
@@ -550,11 +568,17 @@
                     // Make containing package non-hidden if it contains a show-annotation
                     // class. Doclava does this in PackageInfo.isHidden().
                     cls.containingPackage().hidden = false
+                    if (cls.containingClass() != null) {
+                        ensureParentVisible(cls)
+                    }
                 } else if (cls.modifiers.hasHideAnnotations()) {
                     cls.hidden = true
                 } else if (containingClass != null) {
                     if (containingClass.hidden) {
                         cls.hidden = true
+                    } else if (containingClass.originallyHidden && containingClass.modifiers.hasShowSingleAnnotation()) {
+                        // See explanation in visitMethod
+                        cls.hidden = true
                     }
                     if (containingClass.docOnly) {
                         cls.docOnly = true
@@ -566,6 +590,11 @@
                     val containingPackage = cls.containingPackage()
                     if (containingPackage.hidden && !containingPackage.isDefault) {
                         cls.hidden = true
+                    } else if (containingPackage.originallyHidden) {
+                        // Package was marked hidden; it's been unhidden by some other
+                        // classes (marked with show annotations) but this class
+                        // should continue to default.
+                        cls.hidden = true
                     }
                     if (containingPackage.docOnly && !containingPackage.isDefault) {
                         cls.docOnly = true
@@ -579,12 +608,18 @@
             override fun visitMethod(method: MethodItem) {
                 if (method.modifiers.hasShowAnnotation()) {
                     method.hidden = false
+                    ensureParentVisible(method)
                 } else if (method.modifiers.hasHideAnnotations()) {
                     method.hidden = true
                 } else {
                     val containingClass = method.containingClass()
                     if (containingClass.hidden) {
                         method.hidden = true
+                    } else if (containingClass.originallyHidden && containingClass.modifiers.hasShowSingleAnnotation()) {
+                        // This is a member in a class that was hidden but then unhidden;
+                        // but it was unhidden by a non-recursive (single) show annotation, so
+                        // don't inherit the show annotation into this item.
+                        method.hidden = true
                     }
                     if (containingClass.docOnly) {
                         method.docOnly = true
@@ -598,6 +633,7 @@
             override fun visitField(field: FieldItem) {
                 if (field.modifiers.hasShowAnnotation()) {
                     field.hidden = false
+                    ensureParentVisible(field)
                 } else if (field.modifiers.hasHideAnnotations()) {
                     field.hidden = true
                 } else {
@@ -610,6 +646,9 @@
                     */
                     if (includingFields && containingClass.hidden) {
                         field.hidden = true
+                    } else if (containingClass.originallyHidden && containingClass.modifiers.hasShowSingleAnnotation()) {
+                        // See explanation in visitMethod
+                        field.hidden = true
                     }
                     if (containingClass.docOnly) {
                         field.docOnly = true
@@ -619,95 +658,29 @@
                     }
                 }
             }
-        })
-    }
 
-    private fun checkHiddenTypes() {
-        packages.accept(object : ApiVisitor(codebase, visitConstructorsAsMethods = false) {
-            override fun visitMethod(method: MethodItem) {
-                checkType(method, method.returnType() ?: return) // constructors don't have
-            }
-
-            override fun visitField(field: FieldItem) {
-                checkType(field, field.type())
-            }
-
-            override fun visitParameter(parameter: ParameterItem) {
-                checkType(parameter, parameter.type())
-            }
-
-            private fun checkType(item: Item, type: TypeItem) {
-                if (type.primitive) {
-                    return
-                }
-
-                val cls = type.asClass()
-
-                // Don't flag type parameters like T
-                if (cls?.isTypeParameter == true) {
-                    return
-                }
-
-                // class may be null for things like array types and ellipsis types,
-                // but iterating through the type argument classes below will find and
-                // check the component class
-                if (cls != null && !filterReference.test(cls) && !cls.isFromClassPath()) {
+            private fun ensureParentVisible(item: Item) {
+                val parent = item.parent() ?: return
+                if (parent.hidden && item.modifiers.hasShowSingleAnnotation()) {
+                    val annotation = item.modifiers.annotations().find {
+                        options.showSingleAnnotations.contains(it.qualifiedName())
+                    } ?: options.showSingleAnnotations.first()
                     reporter.report(
-                        Errors.HIDDEN_TYPE_PARAMETER, item,
-                        "${item.toString().capitalize()} references hidden type $type."
+                        Errors.SHOWING_MEMBER_IN_HIDDEN_CLASS, item,
+                        "Attempting to unhide ${item.describe()}, but surrounding ${parent.describe()} is " +
+                            "hidden and should also be annotated with $annotation"
                     )
                 }
-
-                type.typeArgumentClasses()
-                    .filter { it != cls }
-                    .forEach { checkType(item, it) }
-            }
-
-            private fun checkType(item: Item, cls: ClassItem) {
-                if (!filterReference.test(cls)) {
-                    if (!cls.isFromClassPath()) {
-                        reporter.report(
-                            Errors.HIDDEN_TYPE_PARAMETER, item,
-                            "${item.toString().capitalize()} references hidden type $cls."
-                        )
-                    }
-                } else {
-                    cls.typeArgumentClasses()
-                        .filter { it != cls }
-                        .forEach { checkType(item, it) }
-                }
             }
         })
     }
 
-    private fun ensureSystemServicesProtectedWithPermission() {
-        if (options.showAnnotations.contains("android.annotation.SystemApi") && options.manifest != null) {
-            // Look for Android @SystemApi exposed outside the normal SDK; we require
-            // that they're protected with a system permission.
-
-            packages.accept(object : ApiVisitor(codebase) {
-                override fun visitClass(cls: ClassItem) {
-                    // This class is a system service if it's annotated with @SystemService,
-                    // or if it's android.content.pm.PackageManager
-                    if (cls.modifiers.isAnnotatedWith("android.annotation.SystemService") ||
-                        cls.qualifiedName() == "android.content.pm.PackageManager"
-                    ) {
-                        // Check permissions on system services
-                        for (method in cls.filteredMethods(filterEmit)) {
-                            checkSystemPermissions(method)
-                        }
-                    }
-                }
-            })
-        }
-    }
-
     private fun checkSystemPermissions(method: MethodItem) {
         if (method.isImplicitConstructor()) { // Don't warn on non-source elements like implicit default constructors
             return
         }
 
-        val annotation = method.modifiers.findAnnotation("android.annotation.RequiresPermission")
+        val annotation = method.modifiers.findAnnotation(ANDROID_REQUIRES_PERMISSION)
         var hasAnnotation = false
 
         if (annotation != null) {
@@ -740,7 +713,7 @@
                         }
 
                         reporter.report(
-                            Errors.REMOVED_FIELD, method,
+                            Errors.REQUIRES_PERMISSION, method,
                             "Permission '$perm' is not defined by manifest ${codebase.manifest}."
                         )
                         continue
@@ -755,7 +728,7 @@
                 }
                 if (any && missing.size == values.size) {
                     reporter.report(
-                        Errors.REMOVED_FIELD, method,
+                        Errors.REQUIRES_PERMISSION, method,
                         "None of the permissions ${missing.joinToString()} are defined by manifest " +
                             "${codebase.manifest}."
                     )
@@ -787,38 +760,151 @@
             return
         }
 
-        // TODO for performance: Single iteration over the whole API surface!
-        ensureSystemServicesProtectedWithPermission()
-        checkHiddenTypes()
+        val checkSystemApi = !reporter.isSuppressed(Errors.REQUIRES_PERMISSION) &&
+            options.showAnnotations.contains(ANDROID_SYSTEM_API) && options.manifest != null
+        val checkHiddenShowAnnotations = !reporter.isSuppressed(Errors.UNHIDDEN_SYSTEM_API) &&
+            options.showAnnotations.isNotEmpty()
 
-        packages.accept(object : ApiVisitor(codebase) {
+        packages.accept(object : ApiVisitor() {
+            override fun visitParameter(parameter: ParameterItem) {
+                checkTypeReferencesHidden(parameter, parameter.type())
+            }
+
             override fun visitItem(item: Item) {
-                // TODO: Check annotations and also mark removed/hidden based on annotations
-                if (item.deprecated && !item.documentation.contains("@deprecated")) {
+                if (item.deprecated && !item.documentation.contains("@deprecated") &&
+                    // Don't warn about this in Kotlin; the Kotlin deprecation annotation includes deprecation
+                    // messages (unlike java.lang.Deprecated which has no attributes). Instead, these
+                    // are added to the documentation by the [DocAnalyzer].
+                    !item.isKotlin()
+                ) {
                     reporter.report(
                         Errors.DEPRECATION_MISMATCH, item,
                         "${item.toString().capitalize()}: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match"
                     )
+                    // TODO: Check opposite (doc tag but no annotation)
                 }
-                // TODO: Check opposite (doc tag but no annotation)
-                // TODO: Other checks
+
+                if (checkHiddenShowAnnotations &&
+                    item.hasShowAnnotation() &&
+                    !item.documentation.contains("@hide")
+                ) {
+                    val annotationName = (item.modifiers.annotations().firstOrNull {
+                        options.showAnnotations.contains(it.qualifiedName())
+                    }?.qualifiedName() ?: options.showAnnotations.first()).removePrefix(ANDROID_ANNOTATION_PREFIX)
+                    reporter.report(
+                        Errors.UNHIDDEN_SYSTEM_API, item,
+                        "@$annotationName APIs must also be marked @hide: ${item.describe()}"
+                    )
+                }
+            }
+
+            override fun visitClass(cls: ClassItem) {
+                // Propagate @Deprecated flags down from classes into inner classes, if configured.
+                // Done here rather than in the analyzer which propagates visibility, since we want to do it
+                // after warning
+                val containingClass = cls.containingClass()
+                if (containingClass != null && containingClass.deprecated && compatibility.propagateDeprecatedInnerClasses) {
+                    cls.deprecated = true
+                }
+
+                if (checkSystemApi) {
+                    // Look for Android @SystemApi exposed outside the normal SDK; we require
+                    // that they're protected with a system permission.
+                    // Also flag @SystemApi apis not annotated with @hide.
+
+                    // This class is a system service if it's annotated with @SystemService,
+                    // or if it's android.content.pm.PackageManager
+                    if (cls.modifiers.isAnnotatedWith("android.annotation.SystemService") ||
+                        cls.qualifiedName() == "android.content.pm.PackageManager"
+                    ) {
+                        // Check permissions on system services
+                        for (method in cls.filteredMethods(filterEmit)) {
+                            checkSystemPermissions(method)
+                        }
+                    }
+                }
+            }
+
+            override fun visitField(field: FieldItem) {
+                val containingClass = field.containingClass()
+                if (containingClass.deprecated && compatibility.propagateDeprecatedMembers) {
+                    field.deprecated = true
+                }
+
+                checkTypeReferencesHidden(field, field.type())
             }
 
             override fun visitMethod(method: MethodItem) {
+                if (!method.isConstructor()) {
+                    checkTypeReferencesHidden(
+                        method,
+                        method.returnType()!!
+                    ) // returnType is nullable only for constructors
+                }
+
+                val containingClass = method.containingClass()
+                if (containingClass.deprecated && compatibility.propagateDeprecatedMembers) {
+                    method.deprecated = true
+                }
+
                 // Make sure we don't annotate findViewById & getSystemService as @Nullable.
                 // See for example 68914170.
                 val name = method.name()
                 if ((name == "findViewById" || name == "getSystemService") && method.parameters().size == 1 &&
-                    method.modifiers.hasNullnessInfo()
+                    method.modifiers.isNullable()
                 ) {
-                    // In master we generate a warning here; in this branch we won't go in
-                    // and remove the assertions
+                    reporter.report(
+                        Errors.EXPECTED_PLATFORM_TYPE, method,
+                        "$method should not be annotated @Nullable; it should be left unspecified to make it a platform type"
+                    )
                     val annotation = method.modifiers.annotations().find { it.isNullable() }
                     annotation?.let {
                         method.mutableModifiers().removeAnnotation(it)
                     }
                 }
             }
+
+            private fun checkTypeReferencesHidden(item: Item, type: TypeItem) {
+                if (type.primitive) {
+                    return
+                }
+
+                val cls = type.asClass()
+
+                // Don't flag type parameters like T
+                if (cls?.isTypeParameter == true) {
+                    return
+                }
+
+                // class may be null for things like array types and ellipsis types,
+                // but iterating through the type argument classes below will find and
+                // check the component class
+                if (cls != null && !filterReference.test(cls) && !cls.isFromClassPath()) {
+                    reporter.report(
+                        Errors.HIDDEN_TYPE_PARAMETER, item,
+                        "${item.toString().capitalize()} references hidden type $type."
+                    )
+                }
+
+                type.typeArgumentClasses()
+                    .filter { it != cls }
+                    .forEach { checkTypeReferencesHidden(item, it) }
+            }
+
+            private fun checkTypeReferencesHidden(item: Item, cls: ClassItem) {
+                if (!filterReference.test(cls)) {
+                    if (!cls.isFromClassPath()) {
+                        reporter.report(
+                            Errors.HIDDEN_TYPE_PARAMETER, item,
+                            "${item.toString().capitalize()} references hidden type $cls."
+                        )
+                    }
+                } else {
+                    cls.typeArgumentClasses()
+                        .filter { it != cls }
+                        .forEach { checkTypeReferencesHidden(item, it) }
+                }
+            }
         })
     }
 
@@ -832,7 +918,7 @@
     private fun handleStripping(stubImportPackages: Set<String>) {
         val notStrippable = HashSet<ClassItem>(5000)
 
-        val filter = ApiPredicate(codebase, ignoreShown = true)
+        val filter = ApiPredicate(ignoreShown = true)
 
         // If a class is public or protected, not hidden, not imported and marked as included,
         // then we can't strip it
@@ -840,7 +926,7 @@
         allTopLevelClasses
             .filter { it.checkLevel() && it.emit && !it.hidden() }
             .forEach {
-                cantStripThis(it, filter, notStrippable, stubImportPackages)
+                cantStripThis(it, filter, notStrippable, stubImportPackages, it, "self")
             }
 
         // complain about anything that looks includeable but is not supposed to
@@ -866,6 +952,13 @@
                     }
 
                     val returnType = m.returnType()
+                    if (!m.deprecated && !cl.deprecated && returnType != null && returnType.asClass()?.deprecated == true) {
+                        reporter.report(
+                            Errors.REFERENCES_DEPRECATED, m,
+                            "Return type of deprecated type $returnType in ${cl.qualifiedName()}.${m.name()}(): this method should also be deprecated"
+                        )
+                    }
+
                     var hiddenClass = findHiddenClasses(returnType, stubImportPackages)
                     if (hiddenClass != null && !hiddenClass.isFromClassPath()) {
                         if (hiddenClass.qualifiedName() == returnType?.asClass()?.qualifiedName()) {
@@ -888,6 +981,13 @@
                     for (p in m.parameters()) {
                         val t = p.type()
                         if (!t.primitive) {
+                            if (!m.deprecated && !cl.deprecated && t.asClass()?.deprecated == true) {
+                                reporter.report(
+                                    Errors.REFERENCES_DEPRECATED, m,
+                                    "Parameter of deprecated type $t in ${cl.qualifiedName()}.${m.name()}(): this method should also be deprecated"
+                                )
+                            }
+
                             hiddenClass = findHiddenClasses(t, stubImportPackages)
                             if (hiddenClass != null && !hiddenClass.isFromClassPath()) {
                                 if (hiddenClass.qualifiedName() == t.asClass()?.qualifiedName()) {
@@ -910,7 +1010,8 @@
             } else if (cl.deprecated) {
                 // not hidden, but deprecated
                 reporter.report(Errors.DEPRECATED, cl, "Class ${cl.qualifiedName()} is deprecated")
-            } else {
+            } else if (reporter.isSuppressed(Errors.REFERENCES_HIDDEN, cl)) {
+                // If we're not reporting hidden references, bring the type back
                 // Bring this class back
                 cl.hidden = false
                 cl.removed = false
@@ -922,7 +1023,9 @@
         cl: ClassItem,
         filter: Predicate<Item>,
         notStrippable: MutableSet<ClassItem>,
-        stubImportPackages: Set<String>?
+        stubImportPackages: Set<String>?,
+        from: Item,
+        usage: String
     ) {
         if (stubImportPackages != null && stubImportPackages.contains(cl.containingPackage().qualifiedName())) {
             // if the package is imported then it does not need stubbing.
@@ -933,6 +1036,15 @@
             return
         }
 
+        if ((cl.isHiddenOrRemoved() || cl.isPackagePrivate) && !cl.isTypeParameter) {
+            reporter.report(
+                Errors.REFERENCES_HIDDEN, from,
+                "Class ${cl.qualifiedName()} is ${if (cl.isHiddenOrRemoved()) "hidden" else "not public"} but was referenced ($usage) from public ${from.describe(
+                    false
+                )}"
+            )
+        }
+
         if (!notStrippable.add(cl)) {
             // slight optimization: if it already contains cl, it already contains
             // all of cl's parents
@@ -948,16 +1060,19 @@
             if (!fieldType.primitive) {
                 val typeClass = fieldType.asClass()
                 if (typeClass != null) {
-                    cantStripThis(typeClass, filter, notStrippable, stubImportPackages)
+                    cantStripThis(typeClass, filter, notStrippable, stubImportPackages, field, "as field type")
                 }
                 for (cls in fieldType.typeArgumentClasses()) {
-                    cantStripThis(cls, filter, notStrippable, stubImportPackages)
+                    if (cls == typeClass) {
+                        continue
+                    }
+                    cantStripThis(cls, filter, notStrippable, stubImportPackages, field, "as field type argument class")
                 }
             }
         }
         // cant strip any of the type's generics
         for (cls in cl.typeArgumentClasses()) {
-            cantStripThis(cls, filter, notStrippable, stubImportPackages)
+            cantStripThis(cls, filter, notStrippable, stubImportPackages, cl, "as type argument")
         }
         // cant strip any of the annotation elements
         // cantStripThis(cl.annotationElements(), notStrippable);
@@ -967,7 +1082,7 @@
         // blow the outer class open if this is an inner class
         val containingClass = cl.containingClass()
         if (containingClass != null) {
-            cantStripThis(containingClass, filter, notStrippable, stubImportPackages)
+            cantStripThis(containingClass, filter, notStrippable, stubImportPackages, cl, "as containing class")
         }
         // blow open super class and interfaces
         // TODO: Consider using val superClass = cl.filteredSuperclass(filter)
@@ -988,7 +1103,10 @@
                     )
                 }
             } else {
-                cantStripThis(superClass, filter, notStrippable, stubImportPackages)
+                // doclava would also mark the package private super classes as unhidden, but that's not
+                // right (this was just done for its stub handling)
+                //   cantStripThis(superClass, filter, notStrippable, stubImportPackages, cl, "as super class")
+
                 if (superClass.isPrivate && !superClass.isFromClassPath()) {
                     reporter.report(
                         Errors.PRIVATE_SUPERCLASS, cl, "Public class " +
@@ -1012,34 +1130,68 @@
                 continue
             }
             for (typeParameterClass in method.typeArgumentClasses()) {
-                cantStripThis(typeParameterClass, filter, notStrippable, stubImportPackages)
+                cantStripThis(
+                    typeParameterClass,
+                    filter,
+                    notStrippable,
+                    stubImportPackages,
+                    method,
+                    "as type parameter"
+                )
             }
             for (parameter in method.parameters()) {
                 for (parameterTypeClass in parameter.type().typeArgumentClasses()) {
-                    cantStripThis(parameterTypeClass, filter, notStrippable, stubImportPackages)
+                    cantStripThis(
+                        parameterTypeClass,
+                        filter,
+                        notStrippable,
+                        stubImportPackages,
+                        parameter,
+                        "as parameter type"
+                    )
                     for (tcl in parameter.type().typeArgumentClasses()) {
+                        if (tcl == parameterTypeClass) {
+                            continue
+                        }
                         if (tcl.isHiddenOrRemoved()) {
                             reporter.report(
                                 Errors.UNAVAILABLE_SYMBOL, method,
-                                "Parameter of hidden type ${tcl.fullName()}" +
+                                "Parameter of hidden type ${tcl.fullName()} " +
                                     "in ${method.containingClass().qualifiedName()}.${method.name()}()"
                             )
                         } else {
-                            cantStripThis(tcl, filter, notStrippable, stubImportPackages)
+                            cantStripThis(
+                                tcl,
+                                filter,
+                                notStrippable,
+                                stubImportPackages,
+                                parameter,
+                                "as type parameter"
+                            )
                         }
                     }
                 }
             }
             for (thrown in method.throwsTypes()) {
-                cantStripThis(thrown, filter, notStrippable, stubImportPackages)
+                cantStripThis(thrown, filter, notStrippable, stubImportPackages, method, "as exception")
             }
             val returnType = method.returnType()
             if (returnType != null && !returnType.primitive) {
                 val returnTypeClass = returnType.asClass()
                 if (returnTypeClass != null) {
-                    cantStripThis(returnTypeClass, filter, notStrippable, stubImportPackages)
+                    cantStripThis(returnTypeClass, filter, notStrippable, stubImportPackages, method, "as return type")
                     for (tyItem in returnType.typeArgumentClasses()) {
-                        cantStripThis(tyItem, filter, notStrippable, stubImportPackages)
+                        if (tyItem == returnTypeClass) {
+                            continue
+                        }
+                        cantStripThis(
+                            tyItem,
+                            filter,
+                            notStrippable,
+                            stubImportPackages,
+                            method,
+                            "as return type parameter"
+                        )
                     }
                 }
             }
diff --git a/src/main/java/com/android/tools/metalava/ApiType.kt b/src/main/java/com/android/tools/metalava/ApiType.kt
new file mode 100644
index 0000000..f712f0e
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/ApiType.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.tools.metalava.doclava1.ApiPredicate
+import com.android.tools.metalava.doclava1.ElidingPredicate
+import com.android.tools.metalava.doclava1.FilterPredicate
+import com.android.tools.metalava.model.Item
+import java.io.File
+import java.util.function.Predicate
+
+/** Types of APIs emitted (or parsed etc) */
+enum class ApiType(val flagName: String, val displayName: String = flagName) {
+    /** The public API */
+    PUBLIC_API("api", "public") {
+        override fun getOptionFile(): File? {
+            return options.apiFile
+        }
+
+        override fun getEmitFilter(): Predicate<Item> {
+            val apiFilter = FilterPredicate(ApiPredicate())
+            val apiReference = ApiPredicate(ignoreShown = true)
+            return apiFilter.and(ElidingPredicate(apiReference))
+        }
+
+        override fun getReferenceFilter(): Predicate<Item> {
+            return ApiPredicate(ignoreShown = true)
+        }
+    },
+
+    /** The API that has been removed */
+    REMOVED("removed", "removed") {
+        override fun getOptionFile(): File? {
+            return options.removedApiFile
+        }
+
+        override fun getEmitFilter(): Predicate<Item> {
+            val removedFilter = FilterPredicate(ApiPredicate(matchRemoved = true))
+            val removedReference = ApiPredicate(ignoreShown = true, ignoreRemoved = true)
+            return removedFilter.and(ElidingPredicate(removedReference))
+        }
+
+        override fun getReferenceFilter(): Predicate<Item> {
+            return ApiPredicate(ignoreShown = true, ignoreRemoved = true)
+        }
+    },
+
+    /** The private API */
+    PRIVATE("private", "private") {
+        override fun getOptionFile(): File? {
+            return options.privateApiFile
+        }
+
+        override fun getEmitFilter(): Predicate<Item> {
+            val apiFilter = FilterPredicate(ApiPredicate())
+            val memberIsNotCloned: Predicate<Item> = Predicate { !it.isCloned() }
+            return memberIsNotCloned.and(apiFilter.negate())
+        }
+
+        override fun getReferenceFilter(): Predicate<Item> {
+            return Predicate { true }
+        }
+    },
+
+    /** Everything */
+    ALL("all", "all") {
+        override fun getOptionFile(): File? {
+            return null
+        }
+
+        override fun getEmitFilter(): Predicate<Item> {
+            return Predicate { it.emit }
+        }
+
+        override fun getReferenceFilter(): Predicate<Item> {
+            return Predicate { true }
+        }
+    };
+
+    /** Returns the user-configured file where the API has been written to, if any */
+    abstract fun getOptionFile(): File?
+
+    abstract fun getEmitFilter(): Predicate<Item>
+
+    abstract fun getReferenceFilter(): Predicate<Item>
+
+    override fun toString(): String = displayName
+}
diff --git a/src/main/java/com/android/tools/metalava/ArtifactTagger.kt b/src/main/java/com/android/tools/metalava/ArtifactTagger.kt
index 23c9be7..ee9daf0 100644
--- a/src/main/java/com/android/tools/metalava/ArtifactTagger.kt
+++ b/src/main/java/com/android/tools/metalava/ArtifactTagger.kt
@@ -38,6 +38,9 @@
     /** Any registered artifacts? */
     fun any() = artifacts.isNotEmpty()
 
+    /** Remove all registrations */
+    fun clear() = artifacts.clear()
+
     /** Returns the artifacts */
     private fun getRegistrations(): Collection<Map.Entry<File, String>> = artifacts.entries
 
@@ -74,7 +77,7 @@
         }
 
         if (warnAboutMissing) {
-            codebase.accept(object : ApiVisitor(codebase) {
+            codebase.accept(object : ApiVisitor() {
                 override fun visitClass(cls: ClassItem) {
                     if (cls.artifact == null && cls.isTopLevelClass()) {
                         reporter.report(
@@ -93,8 +96,14 @@
         codebase: Codebase
     ) {
         for (specPkg in specApi.getPackages().packages) {
+            if (!specPkg.emit) {
+                continue
+            }
             val pkg = codebase.findPackage(specPkg.qualifiedName()) ?: continue
             for (cls in pkg.allClasses()) {
+                if (!cls.emit) {
+                    continue
+                }
                 if (cls.artifact == null) {
                     cls.artifact = mavenSpec
                 } else {
diff --git a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
index 1c6e2f6..77d3dc0 100644
--- a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
+++ b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
@@ -25,6 +25,7 @@
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.PackageItem
 import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.PropertyItem
 import com.android.tools.metalava.model.visitors.ApiVisitor
 import com.android.tools.metalava.model.visitors.VisibleItemVisitor
 import com.intellij.util.containers.Stack
@@ -60,6 +61,7 @@
     open fun compare(old: ConstructorItem, new: ConstructorItem) {}
     open fun compare(old: MethodItem, new: MethodItem) {}
     open fun compare(old: FieldItem, new: FieldItem) {}
+    open fun compare(old: PropertyItem, new: PropertyItem) {}
     open fun compare(old: ParameterItem, new: ParameterItem) {}
 
     open fun added(new: PackageItem) {}
@@ -67,6 +69,7 @@
     open fun added(new: ConstructorItem) {}
     open fun added(new: MethodItem) {}
     open fun added(new: FieldItem) {}
+    open fun added(new: PropertyItem) {}
     open fun added(new: ParameterItem) {}
 
     open fun removed(old: PackageItem, from: Item?) {}
@@ -74,6 +77,7 @@
     open fun removed(old: ConstructorItem, from: ClassItem?) {}
     open fun removed(old: MethodItem, from: ClassItem?) {}
     open fun removed(old: FieldItem, from: ClassItem?) {}
+    open fun removed(old: PropertyItem, from: ClassItem?) {}
     open fun removed(old: ParameterItem, from: MethodItem?) {}
 }
 
@@ -87,14 +91,21 @@
         // two trees
         val oldTree = createTree(old, filter)
         val newTree = createTree(new, filter)
-        compare(visitor, oldTree, newTree, null)
+
+        /* Debugging:
+        println("Old:\n${ItemTree.prettyPrint(oldTree)}")
+        println("New:\n${ItemTree.prettyPrint(newTree)}")
+        */
+
+        compare(visitor, oldTree, newTree, null, null)
     }
 
     private fun compare(
         visitor: ComparisonVisitor,
         oldList: List<ItemTree>,
         newList: List<ItemTree>,
-        newParent: Item?
+        newParent: Item?,
+        oldParent: Item?
     ) {
         // Debugging tip: You can print out a tree like this: ItemTree.prettyPrint(list)
         var index1 = 0
@@ -115,7 +126,7 @@
                     when {
                         compare > 0 -> {
                             index2++
-                            visitAdded(visitor, new)
+                            visitAdded(new, oldParent, visitor, newTree)
                         }
                         compare < 0 -> {
                             index1++
@@ -125,7 +136,7 @@
                             visitCompare(visitor, old, new)
 
                             // Compare the children (recurse)
-                            compare(visitor, oldTree.children, newTree.children, newTree.item())
+                            compare(visitor, oldTree.children, newTree.children, newTree.item(), oldTree.item())
 
                             index1++
                             index2++
@@ -140,7 +151,10 @@
             } else if (index2 < length2) {
                 // All the remaining items in newList have been added
                 while (index2 < length2) {
-                    visitAdded(visitor, newList[index2++].item())
+                    val newTree = newList[index2++]
+                    val new = newTree.item()
+
+                    visitAdded(new, oldParent, visitor, newTree)
                 }
             } else {
                 break
@@ -148,6 +162,39 @@
         }
     }
 
+    private fun visitAdded(
+        new: Item,
+        oldParent: Item?,
+        visitor: ComparisonVisitor,
+        newTree: ItemTree
+    ) {
+        // If it's a method, we may not have added a new method,
+        // we may simply have inherited it previously and overriding
+        // it now (or in the case of signature files, identical overrides
+        // are not explicitly listed and therefore not added to the model)
+        val inherited =
+            if (new is MethodItem && oldParent is ClassItem) {
+                oldParent.findMethod(
+                    template = new,
+                    includeSuperClasses = true,
+                    includeInterfaces = true
+                )?.duplicate(oldParent)
+            } else {
+                null
+            }
+
+        if (inherited != null) {
+            visitCompare(visitor, inherited, new)
+            // Compare the children (recurse)
+            if (inherited.parameters().isNotEmpty()) {
+                val parameters = inherited.parameters().map { ItemTree(it) }.toList()
+                compare(visitor, parameters, newTree.children, newTree.item(), inherited)
+            }
+        } else {
+            visitAdded(visitor, new)
+        }
+    }
+
     private fun visitAdded(visitor: ComparisonVisitor, new: Item) {
         if (visitor.visitAddedItemsRecursively) {
             new.accept(object : VisibleItemVisitor() {
@@ -180,6 +227,7 @@
             }
             is FieldItem -> visitor.added(item)
             is ParameterItem -> visitor.added(item)
+            is PropertyItem -> visitor.added(item)
         }
     }
 
@@ -203,6 +251,7 @@
             }
             is FieldItem -> visitor.removed(item, from as ClassItem?)
             is ParameterItem -> visitor.removed(item, from as MethodItem?)
+            is PropertyItem -> visitor.removed(item, from as ClassItem?)
         }
     }
 
@@ -226,6 +275,7 @@
             }
             is FieldItem -> visitor.compare(old, new as FieldItem)
             is ParameterItem -> visitor.compare(old, new as ParameterItem)
+            is PropertyItem -> visitor.compare(old, new as PropertyItem)
         }
     }
 
@@ -241,7 +291,8 @@
                 is ClassItem -> 4
                 is ParameterItem -> 5
                 is AnnotationItem -> 6
-                else -> 7
+                is PropertyItem -> 7
+                else -> 8
             }
         }
 
@@ -299,6 +350,9 @@
                     is AnnotationItem -> {
                         (item1.qualifiedName() ?: "").compareTo((item2 as AnnotationItem).qualifiedName() ?: "")
                     }
+                    is PropertyItem -> {
+                        item1.name().compareTo((item2 as PropertyItem).name())
+                    }
                     else -> {
                         error("Unexpected item type ${item1.javaClass}")
                     }
@@ -326,13 +380,12 @@
     }
 
     private fun createTree(codebase: Codebase, filter: Predicate<Item>? = null): List<ItemTree> {
-        // TODO: Make sure the items are sorted!
         val stack = Stack<ItemTree>()
         val root = ItemTree(null)
         stack.push(root)
 
-        val predicate = filter ?: Predicate { true }
-        // TODO: Skip empty packages
+        val acceptAll = codebase.preFiltered || filter == null
+        val predicate = if (acceptAll) Predicate { true } else filter!!
         codebase.accept(object : ApiVisitor(
             nestInnerClasses = true,
             inlineInheritedFields = true,
@@ -347,6 +400,8 @@
                 stack.push(node)
             }
 
+            override fun include(cls: ClassItem): Boolean = if (acceptAll) true else super.include(cls)
+
             override fun afterVisitItem(item: Item) {
                 stack.pop()
             }
diff --git a/src/main/java/com/android/tools/metalava/Compatibility.kt b/src/main/java/com/android/tools/metalava/Compatibility.kt
index 536f5f5..c29f0c7 100644
--- a/src/main/java/com/android/tools/metalava/Compatibility.kt
+++ b/src/main/java/com/android/tools/metalava/Compatibility.kt
@@ -58,20 +58,14 @@
     var nonstandardModifierOrder: Boolean = compat
 
     /** In signature files, skip the native modifier from the modifier lists */
-    var skipNativeModifier: Boolean = nonstandardModifierOrder
+    var skipNativeModifier: Boolean = true
 
     /** In signature files, skip the strictfp modifier from the modifier lists */
-    var skipStrictFpModifier: Boolean = nonstandardModifierOrder
+    var skipStrictFpModifier: Boolean = true
 
     /** Whether to include instance methods in annotation classes for the annotation properties */
     var skipAnnotationInstanceMethods: Boolean = compat
 
-    /** Include spaces after commas in type strings */
-    var spacesAfterCommas: Boolean = compat
-
-    /** Use two spaces after type for package private elements in signature files */
-    var doubleSpaceForPackagePrivate: Boolean = compat
-
     /**
      * In signature files, whether interfaces should also be described as "abstract"
      */
@@ -182,5 +176,17 @@
      */
     var includeExtendsObjectInWildcard = compat
 
+    /**
+     * If true, a @Deprecated class will automatically deprecate all its inner classes
+     * as well.
+     */
+    var propagateDeprecatedInnerClasses = !compat
+
+    /**
+     * If true, a @Deprecated class will automatically deprecate all members (not
+     * including inner classes; for that see [propagateDeprecatedInnerClasses]) as well.
+     */
+    var propagateDeprecatedMembers = !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 ab77657..e46958d 100644
--- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
+++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
@@ -21,16 +21,21 @@
 import com.android.tools.metalava.doclava1.ApiPredicate
 import com.android.tools.metalava.doclava1.Errors
 import com.android.tools.metalava.doclava1.Errors.Error
+import com.android.tools.metalava.doclava1.TextCodebase
 import com.android.tools.metalava.model.AnnotationItem
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.FieldItem
 import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.Item.Companion.describe
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.PackageItem
 import com.android.tools.metalava.model.ParameterItem
 import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.model.configuration
 import com.intellij.psi.PsiField
+import java.io.File
+import java.util.function.Predicate
 
 /**
  * Compares the current API with a previous version and makes sure
@@ -39,16 +44,68 @@
  *
  * TODO: Only allow nullness changes on final classes!
  */
-class CompatibilityCheck : ComparisonVisitor() {
+class CompatibilityCheck(
+    val filterReference: Predicate<Item>,
+    oldCodebase: Codebase,
+    private val apiType: ApiType,
+    private val base: Codebase? = null
+) : ComparisonVisitor() {
+
+    /**
+     * Request for compatibility checks.
+     * [file] represents the signature file to be checked. [apiType] represents which
+     * part of the API should be checked, [releaseType] represents what kind of codebase
+     * we are comparing it against.
+     */
+    data class CheckRequest(val file: File, val apiType: ApiType, val releaseType: ReleaseType) {
+        override fun toString(): String {
+            return "--check-compatibility:${apiType.flagName}:${releaseType.flagName} $file"
+        }
+    }
+
+    /** In old signature files, methods inherited from hidden super classes
+     * are not included. An example of this is StringBuilder.setLength.
+     * More details about this are listed in Compatibility.skipInheritedMethods.
+     * We may see these in the codebase but not in the (old) signature files,
+     * 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
+
     var foundProblems = false
 
     override fun compare(old: Item, new: Item) {
+        val oldModifiers = old.modifiers
+        val newModifiers = new.modifiers
+        if (oldModifiers.isOperator() && !newModifiers.isOperator()) {
+            report(
+                Errors.OPERATOR_REMOVAL,
+                new,
+                "Cannot remove `operator` modifier from ${describe(new)}: Incompatible change"
+            )
+        }
+
+        if (oldModifiers.isInfix() && !newModifiers.isInfix()) {
+            report(
+                Errors.INFIX_REMOVAL,
+                new,
+                "Cannot remove `infix` modifier from ${describe(new)}: Incompatible change"
+            )
+        }
+
         // Should not remove nullness information
         // Can't change information incompatibly
         val oldNullnessAnnotation = findNullnessAnnotation(old)
         if (oldNullnessAnnotation != null) {
             val newNullnessAnnotation = findNullnessAnnotation(new)
             if (newNullnessAnnotation == null) {
+                val implicitNullness = AnnotationItem.getImplicitNullness(new)
+                if (implicitNullness == true && isNullable(old)) {
+                    return
+                }
+                if (implicitNullness == false && !isNullable(old)) {
+                    return
+                }
                 val name = AnnotationItem.simpleName(oldNullnessAnnotation)
                 report(
                     Errors.INVALID_NULL_CONVERSION, new,
@@ -80,24 +137,6 @@
                 }
             }
         }
-
-        val oldModifiers = old.modifiers
-        val newModifiers = new.modifiers
-        if (oldModifiers.isOperator() && !newModifiers.isOperator()) {
-            report(
-                Errors.OPERATOR_REMOVAL,
-                new,
-                "Cannot remove `operator` modifier from ${describe(new)}: Incompatible change"
-            )
-        }
-
-        if (oldModifiers.isInfix() && !newModifiers.isInfix()) {
-            report(
-                Errors.INFIX_REMOVAL,
-                new,
-                "Cannot remove `infix` modifier from ${describe(new)}: Incompatible change"
-            )
-        }
     }
 
     override fun compare(old: ParameterItem, new: ParameterItem) {
@@ -163,7 +202,7 @@
             }
         }
 
-        for (iface in new.interfaceTypes()) {
+        for (iface in new.filteredInterfaceTypes(filterReference)) {
             val qualifiedName = iface.asClass()?.qualifiedName() ?: continue
             if (!old.implements(qualifiedName)) {
                 report(
@@ -173,10 +212,10 @@
         }
 
         if (!oldModifiers.isSealed() && newModifiers.isSealed()) {
-            report(Errors.ADD_SEALED, new, "Cannot add `sealed` modifier to ${describe(new)}: Incompatible change")
-        } else if (oldModifiers.isAbstract() != newModifiers.isAbstract()) {
+            report(Errors.ADD_SEALED, new, "Cannot add 'sealed' modifier to ${describe(new)}: Incompatible change")
+        } else if (old.isClass() && oldModifiers.isAbstract() != newModifiers.isAbstract()) {
             report(
-                Errors.CHANGED_ABSTRACT, new, "${describe(new, capitalize = true)} changed abstract qualifier"
+                Errors.CHANGED_ABSTRACT, new, "${describe(new, capitalize = true)} changed 'abstract' qualifier"
             )
         }
 
@@ -192,22 +231,22 @@
                         "${describe(
                             new,
                             capitalize = true
-                        )} added final qualifier but was previously uninstantiable and therefore could not be subclassed"
+                        )} added 'final' qualifier but was previously uninstantiable and therefore could not be subclassed"
                     )
                 } else {
                     report(
-                        Errors.ADDED_FINAL, new, "${describe(new, capitalize = true)} added final qualifier"
+                        Errors.ADDED_FINAL, new, "${describe(new, capitalize = true)} added 'final' qualifier"
                     )
                 }
             } else if (oldModifiers.isFinal() && !newModifiers.isFinal()) {
                 report(
-                    Errors.REMOVED_FINAL, new, "${describe(new, capitalize = true)} removed final qualifier"
+                    Errors.REMOVED_FINAL, new, "${describe(new, capitalize = true)} removed 'final' qualifier"
                 )
             }
 
             if (oldModifiers.isStatic() != newModifiers.isStatic()) {
                 report(
-                    Errors.CHANGED_STATIC, new, "${describe(new, capitalize = true)} changed static qualifier"
+                    Errors.CHANGED_STATIC, new, "${describe(new, capitalize = true)} changed 'static' qualifier"
                 )
             }
         }
@@ -320,12 +359,50 @@
                     "${describe(new, capitalize = true)} has changed return type from $oldTypeString to $newTypeString"
                 report(Errors.CHANGED_TYPE, new, message)
             }
+
+            // Annotation methods?
+            if (!old.hasSameValue(new)) {
+                val prevValue = old.defaultValue()
+                val prevString = if (prevValue.isEmpty()) {
+                    "nothing"
+                } else {
+                    prevValue
+                }
+
+                val newValue = new.defaultValue()
+                val newString = if (newValue.isEmpty()) {
+                    "nothing"
+                } else {
+                    newValue
+                }
+                val message = "${describe(
+                    new,
+                    capitalize = true
+                )} has changed value from $prevString to $newString"
+                report(Errors.CHANGED_VALUE, new, message)
+            }
         }
 
         // Check for changes in abstract, but only for regular classes; older signature files
         // sometimes describe interface methods as abstract
         if (new.containingClass().isClass()) {
-            if (oldModifiers.isAbstract() != newModifiers.isAbstract()) {
+            if (!oldModifiers.isAbstract() && newModifiers.isAbstract() &&
+                // In old signature files, overridden methods of abstract methods declared
+                // in super classes are sometimes omitted by doclava. This means that the method
+                // looks (from the signature file perspective) like it has not been implemented,
+                // whereas in reality it has. For just one example of this, consider
+                // FragmentBreadCrumbs.onLayout: it's a concrete implementation in that class
+                // of the inherited method from ViewGroup. However, in the signature file,
+                // FragmentBreadCrumbs does not list this method; it's only listed (as abstract)
+                // in the super class. In this scenario, the compatibility check would believe
+                // the old method in FragmentBreadCrumbs is abstract and the new method is not,
+                // which is not the case. Therefore, if the old method is coming from a signature
+                // file based codebase with an old format, we omit abstract change warnings.
+                // The reverse situation can also happen: AbstractSequentialList defines listIterator
+                // as abstract, but it's not recorded as abstract in the signature files anywhere,
+                // so we treat this as a nearly abstract method, which it is not.
+                (old.inheritedFrom == null || !comparingWithPartialSignatures)
+            ) {
                 report(
                     Errors.CHANGED_ABSTRACT, new, "${describe(new, capitalize = true)} has changed 'abstract' qualifier"
                 )
@@ -340,8 +417,12 @@
 
         // Check changes to final modifier. But skip enums where it varies between signature files and PSI
         // whether the methods are considered final.
-        if (!new.containingClass().isEnum()) {
-            if (!oldModifiers.isStatic()) {
+        if (!new.containingClass().isEnum() && !oldModifiers.isStatic()) {
+            // Skip changes in final; modifier change could come from inherited
+            // implementation from hidden super class. An example of this
+            // is SpannableString.charAt whose implementation comes from
+            // SpannableStringInternal.
+            if (old.inheritedFrom == null || !comparingWithPartialSignatures) {
                 // Compiler-generated methods vary in their 'final' qualifier between versions of
                 // the compiler, so this check needs to be quite narrow. A change in 'final'
                 // status of a method is only relevant if (a) the method is not declared 'static'
@@ -412,7 +493,7 @@
             }
         }
 
-        for (exec in new.throwsTypes()) {
+        for (exec in new.filteredThrowsTypes(filterReference)) {
             // exclude 'throws' changes to finalize() overrides with no arguments
             if (!old.throws(exec.qualifiedName())) {
                 if (old.name() != "finalize" || old.parameters().isNotEmpty()) {
@@ -421,6 +502,23 @@
                 }
             }
         }
+
+        if (new.modifiers.isInline() && new.isKotlin()) {
+            val oldTypes = old.typeParameterList().typeParameters()
+            val newTypes = new.typeParameterList().typeParameters()
+            for (i in 0 until oldTypes.size) {
+                if (i == newTypes.size) {
+                    break
+                }
+                if (newTypes[i].isReified() && !oldTypes[i].isReified()) {
+                    val message = "${describe(
+                        new,
+                        capitalize = true
+                    )} made type variable ${newTypes[i].simpleName()} reified: incompatible change"
+                    report(Errors.CHANGED_THROWS, new, message)
+                }
+            }
+        }
     }
 
     private fun describeBounds(
@@ -463,7 +561,14 @@
                     new,
                     capitalize = true
                 )} has changed value from $prevString to $newString"
-                report(Errors.CHANGED_VALUE, new, message)
+
+                if (message == "Field android.telephony.data.ApnSetting.TYPE_DEFAULT has changed value from 17 to 1") {
+                    // Temporarily ignore: this value changed incompatibly from 28.txt to current.txt.
+                    // It's not clear yet whether this value change needs to be reverted, or suppressed
+                    // permanently in the source code, but suppressing from metalava so we can unblock
+                    // getting the compatibility checks enabled.
+                } else
+                    report(Errors.CHANGED_VALUE, new, message)
             }
         }
 
@@ -519,13 +624,77 @@
     }
 
     private fun handleAdded(error: Error, item: Item) {
-        report(error, item, "Added ${describe(item)}")
+        var message = "Added ${describe(item)}"
+
+        // Clarify error message for removed API to make it less ambiguous
+        if (apiType == ApiType.REMOVED) {
+            message += " to the removed API"
+        } else if (options.showAnnotations.isNotEmpty()) {
+            if (options.showAnnotations.any { it.endsWith("SystemApi") }) {
+                message += " to the system API"
+            } else if (options.showAnnotations.any { it.endsWith("TestApi") }) {
+                message += " to the test API"
+            }
+        }
+
+        // In some cases we run the comparison on signature files
+        // generated into the temp directory, but in these cases
+        // try to report the item against the real item in the API instead
+        val equivalent = findBaseItem(item)
+        if (equivalent != null) {
+            report(error, equivalent, message)
+            return
+        }
+
+        report(error, item, message)
     }
 
     private fun handleRemoved(error: Error, item: Item) {
+        if (!item.emit) {
+            // It's a stub; this can happen when analyzing partial APIs
+            // such as a signature file for a library referencing types
+            // from the upstream library dependencies.
+            return
+        }
+
+        if (base != null) {
+            // We're diffing "overlay" APIs, such as system or test API files,
+            // where the signature files only list a delta from the full, "base" API.
+            // In that case, if an API is promoted from @SystemApi or @TestApi to be
+            // a full part of the API, it will look like a removal; it appeared in the
+            // previous file and not in the new file, but it's not removed, it's just
+            // not a delta anymore.
+            //
+            // For that reason, we also pass in the "base" API in these cases, and when
+            // an item is removed, we also check the full API to see if it's present
+            // there, and if so, this item is not actually deleted.
+            val baseItem = findBaseItem(item)
+            if (baseItem != null && ApiPredicate(ignoreShown = true).test(baseItem)) {
+                return
+            }
+        }
+
         report(error, item, "Removed ${if (item.deprecated) "deprecated " else ""}${describe(item)}")
     }
 
+    private fun findBaseItem(
+        item: Item
+    ): Item? {
+        base ?: return null
+
+        return when (item) {
+            is PackageItem -> base.findPackage(item.qualifiedName())
+            is ClassItem -> base.findClass(item.qualifiedName())
+            is MethodItem -> base.findClass(item.containingClass().qualifiedName())?.findMethod(
+                item,
+                true,
+                true
+            )
+            is FieldItem -> base.findClass(item.containingClass().qualifiedName())?.findField(item.name())
+            else -> null
+        }
+    }
+
     override fun added(new: PackageItem) {
         handleAdded(Errors.ADDED_PACKAGE, new)
     }
@@ -534,12 +703,34 @@
         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)
     }
 
     override fun added(new: MethodItem) {
+        // In old signature files, methods inherited from hidden super classes
+        // are not included. An example of this is StringBuilder.setLength.
+        // More details about this are listed in Compatibility.skipInheritedMethods.
+        // We may see these in the codebase but not in the (old) signature files,
+        // so skip these -- they're not really "added".
+        if (new.inheritedFrom != null && comparingWithPartialSignatures) {
+            return
+        }
+
         // *Overriding* methods from super classes that are outside the
         // API is OK (e.g. overriding toString() from java.lang.Object)
         val superMethods = new.superMethods()
@@ -562,13 +753,25 @@
                 includeInterfaces = false
             )
         }
-        if (inherited != null && !inherited.modifiers.isAbstract()) {
+        if (inherited == null || !inherited.modifiers.isAbstract()) {
             val error = if (new.modifiers.isAbstract()) Errors.ADDED_ABSTRACT_METHOD else Errors.ADDED_METHOD
             handleAdded(error, new)
         }
     }
 
     override fun added(new: FieldItem) {
+        val codebase = new.codebase
+        if (new.inheritedFrom != null &&
+            // In old signature files, methods inherited from hidden super classes
+            // are not included. An example of this is StringBuilder.setLength.
+            // More details about this are listed in Compatibility.skipInheritedMethods.
+            // We may see these in the codebase but not in the (old) signature files,
+            // so skip these -- they're not really "added".
+            (codebase is TextCodebase && codebase.format.major < 2)
+        ) {
+            return
+        }
+
         handleAdded(Errors.ADDED_FIELD, new)
     }
 
@@ -582,6 +785,7 @@
             old.deprecated -> Errors.REMOVED_DEPRECATED_CLASS
             else -> Errors.REMOVED_CLASS
         }
+
         handleRemoved(error, old)
     }
 
@@ -614,136 +818,40 @@
         }
     }
 
-    private fun describe(item: Item, capitalize: Boolean = false): String {
-        return when (item) {
-            is PackageItem -> describe(item, capitalize = capitalize)
-            is ClassItem -> describe(item, capitalize = capitalize)
-            is FieldItem -> describe(item, capitalize = capitalize)
-            is MethodItem -> describe(
-                item,
-                includeParameterNames = false,
-                includeParameterTypes = true,
-                capitalize = capitalize
-            )
-            is ParameterItem -> describe(
-                item,
-                includeParameterNames = true,
-                includeParameterTypes = true,
-                capitalize = capitalize
-            )
-            else -> item.toString()
-        }
-    }
-
-    private fun describe(
-        item: MethodItem,
-        includeParameterNames: Boolean = false,
-        includeParameterTypes: Boolean = false,
-        includeReturnValue: Boolean = false,
-        capitalize: Boolean = false
-    ): String {
-        val builder = StringBuilder()
-        if (item.isConstructor()) {
-            builder.append(if (capitalize) "Constructor" else "constructor")
-        } else {
-            builder.append(if (capitalize) "Method" else "method")
-        }
-        builder.append(' ')
-        if (includeReturnValue && !item.isConstructor()) {
-            builder.append(item.returnType()?.toSimpleType())
-            builder.append(' ')
-        }
-        appendMethodSignature(builder, item, includeParameterNames, includeParameterTypes)
-        return builder.toString()
-    }
-
-    private fun describe(
-        item: ParameterItem,
-        includeParameterNames: Boolean = false,
-        includeParameterTypes: Boolean = false,
-        capitalize: Boolean = false
-    ): String {
-        val builder = StringBuilder()
-        builder.append(if (capitalize) "Parameter" else "parameter")
-        builder.append(' ')
-        builder.append(item.name())
-        builder.append(" in ")
-        val method = item.containingMethod()
-        appendMethodSignature(builder, method, includeParameterNames, includeParameterTypes)
-        return builder.toString()
-    }
-
-    private fun appendMethodSignature(
-        builder: StringBuilder,
-        item: MethodItem,
-        includeParameterNames: Boolean,
-        includeParameterTypes: Boolean
-    ) {
-        builder.append(item.containingClass().qualifiedName())
-        if (!item.isConstructor()) {
-            builder.append('.')
-            builder.append(item.name())
-        }
-        if (includeParameterNames || includeParameterTypes) {
-            builder.append('(')
-            var first = true
-            for (parameter in item.parameters()) {
-                if (first) {
-                    first = false
-                } else {
-                    builder.append(',')
-                    if (includeParameterNames && includeParameterTypes) {
-                        builder.append(' ')
-                    }
-                }
-                if (includeParameterTypes) {
-                    builder.append(parameter.type().toSimpleType())
-                    if (includeParameterNames) {
-                        builder.append(' ')
-                    }
-                }
-                if (includeParameterNames) {
-                    builder.append(parameter.publicName() ?: parameter.name())
-                }
-            }
-            builder.append(')')
-        }
-    }
-
-    private fun describe(item: FieldItem, capitalize: Boolean = false): String {
-        return if (item.isEnumConstant()) {
-            "${if (capitalize) "Enum" else "enum"} constant ${item.containingClass().qualifiedName()}.${item.name()}"
-        } else {
-            "${if (capitalize) "Field" else "field"} ${item.containingClass().qualifiedName()}.${item.name()}"
-        }
-    }
-
-    private fun describe(item: ClassItem, capitalize: Boolean = false): String {
-        return "${if (capitalize) "Class" else "class"} ${item.qualifiedName()}"
-    }
-
-    private fun describe(item: PackageItem, capitalize: Boolean = false): String {
-        return "${if (capitalize) "Package" else "package"} ${item.qualifiedName()}"
-    }
-
     private fun report(
         error: Error,
         item: Item,
         message: String
     ) {
-        reporter.report(error, item, message)
-        foundProblems = true
+        if (reporter.report(error, item, message) && configuration.getSeverity(error) == Severity.ERROR) {
+            foundProblems = true
+        }
     }
 
     companion object {
-        fun checkCompatibility(codebase: Codebase, previous: Codebase) {
-            val checker = CompatibilityCheck()
-            CodebaseComparator().compare(checker, previous, codebase, ApiPredicate(codebase))
+        fun checkCompatibility(
+            codebase: Codebase,
+            previous: Codebase,
+            releaseType: ReleaseType,
+            apiType: ApiType,
+            base: Codebase? = null
+        ) {
+            val filter = apiType.getEmitFilter()
+            val checker = CompatibilityCheck(filter, previous, apiType, base)
+            val errorConfiguration = releaseType.getErrorConfiguration()
+            val previousConfiguration = configuration
+            try {
+                configuration = errorConfiguration
+                CodebaseComparator().compare(checker, previous, codebase, filter)
+            } finally {
+                configuration = previousConfiguration
+            }
+
+            val message = "Aborting: Found compatibility problems checking " +
+                "the ${apiType.displayName} API against the API in ${previous.location}"
+
             if (checker.foundProblems) {
-                throw DriverException(
-                    exitCode = -1,
-                    stderr = "Aborting: Found compatibility problems with --check-compatibility"
-                )
+                throw DriverException(exitCode = -1, stderr = message)
             }
         }
     }
diff --git a/src/main/java/com/android/tools/metalava/Constants.kt b/src/main/java/com/android/tools/metalava/Constants.kt
index d863b9b..4e0a3c7 100644
--- a/src/main/java/com/android/tools/metalava/Constants.kt
+++ b/src/main/java/com/android/tools/metalava/Constants.kt
@@ -20,11 +20,20 @@
 const val JAVA_LANG_OBJECT = "java.lang.Object"
 const val JAVA_LANG_STRING = "java.lang.String"
 const val JAVA_LANG_ENUM = "java.lang.Enum"
+const val JAVA_LANG_THROWABLE = "java.lang.Throwable"
 const val JAVA_LANG_ANNOTATION = "java.lang.annotation.Annotation"
+const val JAVA_LANG_DEPRECATED = "java.lang.Deprecated"
 const val ANDROID_SUPPORT_ANNOTATION_PREFIX = "android.support.annotation."
 const val ANDROID_ANNOTATION_PREFIX = "android.annotation."
 const val ANDROIDX_ANNOTATION_PREFIX = "androidx.annotation."
-const val ANDROIDX_NOTNULL = "androidx.annotation.NonNull"
+const val ANDROIDX_NONNULL = "androidx.annotation.NonNull"
 const val ANDROIDX_NULLABLE = "androidx.annotation.Nullable"
+const val ANDROID_SYSTEM_API = "android.annotation.SystemApi"
+const val ANDROID_REQUIRES_PERMISSION = "android.annotation.RequiresPermission"
 const val RECENTLY_NULLABLE = "androidx.annotation.RecentlyNullable"
-const val RECENTLY_NONNULL = "androidx.annotation.RecentlyNonNull"
\ No newline at end of file
+const val RECENTLY_NONNULL = "androidx.annotation.RecentlyNonNull"
+
+const val ENV_VAR_METALAVA_TESTS_RUNNING = "METALAVA_TESTS_RUNNING"
+const val ENV_VAR_METALAVA_DUMP_ARGV = "METALAVA_DUMP_ARGV"
+const val ENV_VAR_METALAVA_PREPEND_ARGS = "METALAVA_PREPEND_ARGS"
+const val ENV_VAR_METALAVA_APPEND_ARGS = "METALAVA_APPEND_ARGS"
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/ConvertJarsToSignatureFiles.kt b/src/main/java/com/android/tools/metalava/ConvertJarsToSignatureFiles.kt
new file mode 100644
index 0000000..5625f88
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/ConvertJarsToSignatureFiles.kt
@@ -0,0 +1,211 @@
+/*
+ * 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
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.PackageItem
+import com.google.common.io.ByteStreams
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.FieldNode
+import org.objectweb.asm.tree.MethodNode
+import java.io.File
+import java.io.IOException
+import java.util.function.Predicate
+import java.util.zip.ZipFile
+
+/**
+ * In an Android source tree, rewrite the signature files in prebuilts/sdk
+ * by reading what's actually there in the android.jar files.
+ */
+class ConvertJarsToSignatureFiles {
+    fun convertJars(root: File) {
+        var api = 1
+        while (true) {
+            val apiJar = File(
+                root,
+                if (api <= 3)
+                    "prebuilts/tools/common/api-versions/android-$api/android.jar"
+                else
+                    "prebuilts/sdk/$api/public/android.jar"
+            )
+            if (!apiJar.isFile) {
+                break
+            }
+            val signatureFile = "prebuilts/sdk/$api/public/api/android.txt"
+            val oldApiFile = File(root, "prebuilts/sdk/$api/public/api/android.txt")
+            val newApiFile =
+                // Place new-style signature files in separate files?
+                // File(root, "prebuilts/sdk/$api/public/api/android.${if (options.compatOutput) "txt" else "v2.txt"}")
+                File(root, "prebuilts/sdk/$api/public/api/android.txt")
+
+            progress("\nWriting signature files $signatureFile for $apiJar")
+
+            // Treat android.jar file as not filtered since they contain misc stuff that shouldn't be
+            // there: package private super classes etc.
+            val jarCodebase = loadFromJarFile(apiJar, null, preFiltered = false)
+            val apiEmit = ApiType.PUBLIC_API.getEmitFilter()
+            val apiReference = ApiType.PUBLIC_API.getReferenceFilter()
+
+            // Sadly the old signature files have some APIs recorded as deprecated which
+            // are not in fact deprecated in the jar files. Try to pull this back in.
+
+            val oldRemovedFile = File(root, "prebuilts/sdk/$api/public/api/removed.txt")
+            if (oldRemovedFile.isFile) {
+                val oldCodebase = SignatureFileLoader.load(oldRemovedFile)
+                val visitor = object : ComparisonVisitor() {
+                    override fun compare(old: MethodItem, new: MethodItem) {
+                        new.removed = true
+                        progress("\nRemoved $old")
+                    }
+
+                    override fun compare(old: FieldItem, new: FieldItem) {
+                        new.removed = true
+                        progress("\nRemoved $old")
+                    }
+                }
+                CodebaseComparator().compare(visitor, oldCodebase, jarCodebase, null)
+            }
+
+            // Read deprecated attributes. Seem to be missing from code model;
+            // try to read via ASM instead since it must clearly be there.
+            markDeprecated(jarCodebase, apiJar, apiJar.path)
+
+            // ASM doesn't seem to pick up everything that's actually there according to
+            // javap. So as another fallback, read from the existing signature files:
+            if (oldApiFile.isFile) {
+                val oldCodebase = SignatureFileLoader.load(oldApiFile)
+                val visitor = object : ComparisonVisitor() {
+                    override fun compare(old: Item, new: Item) {
+                        if (old.deprecated && !new.deprecated && old !is PackageItem) {
+                            new.deprecated = true
+                            progress("\nRecorded deprecation from previous signature file for $old")
+                        }
+                    }
+                }
+                CodebaseComparator().compare(visitor, oldCodebase, jarCodebase, null)
+            }
+
+            createReportFile(jarCodebase, newApiFile, "API") { printWriter ->
+                SignatureWriter(printWriter, apiEmit, apiReference, jarCodebase.preFiltered)
+            }
+
+            // Delete older redundant .xml files
+            val xmlFile = File(newApiFile.parentFile, "android.xml")
+            if (xmlFile.isFile) {
+                xmlFile.delete()
+            }
+
+            api++
+        }
+    }
+
+    private fun markDeprecated(codebase: Codebase, file: File, path: String) {
+        when {
+            file.name.endsWith(SdkConstants.DOT_JAR) -> try {
+                ZipFile(file).use { jar ->
+                    val enumeration = jar.entries()
+                    while (enumeration.hasMoreElements()) {
+                        val entry = enumeration.nextElement()
+                        if (entry.name.endsWith(SdkConstants.DOT_CLASS)) {
+                            try {
+                                jar.getInputStream(entry).use { `is` ->
+                                    val bytes = ByteStreams.toByteArray(`is`)
+                                    if (bytes != null) {
+                                        markDeprecated(codebase, bytes, path + ":" + entry.name)
+                                    }
+                                }
+                            } catch (e: Exception) {
+                                options.stdout.println("Could not read jar file entry ${entry.name} from $file: $e")
+                            }
+                        }
+                    }
+                }
+            } catch (e: IOException) {
+                options.stdout.println("Could not read jar file contents from $file: $e")
+            }
+            file.isDirectory -> {
+                val listFiles = file.listFiles()
+                listFiles?.forEach {
+                    markDeprecated(codebase, it, it.path)
+                }
+            }
+            file.path.endsWith(SdkConstants.DOT_CLASS) -> {
+                val bytes = file.readBytes()
+                markDeprecated(codebase, bytes, file.path)
+            }
+            else -> options.stdout.println("Ignoring entry $file")
+        }
+    }
+
+    private fun markDeprecated(codebase: Codebase, bytes: ByteArray, path: String) {
+        val reader: ClassReader
+        val classNode: ClassNode
+        try {
+            // TODO: We don't actually need to build a DOM.
+            reader = ClassReader(bytes)
+            classNode = ClassNode()
+            reader.accept(classNode, 0)
+        } catch (t: Throwable) {
+            options.stderr.println("Error processing $path: broken class file?")
+            return
+        }
+
+        if ((classNode.access and Opcodes.ACC_DEPRECATED) != 0) {
+            val item = codebase.findClass(classNode, MATCH_ALL)
+            if (item != null && !item.deprecated) {
+                item.deprecated = true
+                progress("\nTurned deprecation on for $item")
+            }
+        }
+
+        val methodList = classNode.methods
+        for (f in methodList) {
+            val methodNode = f as MethodNode
+            if ((methodNode.access and Opcodes.ACC_DEPRECATED) == 0) {
+                continue
+            }
+            val item = codebase.findMethod(classNode, methodNode, MATCH_ALL)
+            if (item != null && !item.deprecated) {
+                item.deprecated = true
+                progress("\nTurned deprecation on for $item")
+            }
+        }
+
+        val fieldList = classNode.fields
+        for (f in fieldList) {
+            val fieldNode = f as FieldNode
+            if ((fieldNode.access and Opcodes.ACC_DEPRECATED) == 0) {
+                continue
+            }
+            val item = codebase.findField(classNode, fieldNode, MATCH_ALL)
+            if (item != null && !item.deprecated) {
+                item.deprecated = true
+                progress("\nTurned deprecation on for $item")
+            }
+        }
+    }
+
+    companion object {
+        val MATCH_ALL: Predicate<Item> = Predicate { true }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/DexApiWriter.kt b/src/main/java/com/android/tools/metalava/DexApiWriter.kt
index 27952a5..b779a03 100644
--- a/src/main/java/com/android/tools/metalava/DexApiWriter.kt
+++ b/src/main/java/com/android/tools/metalava/DexApiWriter.kt
@@ -28,7 +28,9 @@
     private val writer: PrintWriter,
     filterEmit: Predicate<Item>,
     filterReference: Predicate<Item>,
-    inlineInheritedFields: Boolean = true
+    inlineInheritedFields: Boolean = true,
+    private val membersOnly: Boolean = false,
+    private val includePositions: Boolean = false
 ) : ApiVisitor(
     visitConstructorsAsMethods = true,
     nestInnerClasses = false,
@@ -37,10 +39,17 @@
     filterReference = filterReference
 ) {
     override fun visitClass(cls: ClassItem) {
+        if (membersOnly) {
+            return
+        }
+
         if (filterEmit.test(cls)) {
             writer.print(cls.toType().internalName())
             writer.print("\n")
         }
+        if (includePositions) {
+            writeLocation(cls)
+        }
     }
 
     override fun visitMethod(method: MethodItem) {
@@ -63,6 +72,9 @@
             writer.print(returnType?.internalName() ?: "V")
         }
         writer.print("\n")
+        if (includePositions) {
+            writeLocation(method)
+        }
     }
 
     override fun visitField(field: FieldItem) {
@@ -74,5 +86,15 @@
         writer.print(":")
         writer.print(field.type().internalName())
         writer.print("\n")
+        if (includePositions) {
+            writeLocation(field)
+        }
+    }
+
+    private fun writeLocation(item: Item) {
+        val psiItem =
+            item.psi() ?: throw DriverException(stderr = "$ARG_DEX_API_MAPPING should only be used on source trees")
+        val location = reporter.elementToLocation(psiItem, false)
+        writer.println(location ?: "<unknown>:-1")
     }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
index f040027..afecae5 100644
--- a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
@@ -1,5 +1,6 @@
 package com.android.tools.metalava
 
+import com.android.SdkConstants.ATTR_VALUE
 import com.android.sdklib.SdkVersionInfo
 import com.android.sdklib.repository.AndroidSdkHandler
 import com.android.tools.lint.LintCliClient
@@ -15,8 +16,10 @@
 import com.android.tools.metalava.model.MemberItem
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.psi.containsLinkTags
 import com.android.tools.metalava.model.visitors.ApiVisitor
 import com.android.tools.metalava.model.visitors.VisibleItemVisitor
+import com.google.common.io.Files
 import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiField
 import com.intellij.psi.PsiMethod
@@ -25,6 +28,13 @@
 import java.util.regex.Pattern
 
 /**
+ * Whether to include textual descriptions of the API requirements instead
+ * of just inserting a since-tag. This should be off if there is post-processing
+ * to convert since tags in the documentation tool used.
+ */
+const val ADD_API_LEVEL_TEXT = false
+
+/**
  * Walk over the API and apply tweaks to the documentation, such as
  *     - Looking for annotations and converting them to auxiliary tags
  *       that will be processed by the documentation tools later.
@@ -89,7 +99,7 @@
         // set also requires updating framework source code, so this doesn't seem
         // like an unreasonable burden.
 
-        codebase.accept(object : ApiVisitor(codebase) {
+        codebase.accept(object : ApiVisitor() {
             override fun visitItem(item: Item) {
                 val annotations = item.modifiers.annotations()
                 if (annotations.isEmpty()) {
@@ -172,9 +182,59 @@
                     return
                 }
 
+                if (item is ClassItem && name == item.qualifiedName()) {
+                    // The annotation annotates itself; we shouldn't attempt to recursively
+                    // pull in documentation from it; the documentation is already complete.
+                    return
+                }
+
                 // Some annotations include the documentation they want inlined into usage docs.
                 // Copy those here:
 
+                handleInliningDocs(annotation, item)
+
+                when (name) {
+                    "androidx.annotation.RequiresPermission" -> handleRequiresPermission(annotation, item)
+                    "androidx.annotation.IntRange",
+                    "androidx.annotation.FloatRange" -> handleRange(annotation, item)
+                    "androidx.annotation.IntDef",
+                    "androidx.annotation.LongDef",
+                    "androidx.annotation.StringDef" -> handleTypeDef(annotation, item)
+                    "android.annotation.RequiresFeature" -> handleRequiresFeature(annotation, item)
+                    "androidx.annotation.RequiresApi" -> handleRequiresApi(annotation, item)
+                    "kotlin.Deprecated" -> handleKotlinDeprecation(annotation, item)
+                }
+
+                // Thread annotations are ignored here because they're handled as a group afterwards
+
+                // TODO: Resource type annotations
+
+                // Handle inner annotations
+                annotation.resolve()?.modifiers?.annotations()?.forEach { nested ->
+                    if (depth == 20) { // Temp debugging
+                        throw StackOverflowError(
+                            "Unbounded recursion, processing annotation " +
+                                "${annotation.toSource()} in $item in ${item.compilationUnit()} "
+                        )
+                    } else if (nested.qualifiedName() != annotation.qualifiedName()) {
+                        handleAnnotation(nested, item, depth + 1)
+                    }
+                }
+            }
+
+            private fun handleKotlinDeprecation(annotation: AnnotationItem, item: Item) {
+                val text = annotation.findAttribute(ATTR_VALUE)?.value?.value()?.toString() ?: return
+                if (text.isBlank() || item.documentation.contains(text)) {
+                    return
+                }
+
+                item.appendDocumentation(text, "@deprecated")
+            }
+
+            private fun handleInliningDocs(
+                annotation: AnnotationItem,
+                item: Item
+            ) {
                 if (annotation.isNullable() || annotation.isNonNull()) {
                     // Some docs already specifically talk about null policy; in that case,
                     // don't include the docs (since it may conflict with more specific conditions
@@ -201,210 +261,209 @@
                         addDoc(annotation, "classDoc", item)
                     }
                 }
+            }
 
-                // Document required permissions
-                if (item is MemberItem && name == "androidx.annotation.RequiresPermission") {
-                    var values: List<AnnotationAttributeValue>? = null
-                    var any = false
-                    var conditional = false
-                    for (attribute in annotation.attributes()) {
-                        when (attribute.name) {
-                            "value", "allOf" -> {
-                                values = attribute.leafValues()
-                            }
-                            "anyOf" -> {
-                                any = true
-                                values = attribute.leafValues()
-                            }
-                            "conditional" -> {
-                                conditional = attribute.value.value() == true
-                            }
+            private fun handleRequiresPermission(
+                annotation: AnnotationItem,
+                item: Item
+            ) {
+                if (item !is MemberItem) {
+                    return
+                }
+                var values: List<AnnotationAttributeValue>? = null
+                var any = false
+                var conditional = false
+                for (attribute in annotation.attributes()) {
+                    when (attribute.name) {
+                        "value", "allOf" -> {
+                            values = attribute.leafValues()
                         }
-                    }
-
-                    if (values != null && values.isNotEmpty() && !conditional) {
-                        // Look at macros_override.cs for the usage of these
-                        // tags. In particular, search for def:dump_permission
-
-                        val sb = StringBuilder(100)
-                        sb.append("Requires ")
-                        var first = true
-                        for (value in values) {
-                            when {
-                                first -> first = false
-                                any -> sb.append(" or ")
-                                else -> sb.append(" and ")
-                            }
-
-                            val resolved = value.resolve()
-                            val field = if (resolved is FieldItem)
-                                resolved
-                            else {
-                                val v: Any = value.value() ?: value.toSource()
-                                findPermissionField(codebase, v)
-                            }
-                            if (field == null) {
-                                reporter.report(
-                                    Errors.MISSING_PERMISSION, item,
-                                    "Cannot find permission field for $value required by $item (may be hidden or removed)"
-                                )
-                                sb.append(value.toSource())
-                            } else {
-                                if (field.isHiddenOrRemoved()) {
-                                    reporter.report(
-                                        Errors.MISSING_PERMISSION, item,
-                                        "Permission $value required by $item is hidden or removed"
-                                    )
-                                }
-                                sb.append("{@link ${field.containingClass().qualifiedName()}#${field.name()}}")
-                            }
+                        "anyOf" -> {
+                            any = true
+                            values = attribute.leafValues()
                         }
-
-                        appendDocumentation(sb.toString(), item, false)
+                        "conditional" -> {
+                            conditional = attribute.value.value() == true
+                        }
                     }
                 }
 
-                // Document value ranges
-                if (name == "androidx.annotation.IntRange" || name == "androidx.annotation.FloatRange") {
-                    val from: String? = annotation.findAttribute("from")?.value?.toSource()
-                    val to: String? = annotation.findAttribute("to")?.value?.toSource()
-                    // TODO: inclusive/exclusive attributes on FloatRange!
-                    if (from != null || to != null) {
-                        val args = HashMap<String, String>()
-                        if (from != null) args["from"] = from
-                        if (from != null) args["from"] = from
-                        if (to != null) args["to"] = to
-                        val doc = if (from != null && to != null) {
-                            "Value is between $from and $to inclusive"
-                        } else if (from != null) {
-                            "Value is $from or greater"
-                        } else if (to != null) {
-                            "Value is $to or less"
-                        } else {
-                            null
-                        }
-                        appendDocumentation(doc, item, true)
-                    }
-                }
-
-                // Document expected constants
-                if (name == "androidx.annotation.IntDef" || name == "androidx.annotation.LongDef" ||
-                    name == "androidx.annotation.StringDef"
-                ) {
-                    val values = annotation.findAttribute("value")?.leafValues() ?: return
-                    val flag = annotation.findAttribute("flag")?.value?.toSource() == "true"
-
+                if (values != null && values.isNotEmpty() && !conditional) {
                     // Look at macros_override.cs for the usage of these
-                    // tags. In particular, search for def:dump_int_def
+                    // tags. In particular, search for def:dump_permission
 
                     val sb = StringBuilder(100)
-                    sb.append("Value is ")
-                    if (flag) {
-                        sb.append("either <code>0</code> or ")
-                        if (values.size > 1) {
-                            sb.append("a combination of ")
+                    sb.append("Requires ")
+                    var first = true
+                    for (value in values) {
+                        when {
+                            first -> first = false
+                            any -> sb.append(" or ")
+                            else -> sb.append(" and ")
                         }
-                    }
 
-                    values.forEachIndexed { index, value ->
-                        sb.append(
-                            when (index) {
-                                0 -> {
-                                    ""
-                                }
-                                values.size - 1 -> {
-                                    if (flag) {
-                                        ", and "
-                                    } else {
-                                        ", or "
-                                    }
-                                }
-                                else -> {
-                                    ", "
-                                }
-                            }
-                        )
-
-                        val field = value.resolve()
-                        if (field is FieldItem)
+                        val resolved = value.resolve()
+                        val field = if (resolved is FieldItem)
+                            resolved
+                        else {
+                            val v: Any = value.value() ?: value.toSource()
+                            findPermissionField(codebase, v)
+                        }
+                        if (field == null) {
+                            reporter.report(
+                                Errors.MISSING_PERMISSION, item,
+                                "Cannot find permission field for $value required by $item (may be hidden or removed)"
+                            )
+                            sb.append(value.toSource())
+                        } else {
                             if (filterReference.test(field)) {
                                 sb.append("{@link ${field.containingClass().qualifiedName()}#${field.name()}}")
                             } else {
-                                // Typdef annotation references field which isn't part of the API: don't
-                                // try to link to it.
                                 reporter.report(
-                                    Errors.MISSING_TYPEDEF_CONSTANT, item,
-                                    "Typedef references constant which isn't part of the API, skipping in documentation: " +
-                                        "${field.containingClass().qualifiedName()}#${field.name()}"
+                                    Errors.MISSING_PERMISSION, item,
+                                    "Permission $value required by $item is hidden or removed"
                                 )
-                                sb.append(field.containingClass().qualifiedName() + "." + field.name())
+                                sb.append("${field.containingClass().qualifiedName()}.${field.name()}")
                             }
-                        else {
-                            sb.append(value.toSource())
                         }
                     }
-                    appendDocumentation(sb.toString(), item, true)
-                }
 
-                // Required Features
-                if (name == "android.annotation.RequiresFeature") {
-                    val value = annotation.findAttribute("value")?.leafValues()?.firstOrNull() ?: return
-                    val sb = StringBuilder(100)
-                    val resolved = value.resolve()
-                    val field = resolved as? FieldItem
-                    sb.append("Requires the ")
-                    if (field == null) {
-                        reporter.report(
-                            Errors.MISSING_PERMISSION, item,
-                            "Cannot find feature field for $value required by $item (may be hidden or removed)"
-                        )
-                        sb.append("{@link ${value.toSource()}}")
-                    } else {
-                        if (field.isHiddenOrRemoved()) {
-                            reporter.report(
-                                Errors.MISSING_PERMISSION, item,
-                                "Feature field $value required by $item is hidden or removed"
-                            )
-                        }
-
-                        sb.append("{@link ${field.containingClass().qualifiedName()}#${field.name()} ${field.containingClass().simpleName()}#${field.name()}} ")
-                    }
-
-                    sb.append("feature which can be detected using ")
-                    sb.append("{@link android.content.pm.PackageManager#hasSystemFeature(String) ")
-                    sb.append("PackageManager.hasSystemFeature(String)}.")
                     appendDocumentation(sb.toString(), item, false)
                 }
+            }
 
-                // Required API levels
-                if (name == "androidx.annotation.RequiresApi") {
-                    val level = run {
-                        val api = annotation.findAttribute("api")?.leafValues()?.firstOrNull()?.value()
-                        if (api == null || api == 1) {
-                            annotation.findAttribute("value")?.leafValues()?.firstOrNull()?.value() ?: return
-                        } else {
-                            api
-                        }
+            private fun handleRange(
+                annotation: AnnotationItem,
+                item: Item
+            ) {
+                val from: String? = annotation.findAttribute("from")?.value?.toSource()
+                val to: String? = annotation.findAttribute("to")?.value?.toSource()
+                // TODO: inclusive/exclusive attributes on FloatRange!
+                if (from != null || to != null) {
+                    val args = HashMap<String, String>()
+                    if (from != null) args["from"] = from
+                    if (from != null) args["from"] = from
+                    if (to != null) args["to"] = to
+                    val doc = if (from != null && to != null) {
+                        "Value is between $from and $to inclusive"
+                    } else if (from != null) {
+                        "Value is $from or greater"
+                    } else if (to != null) {
+                        "Value is $to or less"
+                    } else {
+                        null
                     }
+                    appendDocumentation(doc, item, true)
+                }
+            }
 
-                    if (level is Int) {
-                        addApiLevelDocumentation(level, item)
+            private fun handleTypeDef(
+                annotation: AnnotationItem,
+                item: Item
+            ) {
+                val values = annotation.findAttribute("value")?.leafValues() ?: return
+                val flag = annotation.findAttribute("flag")?.value?.toSource() == "true"
+
+                // Look at macros_override.cs for the usage of these
+                // tags. In particular, search for def:dump_int_def
+
+                val sb = StringBuilder(100)
+                sb.append("Value is ")
+                if (flag) {
+                    sb.append("either <code>0</code> or ")
+                    if (values.size > 1) {
+                        sb.append("a combination of ")
                     }
                 }
 
-                // Thread annotations are ignored here because they're handled as a group afterwards
+                values.forEachIndexed { index, value ->
+                    sb.append(
+                        when (index) {
+                            0 -> {
+                                ""
+                            }
+                            values.size - 1 -> {
+                                if (flag) {
+                                    ", and "
+                                } else {
+                                    ", or "
+                                }
+                            }
+                            else -> {
+                                ", "
+                            }
+                        }
+                    )
 
-                // TODO: Resource type annotations
-
-                // Handle inner annotations
-                annotation.resolve()?.modifiers?.annotations()?.forEach { nested ->
-                    if (depth == 20) { // Temp debugging
-                        throw StackOverflowError(
-                            "Unbounded recursion, processing annotation " +
-                                "${annotation.toSource()} in $item in ${item.compilationUnit()} "
-                        )
+                    val field = value.resolve()
+                    if (field is FieldItem)
+                        if (filterReference.test(field)) {
+                            sb.append("{@link ${field.containingClass().qualifiedName()}#${field.name()}}")
+                        } else {
+                            // Typdef annotation references field which isn't part of the API: don't
+                            // try to link to it.
+                            reporter.report(
+                                Errors.HIDDEN_TYPEDEF_CONSTANT, item,
+                                "Typedef references constant which isn't part of the API, skipping in documentation: " +
+                                    "${field.containingClass().qualifiedName()}#${field.name()}"
+                            )
+                            sb.append(field.containingClass().qualifiedName() + "." + field.name())
+                        }
+                    else {
+                        sb.append(value.toSource())
                     }
-                    handleAnnotation(nested, item, depth + 1)
+                }
+                appendDocumentation(sb.toString(), item, true)
+            }
+
+            private fun handleRequiresFeature(
+                annotation: AnnotationItem,
+                item: Item
+            ) {
+                val value = annotation.findAttribute("value")?.leafValues()?.firstOrNull() ?: return
+                val sb = StringBuilder(100)
+                val resolved = value.resolve()
+                val field = resolved as? FieldItem
+                sb.append("Requires the ")
+                if (field == null) {
+                    reporter.report(
+                        Errors.MISSING_PERMISSION, item,
+                        "Cannot find feature field for $value required by $item (may be hidden or removed)"
+                    )
+                    sb.append("{@link ${value.toSource()}}")
+                } else {
+                    if (filterReference.test(field)) {
+                        sb.append("{@link ${field.containingClass().qualifiedName()}#${field.name()} ${field.containingClass().simpleName()}#${field.name()}} ")
+                    } else {
+                        reporter.report(
+                            Errors.MISSING_PERMISSION, item,
+                            "Feature field $value required by $item is hidden or removed"
+                        )
+                        sb.append("${field.containingClass().simpleName()}#${field.name()} ")
+                    }
+                }
+
+                sb.append("feature which can be detected using ")
+                sb.append("{@link android.content.pm.PackageManager#hasSystemFeature(String) ")
+                sb.append("PackageManager.hasSystemFeature(String)}.")
+                appendDocumentation(sb.toString(), item, false)
+            }
+
+            private fun handleRequiresApi(
+                annotation: AnnotationItem,
+                item: Item
+            ) {
+                val level = run {
+                    val api = annotation.findAttribute("api")?.leafValues()?.firstOrNull()?.value()
+                    if (api == null || api == 1) {
+                        annotation.findAttribute("value")?.leafValues()?.firstOrNull()?.value() ?: return
+                    } else {
+                        api
+                    }
+                }
+
+                if (level is Int) {
+                    addApiLevelDocumentation(level, item)
                 }
             }
         })
@@ -448,11 +507,48 @@
                 documentation.startsWith("@memberDoc") -> null
                 else -> null
             }
-            val insert = stripMetaTags(documentation.substring(tag.length + 2))
-            item.appendDocumentation(insert, section) // 2: @ and space after tag
+
+            val insert = stripLeadingAsterisks(stripMetaTags(documentation.substring(tag.length + 2)))
+            val qualified = if (containsLinkTags(insert)) {
+                val original = "/** $insert */"
+                val qualified = cls.fullyQualifiedDocumentation(original)
+                if (original != qualified) {
+                    qualified.substring(if (qualified[3] == ' ') 4 else 3, qualified.length - 2)
+                } else {
+                    original
+                }
+            } else {
+                insert
+            }
+
+            item.appendDocumentation(qualified, section) // 2: @ and space after tag
         }
     }
 
+    private fun stripLeadingAsterisks(s: String): String {
+        if (s.contains("*")) {
+            val sb = StringBuilder(s.length)
+            var strip = true
+            for (c in s) {
+                if (strip) {
+                    if (c.isWhitespace() || c == '*') {
+                        continue
+                    } else {
+                        strip = false
+                    }
+                } else {
+                    if (c == '\n') {
+                        strip = true
+                    }
+                }
+                sb.append(c)
+            }
+            return sb.toString()
+        }
+
+        return s
+    }
+
     private fun stripMetaTags(string: String): String {
         // Get rid of @hide and @remove tags etc that are part of documentation snippets
         // we pull in, such that we don't accidentally start applying this to the
@@ -465,15 +561,21 @@
 
     /** Replacements to perform in documentation */
     val typos = mapOf(
+        "JetPack" to "Jetpack",
         "Andriod" to "Android",
         "Kitkat" to "KitKat",
         "LemonMeringuePie" to "Lollipop",
         "LMP" to "Lollipop",
         "KeyLimePie" to "KitKat",
-        "KLP" to "KitKat"
+        "KLP" to "KitKat",
+        "teh" to "the"
     )
 
     private fun tweakGrammar() {
+        if (reporter.isSuppressed(Errors.TYPO)) {
+            return
+        }
+
         codebase.accept(object : VisibleItemVisitor() {
             override fun visitItem(item: Item) {
                 var doc = item.documentation
@@ -484,13 +586,16 @@
                 for (typo in typos.keys) {
                     if (doc.contains(typo)) {
                         val replacement = typos[typo] ?: continue
-                        reporter.report(
-                            Errors.TYPO,
-                            item,
-                            "Replaced $typo with $replacement in documentation for $item"
-                        )
-                        doc = doc.replace(typo, replacement, false)
-                        item.documentation = doc
+                        val new = doc.replace(Regex("\\b$typo\\b"), replacement)
+                        if (new != doc) {
+                            reporter.report(
+                                Errors.TYPO,
+                                item,
+                                "Replaced $typo with $replacement in the documentation for $item"
+                            )
+                            doc = new
+                            item.documentation = doc
+                        }
                     }
                 }
 
@@ -504,6 +609,7 @@
     }
 
     fun applyApiLevels(applyApiLevelsXml: File) {
+        @Suppress("DEPRECATION") // still using older lint-api when building with soong
         val client = object : LintCliClient() {
             override fun findResource(relativePath: String): File? {
                 if (relativePath == ApiLookup.XML_FILE_PATH) {
@@ -517,8 +623,33 @@
             }
 
             override fun getCacheDir(name: String?, create: Boolean): File? {
-                val dir = File(System.getProperty("java.io.tmpdir"))
-                if (create) {
+                if (create && java.lang.Boolean.getBoolean(ENV_VAR_METALAVA_TESTS_RUNNING)) {
+                    // Pick unique directory during unit tests
+                    return Files.createTempDir()
+                }
+
+                val sb = StringBuilder(PROGRAM_NAME)
+                if (name != null) {
+                    sb.append(File.separator)
+                    sb.append(name)
+                }
+                val relative = sb.toString()
+
+                val tmp = System.getenv("TMPDIR")
+                if (tmp != null) {
+                    // Android Build environment: Make sure we're really creating a unique
+                    // temp directory each time since builds could be running in
+                    // parallel here.
+                    val dir = File(tmp, relative)
+                    if (!dir.isDirectory) {
+                        dir.mkdirs()
+                    }
+
+                    return java.nio.file.Files.createTempDirectory(dir.toPath(), null).toFile()
+                }
+
+                val dir = File(System.getProperty("java.io.tmpdir"), relative)
+                if (create && !dir.isDirectory) {
                     dir.mkdirs()
                 }
                 return dir
@@ -527,7 +658,7 @@
 
         val apiLookup = ApiLookup.get(client)
 
-        codebase.accept(object : ApiVisitor(codebase, visitConstructorsAsMethods = false) {
+        codebase.accept(object : ApiVisitor(visitConstructorsAsMethods = false) {
             override fun visitMethod(method: MethodItem) {
                 val psiMethod = method.psi() as PsiMethod
                 addApiLevelDocumentation(apiLookup.getMethodVersion(psiMethod), method)
@@ -550,13 +681,16 @@
 
     private fun addApiLevelDocumentation(level: Int, item: Item) {
         if (level > 1) {
-            appendDocumentation("Requires API level $level", item, false)
+            @Suppress("ConstantConditionIf")
+            if (ADD_API_LEVEL_TEXT) { // See 113933920: Remove "Requires API level" from method comment
+                appendDocumentation("Requires API level ${describeApiLevel(level)}", item, false)
+            }
             // Also add @since tag, unless already manually entered.
             // TODO: Override it everywhere in case the existing doc is wrong (we know
             // better), and at least for OpenJDK sources we *should* since the since tags
             // are talking about language levels rather than API levels!
             if (!item.documentation.contains("@since")) {
-                item.appendDocumentation(describeApiLevel(level), "@since")
+                item.appendDocumentation(level.toString(), "@since")
             }
         }
     }
@@ -571,7 +705,7 @@
     }
 
     private fun describeApiLevel(level: Int): String {
-        return "${SdkVersionInfo.getVersionString(level)} ${SdkVersionInfo.getCodeName(level)} ($level)"
+        return "$level (Android ${SdkVersionInfo.getVersionString(level)}, ${SdkVersionInfo.getCodeName(level)})"
     }
 }
 
@@ -611,4 +745,4 @@
     val containingClass = field.containingClass ?: return -1
     val owner = containingClass.qualifiedName ?: return -1
     return getFieldDeprecatedIn(owner, field.name)
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt
index ed27efb..df64f7f 100644
--- a/src/main/java/com/android/tools/metalava/Driver.kt
+++ b/src/main/java/com/android/tools/metalava/Driver.kt
@@ -20,35 +20,41 @@
 import com.android.SdkConstants
 import com.android.SdkConstants.DOT_JAVA
 import com.android.SdkConstants.DOT_KT
+import com.android.SdkConstants.DOT_TXT
 import com.android.ide.common.process.CachedProcessOutputHandler
 import com.android.ide.common.process.DefaultProcessExecutor
 import com.android.ide.common.process.ProcessInfoBuilder
+import com.android.ide.common.process.ProcessOutput
+import com.android.ide.common.process.ProcessOutputHandler
 import com.android.tools.lint.KotlinLintAnalyzerFacade
 import com.android.tools.lint.LintCoreApplicationEnvironment
 import com.android.tools.lint.LintCoreProjectEnvironment
 import com.android.tools.lint.annotations.Extractor
 import com.android.tools.lint.checks.infrastructure.ClassName
+import com.android.tools.lint.detector.api.assertionsEnabled
+import com.android.tools.metalava.CompatibilityCheck.CheckRequest
 import com.android.tools.metalava.apilevels.ApiGenerator
-import com.android.tools.metalava.doclava1.ApiFile
-import com.android.tools.metalava.doclava1.ApiParseException
 import com.android.tools.metalava.doclava1.ApiPredicate
-import com.android.tools.metalava.doclava1.ElidingPredicate
 import com.android.tools.metalava.doclava1.Errors
 import com.android.tools.metalava.doclava1.FilterPredicate
 import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.PackageDocs
 import com.android.tools.metalava.model.psi.PsiBasedCodebase
+import com.android.tools.metalava.model.psi.packageHtmlToJavadoc
 import com.android.tools.metalava.model.visitors.ApiVisitor
 import com.android.utils.StdLogger
 import com.android.utils.StdLogger.Level.ERROR
 import com.google.common.base.Stopwatch
 import com.google.common.collect.Lists
 import com.google.common.io.Files
+import com.intellij.openapi.diagnostic.DefaultLogger
 import com.intellij.openapi.roots.LanguageLevelProjectExtension
 import com.intellij.openapi.util.Disposer
+import com.intellij.util.execution.ParametersListUtil
 import java.io.File
 import java.io.IOException
+import java.io.OutputStream
 import java.io.OutputStreamWriter
 import java.io.PrintWriter
 import java.util.concurrent.TimeUnit
@@ -85,8 +91,8 @@
     setExitCode: Boolean = false
 ): Boolean {
 
-    if (System.getenv("METALAVA_DUMP_ARGV") != null &&
-        !java.lang.Boolean.getBoolean("METALAVA_TESTS_RUNNING")
+    if (System.getenv(ENV_VAR_METALAVA_DUMP_ARGV) != null &&
+        !java.lang.Boolean.getBoolean(ENV_VAR_METALAVA_TESTS_RUNNING)
     ) {
         stdout.println("---Running $PROGRAM_NAME----")
         stdout.println("pwd=${File("").absolutePath}")
@@ -101,7 +107,29 @@
             if (args.isEmpty()) {
                 arrayOf("--help")
             } else {
-                args
+                val index = args.indexOf(ARG_GENERATE_DOCUMENTATION)
+                val prepend = envVarToArgs(ENV_VAR_METALAVA_PREPEND_ARGS)
+                val append = envVarToArgs(ENV_VAR_METALAVA_APPEND_ARGS)
+                if (prepend.isEmpty() && append.isEmpty()) {
+                    args
+                } else {
+                    val newArgs =
+                        if (index != -1) {
+                            args.sliceArray(0 until index) + prepend + args.sliceArray(index until args.size) + append
+                        } else {
+                            prepend + args + append
+                        }
+                    if (System.getenv(ENV_VAR_METALAVA_DUMP_ARGV) != null) {
+                        stdout.println("---Modified $PROGRAM_NAME arguments from environment variables ----")
+                        stdout.println("$ENV_VAR_METALAVA_PREPEND_ARGS: ${prepend.joinToString { "\"$it\"" }}")
+                        stdout.println("$ENV_VAR_METALAVA_APPEND_ARGS: ${append.joinToString { "\"$it\"" }}")
+                        newArgs.forEach { arg ->
+                            stdout.println("\"$arg\",")
+                        }
+                        stdout.println("----------------------------")
+                    }
+                    newArgs
+                }
             }
 
         compatibility = Compatibility(compat = Options.useCompatMode(args))
@@ -132,6 +160,15 @@
     return false
 }
 
+/**
+ * Given an environment variable name pointing to a shell argument string,
+ * returns the parsed argument strings (or empty array if not set)
+ */
+private fun envVarToArgs(varName: String): Array<String> {
+    val value = System.getenv(varName) ?: return emptyArray()
+    return ParametersListUtil.parse(value).toTypedArray()
+}
+
 private fun exit(exitCode: Int = 0) {
     if (options.verbose) {
         options.stdout.println("$PROGRAM_NAME exiting with exit code $exitCode")
@@ -144,25 +181,14 @@
 private fun processFlags() {
     val stopwatch = Stopwatch.createStarted()
 
-    // --copy-annotations?
-    val privateAnnotationsSource = options.privateAnnotationsSource
-    val privateAnnotationsTarget = options.privateAnnotationsTarget
-    if (privateAnnotationsSource != null && privateAnnotationsTarget != null) {
-        val rewrite = RewriteAnnotations()
-        // Support pointing to both stub-annotations and stub-annotations/src/main/java
-        val src = File(privateAnnotationsSource, "src${File.separator}main${File.separator}java")
-        val source = if (src.isDirectory) src else privateAnnotationsSource
-        rewrite.modifyAnnotationSources(source, privateAnnotationsTarget)
-    }
-
-    // --rewrite-annotations?
-    options.rewriteAnnotations?.let { RewriteAnnotations().rewriteAnnotations(it) }
+    processNonCodebaseFlags()
 
     val codebase =
         if (options.sources.size == 1 && options.sources[0].path.endsWith(SdkConstants.DOT_TXT)) {
-            loadFromSignatureFiles(
-                file = options.sources[0], kotlinStyleNulls = options.inputKotlinStyleNulls,
-                manifest = options.manifest, performChecks = true, supportsStagedNullability = true
+            SignatureFileLoader.load(
+                file = options.sources[0],
+                kotlinStyleNulls = options.inputKotlinStyleNulls,
+                supportsStagedNullability = true
             )
         } else if (options.apiJar != null) {
             loadFromJarFile(options.apiJar!!)
@@ -199,69 +225,29 @@
     }
 
     // Generate the documentation stubs *before* we migrate nullness information.
-    options.docStubsDir?.let { createStubFiles(it, codebase, docStubs = true,
-        writeStubList = options.docStubsSourceList != null) }
-
-    val currentApiFile = options.currentApi
-    if (currentApiFile != null && options.checkCompatibility) {
-        val current =
-            if (currentApiFile.path.endsWith(SdkConstants.DOT_JAR)) {
-                loadFromJarFile(currentApiFile)
-            } else {
-                loadFromSignatureFiles(
-                    currentApiFile, options.inputKotlinStyleNulls,
-                    supportsStagedNullability = true
-                )
-            }
-
-        // If configured, compares the new API with the previous API and reports
-        // any incompatibilities.
-        CompatibilityCheck.checkCompatibility(codebase, current)
-    }
-
-    val previousApiFile = options.previousApi
-    if (previousApiFile != null) {
-        val previous =
-            if (previousApiFile.path.endsWith(SdkConstants.DOT_JAR)) {
-                loadFromJarFile(previousApiFile)
-            } else {
-                loadFromSignatureFiles(
-                    previousApiFile, options.inputKotlinStyleNulls,
-                    supportsStagedNullability = true
-                )
-            }
-
-        // If configured, compares the new API with the previous API and reports
-        // any incompatibilities.
-        if (options.checkCompatibility && options.currentApi == null) { // otherwise checked against currentApi above
-            CompatibilityCheck.checkCompatibility(codebase, previous)
-        }
-
-        // If configured, checks for newly added nullness information compared
-        // to the previous stable API and marks the newly annotated elements
-        // as migrated (which will cause the Kotlin compiler to treat problems
-        // as warnings instead of errors
-
-        migrateNulls(codebase, previous)
+    options.docStubsDir?.let {
+        createStubFiles(
+            it, codebase, docStubs = true,
+            writeStubList = options.docStubsSourceList != null
+        )
     }
 
     // Based on the input flags, generates various output files such
     // as signature files and/or stubs files
     options.apiFile?.let { apiFile ->
-        val apiFilter = FilterPredicate(ApiPredicate(codebase))
-        val apiReference = ApiPredicate(codebase, ignoreShown = true)
-        val apiEmit = apiFilter.and(ElidingPredicate(apiReference))
+        val apiType = ApiType.PUBLIC_API
+        val apiEmit = apiType.getEmitFilter()
+        val apiReference = apiType.getReferenceFilter()
 
         createReportFile(codebase, apiFile, "API") { printWriter ->
-            val preFiltered = codebase.original != null
-            SignatureWriter(printWriter, apiEmit, apiReference, preFiltered)
+            SignatureWriter(printWriter, apiEmit, apiReference, codebase.preFiltered)
         }
     }
 
     options.dexApiFile?.let { apiFile ->
-        val apiFilter = FilterPredicate(ApiPredicate(codebase))
+        val apiFilter = FilterPredicate(ApiPredicate())
         val memberIsNotCloned: Predicate<Item> = Predicate { !it.isCloned() }
-        val apiReference = ApiPredicate(codebase, ignoreShown = true)
+        val apiReference = ApiPredicate(ignoreShown = true)
         val dexApiEmit = memberIsNotCloned.and(apiFilter)
 
         createReportFile(
@@ -269,12 +255,38 @@
         ) { printWriter -> DexApiWriter(printWriter, dexApiEmit, apiReference) }
     }
 
+    options.apiXmlFile?.let { apiFile ->
+        val apiType = ApiType.PUBLIC_API
+        val apiEmit = apiType.getEmitFilter()
+        val apiReference = apiType.getReferenceFilter()
+
+        createReportFile(codebase, apiFile, "XML API") { printWriter ->
+            JDiffXmlWriter(printWriter, apiEmit, apiReference, codebase.preFiltered)
+        }
+    }
+
+    options.dexApiMappingFile?.let { apiFile ->
+        val apiType = ApiType.ALL
+        val apiEmit = apiType.getEmitFilter()
+        val apiReference = apiType.getReferenceFilter()
+
+        createReportFile(
+            codebase, apiFile, "DEX API Mapping"
+        ) { printWriter ->
+            DexApiWriter(
+                printWriter, apiEmit, apiReference,
+                membersOnly = true,
+                includePositions = true
+            )
+        }
+    }
+
     options.removedApiFile?.let { apiFile ->
         val unfiltered = codebase.original ?: codebase
 
-        val removedFilter = FilterPredicate(ApiPredicate(codebase, matchRemoved = true))
-        val removedReference = ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = true)
-        val removedEmit = removedFilter.and(ElidingPredicate(removedReference))
+        val apiType = ApiType.REMOVED
+        val removedEmit = apiType.getEmitFilter()
+        val removedReference = apiType.getReferenceFilter()
 
         createReportFile(unfiltered, apiFile, "removed API") { printWriter ->
             SignatureWriter(printWriter, removedEmit, removedReference, codebase.original != null)
@@ -284,8 +296,8 @@
     options.removedDexApiFile?.let { apiFile ->
         val unfiltered = codebase.original ?: codebase
 
-        val removedFilter = FilterPredicate(ApiPredicate(codebase, matchRemoved = true))
-        val removedReference = ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = true)
+        val removedFilter = FilterPredicate(ApiPredicate(matchRemoved = true))
+        val removedReference = ApiPredicate(ignoreShown = true, ignoreRemoved = true)
         val memberIsNotCloned: Predicate<Item> = Predicate { !it.isCloned() }
         val removedDexEmit = memberIsNotCloned.and(removedFilter)
 
@@ -295,10 +307,9 @@
     }
 
     options.privateApiFile?.let { apiFile ->
-        val apiFilter = FilterPredicate(ApiPredicate(codebase))
-        val memberIsNotCloned: Predicate<Item> = Predicate { !it.isCloned() }
-        val privateEmit = memberIsNotCloned.and(apiFilter.negate())
-        val privateReference = Predicate<Item> { true }
+        val apiType = ApiType.PRIVATE
+        val privateEmit = apiType.getEmitFilter()
+        val privateReference = apiType.getReferenceFilter()
 
         createReportFile(codebase, apiFile, "private API") { printWriter ->
             SignatureWriter(printWriter, privateEmit, privateReference, codebase.original != null)
@@ -306,7 +317,7 @@
     }
 
     options.privateDexApiFile?.let { apiFile ->
-        val apiFilter = FilterPredicate(ApiPredicate(codebase))
+        val apiFilter = FilterPredicate(ApiPredicate())
         val privateEmit = apiFilter.negate()
         val privateReference = Predicate<Item> { true }
 
@@ -320,8 +331,8 @@
     }
 
     options.proguard?.let { proguard ->
-        val apiEmit = FilterPredicate(ApiPredicate(codebase))
-        val apiReference = ApiPredicate(codebase, ignoreShown = true)
+        val apiEmit = FilterPredicate(ApiPredicate())
+        val apiReference = ApiPredicate(ignoreShown = true)
         createReportFile(
             codebase, proguard, "Proguard file"
         ) { printWriter -> ProguardWriter(printWriter, apiEmit, apiReference) }
@@ -332,6 +343,33 @@
         SdkFileWriter(codebase, dir).generate()
     }
 
+    for (check in options.compatibilityChecks) {
+        checkCompatibility(codebase, check)
+    }
+
+    val previousApiFile = options.migrateNullsFrom
+    if (previousApiFile != null) {
+        val previous =
+            if (previousApiFile.path.endsWith(SdkConstants.DOT_JAR)) {
+                loadFromJarFile(previousApiFile)
+            } else {
+                SignatureFileLoader.load(
+                    file = previousApiFile,
+                    kotlinStyleNulls = options.inputKotlinStyleNulls,
+                    supportsStagedNullability = true
+                )
+            }
+
+        // If configured, checks for newly added nullness information compared
+        // to the previous stable API and marks the newly annotated elements
+        // as migrated (which will cause the Kotlin compiler to treat problems
+        // as warnings instead of errors
+
+        migrateNulls(codebase, previous)
+
+        previous.dispose()
+    }
+
     // Now that we've migrated nullness information we can proceed to write non-doc stubs, if any.
 
     options.stubsDir?.let {
@@ -346,7 +384,7 @@
             val src = File(stubAnnotations, "src${File.separator}main${File.separator}java")
             val source = if (src.isDirectory) src else stubAnnotations
             source.listFiles()?.forEach { file ->
-                RewriteAnnotations().copyAnnotations(file, File(it, file.name))
+                RewriteAnnotations().copyAnnotations(codebase, file, File(it, file.name))
             }
         }
     }
@@ -364,7 +402,7 @@
                     path
                 }
             }
-            Files.asCharSink(file, UTF_8).write(contents)
+            file.writeText(contents)
         }
         options.stubsSourceList?.let(writeStubsFile)
         options.docStubsSourceList?.let(writeStubsFile)
@@ -384,7 +422,7 @@
 
     Disposer.dispose(LintCoreApplicationEnvironment.get().parentDisposable)
 
-    if (!options.quiet) {
+    if (options.verbose) {
         val packageCount = codebase.size()
         options.stdout.println("\n$PROGRAM_NAME finished handling $packageCount packages in $stopwatch")
         options.stdout.flush()
@@ -393,6 +431,134 @@
     invokeDocumentationTool()
 }
 
+fun processNonCodebaseFlags() {
+    // --copy-annotations?
+    val privateAnnotationsSource = options.privateAnnotationsSource
+    val privateAnnotationsTarget = options.privateAnnotationsTarget
+    if (privateAnnotationsSource != null && privateAnnotationsTarget != null) {
+        val rewrite = RewriteAnnotations()
+        // Support pointing to both stub-annotations and stub-annotations/src/main/java
+        val src = File(privateAnnotationsSource, "src${File.separator}main${File.separator}java")
+        val source = if (src.isDirectory) src else privateAnnotationsSource
+        source.listFiles()?.forEach { file ->
+            rewrite.modifyAnnotationSources(null, file, File(privateAnnotationsTarget, file.name))
+        }
+    }
+
+    // --rewrite-annotations?
+    options.rewriteAnnotations?.let { RewriteAnnotations().rewriteAnnotations(it) }
+
+    // Convert android.jar files?
+    options.androidJarSignatureFiles?.let { root ->
+        // Generate API signature files for all the historical JAR files
+        ConvertJarsToSignatureFiles().convertJars(root)
+    }
+
+    for ((signatureFile, jDiffFile) in options.convertToXmlFiles) {
+        val apiType = ApiType.ALL
+        val apiEmit = apiType.getEmitFilter()
+        val apiReference = apiType.getReferenceFilter()
+
+        val signatureApi = SignatureFileLoader.load(
+            file = signatureFile,
+            kotlinStyleNulls = options.inputKotlinStyleNulls,
+            supportsStagedNullability = true
+        )
+
+        createReportFile(signatureApi, jDiffFile, "JDiff File") { printWriter ->
+            JDiffXmlWriter(printWriter, apiEmit, apiReference, signatureApi.preFiltered)
+        }
+    }
+}
+
+/**
+ * Checks compatibility of the given codebase with the codebase described in the
+ * signature file.
+ */
+fun checkCompatibility(
+    codebase: Codebase,
+    check: CheckRequest
+) {
+    val signatureFile = check.file
+
+    val current =
+        if (signatureFile.path.endsWith(SdkConstants.DOT_JAR)) {
+            loadFromJarFile(signatureFile)
+        } else {
+            SignatureFileLoader.load(
+                file = signatureFile,
+                kotlinStyleNulls = options.inputKotlinStyleNulls,
+                supportsStagedNullability = true
+            )
+        }
+
+    var base: Codebase? = null
+    val releaseType = check.releaseType
+    val apiType = check.apiType
+
+    // If diffing with a system-api or test-api (or other signature-based codebase
+    // generated from --show-annotations), the API is partial: it's only listing
+    // the API that is *different* from the base API. This really confuses the
+    // codebase comparison when diffing with a complete codebase, since it looks like
+    // many classes and members have been added and removed. Therefore, the comparison
+    // is simpler if we just make the comparison with the same generated signature
+    // file. If we've only emitted one for the new API, use it directly, if not, generate
+    // it first
+    val new =
+        if (options.showAnnotations.isNotEmpty() || apiType != ApiType.PUBLIC_API) {
+            val apiFile = apiType.getOptionFile() ?: run {
+                val tempFile = createTempFile("compat-check-signatures-$apiType", DOT_TXT)
+                tempFile.deleteOnExit()
+                val apiEmit = apiType.getEmitFilter()
+                val apiReference = apiType.getReferenceFilter()
+
+                createReportFile(codebase, tempFile, null) { printWriter ->
+                    SignatureWriter(printWriter, apiEmit, apiReference, codebase.preFiltered)
+                }
+
+                tempFile
+            }
+
+            // Fast path: if the signature files are identical, we're already good!
+            if (apiFile.readText(UTF_8) == signatureFile.readText(UTF_8)) {
+                return
+            }
+
+            base = codebase
+
+            SignatureFileLoader.load(
+                file = apiFile,
+                kotlinStyleNulls = options.inputKotlinStyleNulls,
+                supportsStagedNullability = true
+            )
+        } else {
+            // Fast path: if we've already generated a signature file and it's identical, we're good!
+            val apiFile = options.apiFile
+            if (apiFile != null && apiFile.readText(UTF_8) == signatureFile.readText(UTF_8)) {
+                return
+            }
+
+            codebase
+        }
+
+    // If configured, compares the new API with the previous API and reports
+    // any incompatibilities.
+    CompatibilityCheck.checkCompatibility(new, current, releaseType, apiType, base)
+}
+
+fun createTempFile(namePrefix: String, nameSuffix: String): File {
+    val tempFolder = options.tempFolder
+    return if (tempFolder != null) {
+        val preferred = File(tempFolder, namePrefix + nameSuffix)
+        if (!preferred.exists()) {
+            return preferred
+        }
+        File.createTempFile(namePrefix, nameSuffix, tempFolder)
+    } else {
+        File.createTempFile(namePrefix, nameSuffix)
+    }
+}
+
 fun invokeDocumentationTool() {
     if (options.noDocs) {
         return
@@ -413,114 +579,121 @@
         builder.setExecutable(File(args[0]))
         builder.addArgs(args.slice(1 until args.size))
 
-        val processOutputHandler = CachedProcessOutputHandler()
+        val processOutputHandler =
+            if (options.quiet) {
+                CachedProcessOutputHandler()
+            } else {
+                object : ProcessOutputHandler {
+                    override fun handleOutput(processOutput: ProcessOutput?) {
+                    }
+
+                    override fun createOutput(): ProcessOutput {
+                        val out = PrintWriterOutputStream(options.stdout)
+                        val err = PrintWriterOutputStream(options.stderr)
+                        return object : ProcessOutput {
+                            override fun getStandardOutput(): OutputStream {
+                                return out
+                            }
+
+                            override fun getErrorOutput(): OutputStream {
+                                return err
+                            }
+
+                            override fun close() {
+                                out.flush()
+                                err.flush()
+                            }
+                        }
+                    }
+                }
+            }
 
         val result = DefaultProcessExecutor(StdLogger(ERROR))
             .execute(builder.createProcess(), processOutputHandler)
-        val output = processOutputHandler.processOutput
-        output.standardOutputAsString
-        output.errorOutputAsString
 
         val exitCode = result.exitValue
-        options.stdout.println("${args[0]} finished with exitCode $exitCode")
-        options.stdout.flush()
+        if (!options.quiet) {
+            options.stdout.println("${args[0]} finished with exitCode $exitCode")
+            options.stdout.flush()
+        }
         if (exitCode != 0) {
+            val stdout = if (processOutputHandler is CachedProcessOutputHandler)
+                processOutputHandler.processOutput.standardOutputAsString
+            else ""
+            val stderr = if (processOutputHandler is CachedProcessOutputHandler)
+                processOutputHandler.processOutput.errorOutputAsString
+            else ""
             throw DriverException(
-                stdout = output.standardOutputAsString,
-                stderr = output.errorOutputAsString,
+                stdout = "Invoking documentation tool ${args[0]} failed with exit code $exitCode\n$stdout",
+                stderr = stderr,
                 exitCode = exitCode
             )
         }
     }
 }
 
-private fun migrateNulls(codebase: Codebase, previous: Codebase) {
-    if (options.migrateNulls) {
-        val codebaseSupportsNullability = previous.supportsStagedNullability
-        val prevSupportsNullability = previous.supportsStagedNullability
-        try {
-            previous.supportsStagedNullability = true
-            codebase.supportsStagedNullability = true
-            previous.compareWith(
-                NullnessMigration(), codebase,
-                ApiPredicate(codebase)
-            )
-        } finally {
-            previous.supportsStagedNullability = prevSupportsNullability
-            codebase.supportsStagedNullability = codebaseSupportsNullability
-        }
+class PrintWriterOutputStream(private val writer: PrintWriter) : OutputStream() {
+
+    override fun write(b: ByteArray) {
+        writer.write(String(b, Charsets.UTF_8))
+    }
+
+    override fun write(b: Int) {
+        write(byteArrayOf(b.toByte()), 0, 1)
+    }
+
+    override fun write(b: ByteArray, off: Int, len: Int) {
+        writer.write(String(b, off, len, Charsets.UTF_8))
+    }
+
+    override fun flush() {
+        writer.flush()
+    }
+
+    override fun close() {
+        writer.close()
     }
 }
 
-private fun loadFromSignatureFiles(
-    file: File,
-    kotlinStyleNulls: Boolean,
-    manifest: File? = null,
-    performChecks: Boolean = false,
-    supportsStagedNullability: Boolean = false
-): Codebase {
+private fun migrateNulls(codebase: Codebase, previous: Codebase) {
+    val codebaseSupportsNullability = codebase.supportsStagedNullability
+    val prevSupportsNullability = previous.supportsStagedNullability
     try {
-        val codebase = ApiFile.parseApi(File(file.path), kotlinStyleNulls, supportsStagedNullability)
-        codebase.manifest = manifest
-        codebase.description = "Codebase loaded from ${file.name}"
-
-        if (performChecks) {
-            val analyzer = ApiAnalyzer(codebase)
-            analyzer.performChecks()
-        }
-        return codebase
-    } catch (ex: ApiParseException) {
-        val message = "Unable to parse signature file $file: ${ex.message}"
-        throw DriverException(message)
+        previous.supportsStagedNullability = true
+        codebase.supportsStagedNullability = true
+        previous.compareWith(
+            NullnessMigration(), codebase,
+            ApiPredicate()
+        )
+    } finally {
+        previous.supportsStagedNullability = prevSupportsNullability
+        codebase.supportsStagedNullability = codebaseSupportsNullability
     }
 }
 
 private fun loadFromSources(): Codebase {
-    val projectEnvironment = createProjectEnvironment()
-
-    // Push language level to PSI handler
-    projectEnvironment.project.getComponent(LanguageLevelProjectExtension::class.java)?.languageLevel =
-        options.javaLanguageLevel
-
     progress("\nProcessing sources: ")
 
     val sources = if (options.sources.isEmpty()) {
-        if (!options.quiet) {
-            options.stdout.println("No source files specified: recursively including all sources found in the source path")
+        if (options.verbose) {
+            options.stdout.println("No source files specified: recursively including all sources found in the source path (${options.sourcePath.joinToString()}})")
         }
         gatherSources(options.sourcePath)
     } else {
         options.sources
     }
 
-    val joined = mutableListOf<File>()
-    joined.addAll(options.sourcePath.map { it.absoluteFile })
-    joined.addAll(options.classpath.map { it.absoluteFile })
-    // Add in source roots implied by the source files
-    extractRoots(sources, joined)
-
-    // Create project environment with those paths
-    projectEnvironment.registerPaths(joined)
-    val project = projectEnvironment.project
-
-    val kotlinFiles = sources.filter { it.path.endsWith(SdkConstants.DOT_KT) }
-    KotlinLintAnalyzerFacade().analyze(kotlinFiles, joined, project)
-
-    val units = Extractor.createUnitsForFiles(project, sources)
-    val packageDocs = gatherHiddenPackagesFromJavaDocs(options.sourcePath)
-
     progress("\nReading Codebase: ")
-
-    val codebase = PsiBasedCodebase("Codebase loaded from source folders")
-    codebase.initialize(project, units, packageDocs)
-    codebase.manifest = options.manifest
-    codebase.apiLevel = options.currentApiLevel
+    val codebase = parseSources(sources, "Codebase loaded from source folders")
 
     progress("\nAnalyzing API: ")
 
     val analyzer = ApiAnalyzer(codebase)
-    analyzer.mergeExternalAnnotations()
+    analyzer.mergeExternalInclusionAnnotations()
     analyzer.computeApi()
+    analyzer.mergeExternalQualifierAnnotations()
+    options.nullabilityAnnotationsValidator?.validateAllFrom(codebase, options.validateNullabilityFromList)
+    options.nullabilityAnnotationsValidator?.report()
     analyzer.handleStripping()
 
     if (options.checkKotlinInterop) {
@@ -530,9 +703,9 @@
     // General API checks for Android APIs
     AndroidApiChecks().check(codebase)
 
-    val filterEmit = ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = false)
-    val apiEmit = ApiPredicate(codebase, ignoreShown = true)
-    val apiReference = ApiPredicate(codebase, ignoreShown = true)
+    val filterEmit = ApiPredicate(ignoreShown = true, ignoreRemoved = false)
+    val apiEmit = ApiPredicate(ignoreShown = true)
+    val apiReference = ApiPredicate(ignoreShown = true)
 
     // Copy methods from soon-to-be-hidden parents into descendant classes, when necessary
     progress("\nInsert missing stubs methods: ")
@@ -551,38 +724,73 @@
     return codebase
 }
 
-@Suppress("unused") // Planning to restore for performance optimizations
-private fun filterCodebase(codebase: PsiBasedCodebase): Codebase {
-    val ignoreShown = options.showAnnotations.isEmpty()
+/**
+ * Returns a codebase initialized from the given Java or Kotlin source files, with the given
+ * description. The codebase will use a project environment initialized according to the current
+ * [options].
+ */
+internal fun parseSources(sources: List<File>, description: String): PsiBasedCodebase {
+    val projectEnvironment = createProjectEnvironment()
+    val project = projectEnvironment.project
 
-    // We ignore removals when limiting the API
-    val apiFilter = FilterPredicate(ApiPredicate(codebase, ignoreShown = ignoreShown))
-    val apiReference = ApiPredicate(codebase, ignoreShown = true)
-    val apiEmit = apiFilter.and(ElidingPredicate(apiReference))
+    // Push language level to PSI handler
+    project.getComponent(LanguageLevelProjectExtension::class.java)?.languageLevel =
+        options.javaLanguageLevel
 
-    return codebase.filter(apiEmit, apiReference)
+    val joined = mutableListOf<File>()
+    joined.addAll(options.sourcePath.mapNotNull { if (it.path.isNotBlank()) it.absoluteFile else null })
+    joined.addAll(options.classpath.map { it.absoluteFile })
+    // Add in source roots implied by the source files
+    val sourceRoots = mutableListOf<File>()
+    extractRoots(options.sources, sourceRoots)
+    joined.addAll(sourceRoots)
+
+    // Create project environment with those paths
+    projectEnvironment.registerPaths(joined)
+
+    val kotlinFiles = sources.filter { it.path.endsWith(SdkConstants.DOT_KT) }
+    val trace = KotlinLintAnalyzerFacade().analyze(kotlinFiles, joined, project)
+
+    val rootDir = sourceRoots.firstOrNull() ?: options.sourcePath.firstOrNull() ?: File("").canonicalFile
+
+    val units = Extractor.createUnitsForFiles(project, sources)
+    val packageDocs = gatherHiddenPackagesFromJavaDocs(options.sourcePath)
+
+    val codebase = PsiBasedCodebase(rootDir, description)
+    codebase.initialize(project, units, packageDocs)
+    codebase.manifest = options.manifest
+    codebase.apiLevel = options.currentApiLevel
+    codebase.bindingContext = trace.bindingContext
+    return codebase
 }
 
-private fun loadFromJarFile(apiJar: File, manifest: File? = null): Codebase {
+fun loadFromJarFile(apiJar: File, manifest: File? = null, preFiltered: Boolean = false): Codebase {
     val projectEnvironment = createProjectEnvironment()
 
-    progress("Processing jar file: ")
+    progress("\nProcessing jar file: ")
 
     // Create project environment with those paths
     val project = projectEnvironment.project
     projectEnvironment.registerPaths(listOf(apiJar))
 
     val kotlinFiles = emptyList<File>()
-    KotlinLintAnalyzerFacade().analyze(kotlinFiles, listOf(apiJar), project)
+    val trace = KotlinLintAnalyzerFacade().analyze(kotlinFiles, listOf(apiJar), project)
 
-    val codebase = PsiBasedCodebase()
-    codebase.description = "Codebase loaded from $apiJar"
-    codebase.initialize(project, apiJar)
+    val codebase = PsiBasedCodebase(apiJar, "Codebase loaded from $apiJar")
+    codebase.initialize(project, apiJar, preFiltered)
     if (manifest != null) {
         codebase.manifest = options.manifest
     }
+    val apiEmit = ApiPredicate(ignoreShown = true)
+    val apiReference = ApiPredicate(ignoreShown = true)
     val analyzer = ApiAnalyzer(codebase)
-    analyzer.mergeExternalAnnotations()
+    analyzer.mergeExternalInclusionAnnotations()
+    analyzer.computeApi()
+    analyzer.mergeExternalQualifierAnnotations()
+    options.nullabilityAnnotationsValidator?.validateAllFrom(codebase, options.validateNullabilityFromList)
+    options.nullabilityAnnotationsValidator?.report()
+    analyzer.generateInheritedStubs(apiEmit, apiReference)
+    codebase.bindingContext = trace.bindingContext
     return codebase
 }
 
@@ -590,6 +798,14 @@
     ensurePsiFileCapacity()
     val appEnv = LintCoreApplicationEnvironment.get()
     val parentDisposable = Disposer.newDisposable()
+
+    if (!assertionsEnabled() &&
+        System.getenv(ENV_VAR_METALAVA_DUMP_ARGV) == null &&
+        !java.lang.Boolean.getBoolean(ENV_VAR_METALAVA_TESTS_RUNNING)
+    ) {
+        DefaultLogger.disableStderrDumping(parentDisposable)
+    }
+
     return LintCoreProjectEnvironment.create(parentDisposable, appEnv)
 }
 
@@ -644,11 +860,21 @@
             codebase = codebase,
             stubsDir = stubDir,
             generateAnnotations = options.generateAnnotations,
-            preFiltered = codebase.original != null,
+            preFiltered = codebase.preFiltered,
             docStubs = docStubs
         )
     codebase.accept(stubWriter)
 
+    if (docStubs) {
+        // Overview docs? These are generally in the empty package.
+        codebase.findPackage("")?.let { empty ->
+            val overview = codebase.getPackageDocs()?.getOverviewDocumentation(empty)
+            if (overview != null && overview.isNotBlank()) {
+                stubWriter.writeDocOverview(empty, overview)
+            }
+        }
+    }
+
     if (writeStubList) {
         // Optionally also write out a list of source files that were generated; used
         // for example to point javadoc to the stubs output to generate documentation
@@ -678,13 +904,15 @@
     }
 }
 
-private fun createReportFile(
+fun createReportFile(
     codebase: Codebase,
     apiFile: File,
-    description: String,
+    description: String?,
     createVisitor: (PrintWriter) -> ApiVisitor
 ) {
-    progress("\nWriting $description file: ")
+    if (description != null) {
+        progress("\nWriting $description file: ")
+    }
     val localTimer = Stopwatch.createStarted()
     try {
         val writer = PrintWriter(Files.asCharSink(apiFile, Charsets.UTF_8).openBufferedStream())
@@ -695,7 +923,7 @@
     } catch (e: IOException) {
         reporter.report(Errors.IO_ERROR, apiFile, "Cannot open file for write.")
     }
-    if (options.verbose) {
+    if (description != null && options.verbose) {
         options.stdout.print("\n$PROGRAM_NAME wrote $description file $apiFile in ${localTimer.elapsed(SECONDS)} seconds")
     }
 }
@@ -720,8 +948,18 @@
     }
 }
 
+private fun skippableDirectory(file: File): Boolean = file.path.endsWith(".git") && file.name == ".git"
+
 private fun addSourceFiles(list: MutableList<File>, file: File) {
     if (file.isDirectory) {
+        if (skippableDirectory(file)) {
+            return
+        }
+        if (java.nio.file.Files.isSymbolicLink(file.toPath())) {
+            reporter.report(Errors.IGNORING_SYMLINK, file,
+                "Ignoring symlink during source file discovery directory traversal")
+            return
+        }
         val files = file.listFiles()
         if (files != null) {
             for (child in files) {
@@ -738,6 +976,10 @@
 fun gatherSources(sourcePath: List<File>): List<File> {
     val sources = Lists.newArrayList<File>()
     for (file in sourcePath) {
+        if (file.path.isBlank()) {
+            // --source-path "" means don't search source path; use "." for pwd
+            continue
+        }
         addSourceFiles(sources, file.absoluteFile)
     }
     return sources
@@ -745,39 +987,91 @@
 
 private fun addHiddenPackages(
     packageToDoc: MutableMap<String, String>,
+    packageToOverview: MutableMap<String, String>,
     hiddenPackages: MutableSet<String>,
     file: File,
     pkg: String
 ) {
     if (file.isDirectory) {
+        if (skippableDirectory(file)) {
+            return
+        }
+        // Ignore symbolic links during traversal
+        if (java.nio.file.Files.isSymbolicLink(file.toPath())) {
+            reporter.report(Errors.IGNORING_SYMLINK, file,
+                "Ignoring symlink during package.html discovery directory traversal")
+            return
+        }
         val files = file.listFiles()
         if (files != null) {
             for (child in files) {
-                val subPkg =
+                var subPkg =
                     if (child.isDirectory)
                         if (pkg.isEmpty())
                             child.name
                         else pkg + "." + child.name
                     else pkg
-                addHiddenPackages(packageToDoc, hiddenPackages, child, subPkg)
+
+                if (subPkg.endsWith("src.main.java")) {
+                    // It looks like the source path was incorrectly configured; make corrections here
+                    // to ensure that we map the package.html files to the real packages.
+                    subPkg = ""
+                }
+
+                addHiddenPackages(packageToDoc, packageToOverview, hiddenPackages, child, subPkg)
             }
         }
-    } else if (file.isFile && (file.name == "package.html" || file.name == "overview.html")) {
-        val contents = Files.asCharSource(file, Charsets.UTF_8).read()
-        packageToDoc[pkg] = contents + (packageToDoc[pkg] ?: "") // Concatenate in case package has both files
+    } else if (file.isFile) {
+        var javadoc = false
+        val map = when {
+            file.name == "package.html" -> {
+                javadoc = true; packageToDoc
+            }
+            file.name == "overview.html" -> {
+                packageToOverview
+            }
+            else -> return
+        }
+        var contents = Files.asCharSource(file, Charsets.UTF_8).read()
+        if (javadoc) {
+            contents = packageHtmlToJavadoc(contents)
+        }
+
+        var realPkg = pkg
+        // Sanity check the package; it's computed from the directory name
+        // relative to the source path, but if the real source path isn't
+        // passed in (and is instead some directory containing the source path)
+        // then we compute the wrong package here. Instead, look for an adjacent
+        // java class and pick the package from it
+        for (sibling in file.parentFile.listFiles()) {
+            if (sibling.path.endsWith(DOT_JAVA)) {
+                val javaPkg = ClassName(sibling.readText()).packageName
+                if (javaPkg != null) {
+                    realPkg = pkg
+                    break
+                }
+            }
+        }
+
+        map[realPkg] = contents
         if (contents.contains("@hide")) {
-            hiddenPackages.add(pkg)
+            hiddenPackages.add(realPkg)
         }
     }
 }
 
 private fun gatherHiddenPackagesFromJavaDocs(sourcePath: List<File>): PackageDocs {
-    val map = HashMap<String, String>(100)
+    val packageComments = HashMap<String, String>(100)
+    val overviewHtml = HashMap<String, String>(10)
     val set = HashSet<String>(100)
     for (file in sourcePath) {
-        addHiddenPackages(map, set, file, "")
+        if (file.path.isBlank()) {
+            // Ignoring empty paths, which means "no source path search". Use "." for current directory.
+            continue
+        }
+        addHiddenPackages(packageComments, overviewHtml, set, file, "")
     }
-    return PackageDocs(map, set)
+    return PackageDocs(packageComments, overviewHtml, set)
 }
 
 private fun extractRoots(sources: List<File>, sourceRoots: MutableList<File> = mutableListOf()): List<File> {
diff --git a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
index e065f58..821dadd 100644
--- a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
+++ b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
@@ -19,9 +19,9 @@
 import com.android.SdkConstants
 import com.android.tools.lint.annotations.Extractor
 import com.android.tools.lint.client.api.AnnotationLookup
-import com.android.tools.lint.detector.api.ConstantEvaluator
 import com.android.tools.metalava.doclava1.Errors
 import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.AnnotationTarget
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.FieldItem
@@ -30,37 +30,31 @@
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.PackageItem
 import com.android.tools.metalava.model.ParameterItem
-import com.android.tools.metalava.model.canonicalizeFloatingPointString
-import com.android.tools.metalava.model.javaEscapeString
+import com.android.tools.metalava.model.psi.CodePrinter
 import com.android.tools.metalava.model.psi.PsiAnnotationItem
 import com.android.tools.metalava.model.psi.PsiClassItem
+import com.android.tools.metalava.model.psi.PsiMethodItem
 import com.android.tools.metalava.model.visitors.ApiVisitor
-import com.android.utils.XmlUtils
 import com.google.common.base.Charsets
 import com.google.common.xml.XmlEscapers
+import com.intellij.psi.JavaRecursiveElementVisitor
 import com.intellij.psi.PsiAnnotation
-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.PsiModifier
 import com.intellij.psi.PsiNameValuePair
-import com.intellij.psi.PsiReference
-import com.intellij.psi.PsiTypeCastExpression
+import com.intellij.psi.PsiReferenceExpression
+import com.intellij.psi.PsiReturnStatement
 import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.UBinaryExpressionWithType
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.ULiteralExpression
 import org.jetbrains.uast.UNamedExpression
 import org.jetbrains.uast.UReferenceExpression
+import org.jetbrains.uast.USimpleNameReferenceExpression
 import org.jetbrains.uast.UastEmptyExpression
 import org.jetbrains.uast.java.JavaUAnnotation
 import org.jetbrains.uast.java.expressions.JavaUAnnotationCallExpression
-import org.jetbrains.uast.util.isArrayInitializer
-import org.jetbrains.uast.util.isTypeCast
 import java.io.BufferedOutputStream
 import java.io.File
 import java.io.FileOutputStream
@@ -76,7 +70,7 @@
 class ExtractAnnotations(
     private val codebase: Codebase,
     private val outputFile: File
-) : ApiVisitor(codebase) {
+) : ApiVisitor() {
     // Used linked hash map for order such that we always emit parameters after their surrounding method etc
     private val packageToAnnotationPairs = LinkedHashMap<PackageItem, MutableList<Pair<Item, AnnotationHolder>>>()
 
@@ -88,6 +82,20 @@
         val uAnnotation: UAnnotation?
     )
 
+    private val fieldNamePrinter = CodePrinter(
+        codebase = codebase,
+        filterReference = filterReference,
+        inlineFieldValues = false,
+        skipUnknown = true
+    )
+
+    private val fieldValuePrinter = CodePrinter(
+        codebase = codebase,
+        filterReference = filterReference,
+        inlineFieldValues = true,
+        skipUnknown = true
+    )
+
     private val classToAnnotationHolder = mutableMapOf<String, AnnotationHolder>()
 
     fun extractAnnotations() {
@@ -189,7 +197,7 @@
                 if (annotation.isTypeDefAnnotation()) {
                     // Imported typedef
                     addItem(item, AnnotationHolder(null, annotation, null))
-                } else if (annotation.isSignificantInStubs() && !annotation.hasClassRetention() &&
+                } else if (annotation.targets().contains(AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE) &&
                     !options.includeSourceRetentionAnnotations
                 ) {
                     addItem(item, AnnotationHolder(null, annotation, null))
@@ -237,6 +245,13 @@
                         )
                         classToAnnotationHolder[className] = result
                         addItem(item, result)
+
+                        if (item is PsiMethodItem && result.uAnnotation != null &&
+                            !reporter.isSuppressed(Errors.RETURNING_UNEXPECTED_CONSTANT)
+                        ) {
+                            verifyReturnedConstants(item, result.uAnnotation, result, className)
+                        }
+
                         continue
                     }
                 }
@@ -244,6 +259,63 @@
         }
     }
 
+    /**
+     * Given a method whose return value is annotated with a typedef, runs checks on the typedef
+     * and flags any returned constants not in the list.
+     */
+    private fun verifyReturnedConstants(
+        item: PsiMethodItem,
+        uAnnotation: UAnnotation,
+        result: AnnotationHolder,
+        className: String
+    ) {
+        val method = item.psiMethod
+        if (method.body != null) {
+            method.body?.accept(object : JavaRecursiveElementVisitor() {
+                private var constants: List<String>? = null
+
+                override fun visitReturnStatement(statement: PsiReturnStatement) {
+                    val value = statement.returnValue
+                    if (value is PsiReferenceExpression) {
+                        val resolved = value.resolve() as? PsiField ?: return
+                        val modifiers = resolved.modifierList ?: return
+                        if (modifiers.hasModifierProperty(PsiModifier.STATIC) &&
+                            modifiers.hasModifierProperty(PsiModifier.FINAL)
+                        ) {
+                            if (resolved.type.arrayDimensions > 0) {
+                                return
+                            }
+                            val name = resolved.name
+
+                            // Make sure this is one of the allowed annotations
+                            val names = constants ?: run {
+                                constants = computeValidConstantNames(uAnnotation)
+                                constants!!
+                            }
+                            if (names.isNotEmpty() && !names.contains(name)) {
+                                val expected = names.joinToString { it }
+                                reporter.report(
+                                    Errors.RETURNING_UNEXPECTED_CONSTANT, value as PsiElement,
+                                    "Returning unexpected constant $name; is @${result.annotationClass?.simpleName()
+                                        ?: className} missing this constant? Expected one of $expected"
+                                )
+                            }
+                        }
+                    }
+                }
+            })
+        }
+    }
+
+    private fun computeValidConstantNames(annotation: UAnnotation): List<String> {
+        val constants = annotation.findAttributeValue(SdkConstants.ATTR_VALUE) ?: return emptyList()
+        if (constants is UCallExpression) {
+            return constants.valueArguments.mapNotNull { (it as? USimpleNameReferenceExpression)?.identifier }.toList()
+        }
+
+        return emptyList()
+    }
+
     private fun hasSourceRetention(annotationClass: ClassItem): Boolean {
         if (annotationClass is PsiClassItem) {
             return hasSourceRetention(annotationClass.psiClass)
@@ -270,7 +342,10 @@
         if ("java.lang.annotation.Retention" == qualifiedName || "kotlin.annotation.Retention" == qualifiedName) {
             val attributes = annotation.attributeValues
             if (attributes.size != 1) {
-                error("Expected exactly one parameter passed to @Retention")
+                reporter.report(
+                    Severity.ERROR, null as PsiElement?,
+                    "Expected exactly one parameter passed to @Retention", Errors.ANNOTATION_EXTRACTION
+                )
                 return false
             }
             val value = attributes[0].expression
@@ -364,7 +439,10 @@
                     if (i > 0) {
                         sb.append(',').append(' ')
                     }
-                    val type = parameterList[i].type().toTypeString().replace(" ", "")
+                    val type = parameterList[i].type().toTypeString()
+                        .replace(" ", "")
+                        .replace("?extends", "? extends ")
+                        .replace("?super", "? super ")
                     sb.append(type)
                     i++
                 }
@@ -373,11 +451,11 @@
             }
 
             is FieldItem -> {
-                return escapeXml(containingClass().qualifiedName()) + ' '.toString() + name()
+                return escapeXml(containingClass().qualifiedName()) + " " + name()
             }
 
             is ParameterItem -> {
-                return containingMethod().getExternalAnnotationSignature() + ' '.toString() + this.parameterIndex
+                return containingMethod().getExternalAnnotationSignature() + " " + this.parameterIndex
             }
         }
 
@@ -504,126 +582,18 @@
         writer.println("    </annotation>")
     }
 
-    private fun attributeString(value: UExpression?, inlineConstants: Boolean): String? {
-        value ?: return null
-        val sb = StringBuilder()
-        return if (appendExpression(sb, value, inlineConstants)) {
-            sb.toString()
-        } else {
-            null
-        }
-    }
-
-    private fun appendExpression(
-        sb: StringBuilder,
-        expression: UExpression,
+    private fun attributeString(
+        value: UExpression?,
         inlineConstants: Boolean
-    ): Boolean {
-        if (expression.isArrayInitializer()) {
-            val call = expression as UCallExpression
-            val initializers = call.valueArguments
-            sb.append('{')
-            var first = true
-            val initialLength = sb.length
-            for (e in initializers) {
-                val length = sb.length
-                if (first) {
-                    first = false
-                } else {
-                    sb.append(", ")
-                }
-                val appended = appendExpression(sb, e, inlineConstants)
-                if (!appended) {
-                    // trunk off comma if it bailed for some reason (e.g. constant
-                    // filtered out by API etc)
-                    sb.setLength(length)
-                    if (length == initialLength) {
-                        first = true
-                    }
-                }
-            }
-            sb.append('}')
-            return sb.length != 2
-        } else if (expression is UReferenceExpression) {
-            val resolved = expression.resolve()
-            if (resolved is PsiField) {
-                val field = resolved as PsiField?
-                if (!inlineConstants) {
-                    // Inline constants
-                    val value = field!!.computeConstantValue()
-                    if (appendLiteralValue(sb, value)) {
-                        return true
-                    }
-                }
-
-                val declaringClass = field!!.containingClass
-                if (declaringClass == null) {
-                    error("No containing class found for " + field.name)
-                    return false
-                }
-                val qualifiedName = declaringClass.qualifiedName
-                val fieldName = field.name
-
-                if (qualifiedName != null) {
-                    val cls = codebase.findClass(qualifiedName)
-                    val fld = cls?.findField(fieldName, true)
-                    if (fld == null || !filterReference.test(fld)) {
-                        // This field is not visible: remove from typedef
-                        if (fld != null) {
-                            reporter.report(
-                                Errors.HIDDEN_TYPEDEF_CONSTANT, fld,
-                                "Typedef class references hidden field $fld: removed from typedef metadata"
-                            )
-                        }
-                        return false
-                    }
-                    sb.append(qualifiedName)
-                    sb.append('.')
-                    sb.append(fieldName)
-                    return true
-                }
-                return false
+    ): String? {
+        val printer =
+            if (inlineConstants) {
+                fieldValuePrinter
             } else {
-                warning("Unexpected reference to $expression")
-                return false
+                fieldNamePrinter
             }
-        } else if (expression is ULiteralExpression) {
-            val literalValue = expression.value
-            if (appendLiteralValue(sb, literalValue)) {
-                return true
-            }
-        } else if (expression is UBinaryExpressionWithType) {
-            if ((expression).isTypeCast()) {
-                val operand = expression.operand
-                return appendExpression(sb, operand, inlineConstants)
-            }
-            return false
-        }
 
-        // For example, binary expressions like 3 + 4
-        val literalValue = ConstantEvaluator.evaluate(null, expression)
-        if (literalValue != null) {
-            if (appendLiteralValue(sb, literalValue)) {
-                return true
-            }
-        }
-
-        warning("Unexpected annotation expression of type ${expression.javaClass} and is $expression")
-
-        return false
-    }
-
-    private fun appendLiteralValue(sb: StringBuilder, literalValue: Any?): Boolean {
-        if (literalValue is Number || literalValue is Boolean) {
-            sb.append(literalValue.toString())
-            return true
-        } else if (literalValue is String || literalValue is Char) {
-            sb.append('"')
-            XmlUtils.appendXmlAttributeValue(sb, literalValue.toString())
-            sb.append('"')
-            return true
-        }
-        return false
+        return printer.toSourceString(value)
     }
 
     private fun isInlinedConstant(annotationItem: AnnotationItem): Boolean {
@@ -632,141 +602,4 @@
 
     /** Whether to sort annotation attributes (otherwise their declaration order is used)  */
     private val sortAnnotations: Boolean = true
-
-    private fun warning(string: String) {
-        reporter.report(Severity.ERROR, null as PsiElement?, string, Errors.ANNOTATION_EXTRACTION)
-    }
-
-    private fun error(string: String) {
-        reporter.report(Severity.WARNING, null as PsiElement?, string, Errors.ANNOTATION_EXTRACTION)
-    }
-
-    companion object {
-        /** Given an annotation member value, returns the corresponding Java source expression */
-        fun toSourceExpression(value: PsiAnnotationMemberValue, owner: Item): String {
-            val sb = StringBuilder()
-            appendSourceExpression(value, sb, owner)
-            return sb.toString()
-        }
-
-        private fun appendSourceExpression(value: PsiAnnotationMemberValue, sb: StringBuilder, owner: Item): Boolean {
-            if (value is PsiReference) {
-                val resolved = value.resolve()
-                if (resolved is PsiField) {
-                    sb.append(resolved.containingClass?.qualifiedName).append('.').append(resolved.name)
-                    return true
-                }
-            } else if (value is PsiLiteral) {
-                return appendSourceLiteral(value.value, sb, owner)
-            } else if (value is PsiClassObjectAccessExpression) {
-                sb.append(value.operand.type.canonicalText).append(".class")
-                return true
-            } else if (value is PsiArrayInitializerMemberValue) {
-                sb.append('{')
-                var first = true
-                val initialLength = sb.length
-                for (e in value.initializers) {
-                    val length = sb.length
-                    if (first) {
-                        first = false
-                    } else {
-                        sb.append(", ")
-                    }
-                    val appended = appendSourceExpression(e, sb, owner)
-                    if (!appended) {
-                        // trunk off comma if it bailed for some reason (e.g. constant
-                        // filtered out by API etc)
-                        sb.setLength(length)
-                        if (length == initialLength) {
-                            first = true
-                        }
-                    }
-                }
-                sb.append('}')
-                return true
-            } else if (value is PsiAnnotation) {
-                sb.append('@').append(value.qualifiedName)
-                return true
-            } else {
-                if (value is PsiTypeCastExpression) {
-                    val type = value.castType?.type
-                    val operand = value.operand
-                    if (type != null && operand is PsiAnnotationMemberValue) {
-                        sb.append('(')
-                        sb.append(type.canonicalText)
-                        sb.append(')')
-                        return appendSourceExpression(operand, sb, owner)
-                    }
-                }
-                val constant = ConstantEvaluator.evaluate(null, value)
-                if (constant != null) {
-                    return appendSourceLiteral(constant, sb, owner)
-                }
-            }
-            reporter.report(Errors.INTERNAL_ERROR, owner, "Unexpected annotation default value $value")
-            return false
-        }
-
-        private fun appendSourceLiteral(v: Any?, sb: StringBuilder, owner: Item): Boolean {
-            if (v == null) {
-                sb.append("null")
-                return true
-            }
-            when (v) {
-                is Int, is Long, is Boolean, is Byte, is Short -> {
-                    sb.append(v.toString())
-                    return true
-                }
-                is String -> {
-                    sb.append('"').append(javaEscapeString(v)).append('"')
-                    return true
-                }
-                is Float -> {
-                    return when (v) {
-                        Float.POSITIVE_INFINITY -> {
-                            // This convention (displaying fractions) is inherited from doclava
-                            sb.append("(1.0f/0.0f)"); true
-                        }
-                        Float.NEGATIVE_INFINITY -> {
-                            sb.append("(-1.0f/0.0f)"); true
-                        }
-                        Float.NaN -> {
-                            sb.append("(0.0f/0.0f)"); true
-                        }
-                        else -> {
-                            sb.append(canonicalizeFloatingPointString(v.toString()) + "f")
-                            true
-                        }
-                    }
-                }
-                is Double -> {
-                    return when (v) {
-                        Double.POSITIVE_INFINITY -> {
-                            // This convention (displaying fractions) is inherited from doclava
-                            sb.append("(1.0/0.0)"); true
-                        }
-                        Double.NEGATIVE_INFINITY -> {
-                            sb.append("(-1.0/0.0)"); true
-                        }
-                        Double.NaN -> {
-                            sb.append("(0.0/0.0)"); true
-                        }
-                        else -> {
-                            sb.append(canonicalizeFloatingPointString(v.toString()))
-                            true
-                        }
-                    }
-                }
-                is Char -> {
-                    sb.append('\'').append(javaEscapeString(v.toString())).append('\'')
-                    return true
-                }
-                else -> {
-                    reporter.report(Errors.INTERNAL_ERROR, owner, "Unexpected literal value $v")
-                }
-            }
-
-            return false
-        }
-    }
 }
diff --git a/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt b/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
new file mode 100644
index 0000000..786a0ff
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
@@ -0,0 +1,293 @@
+/*
+ * 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.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.ConstructorItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+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.psi.CodePrinter
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import com.android.utils.XmlUtils
+import java.io.PrintWriter
+import java.util.function.Predicate
+
+/**
+ * Writes out an XML format in the JDiff schema: See $ANDROID/external/jdiff/src/api.xsd
+ * (though limited to the same subset as generated by Doclava; and using the same
+ * conventions for the unspecified parts of the schema, such as what value to put
+ * in the deprecated string. It also uses the same XML formatting.)
+ *
+ * Known differences: Doclava seems to skip enum fields. We don't do that.
+ * Doclava seems to skip type parameters; we do the same.
+ */
+class JDiffXmlWriter(
+    private val writer: PrintWriter,
+    filterEmit: Predicate<Item>,
+    filterReference: Predicate<Item>,
+    private val preFiltered: Boolean
+) : ApiVisitor(
+    visitConstructorsAsMethods = false,
+    nestInnerClasses = false,
+    inlineInheritedFields = true,
+    methodComparator = MethodItem.comparator,
+    fieldComparator = FieldItem.comparator,
+    filterEmit = filterEmit,
+    filterReference = filterReference,
+    showUnannotated = options.showUnannotated
+) {
+    override fun visitCodebase(codebase: Codebase) {
+        writer.println("<api>")
+    }
+
+    override fun afterVisitCodebase(codebase: Codebase) {
+        writer.println("</api>")
+    }
+
+    override fun visitPackage(pkg: PackageItem) {
+        // Note: we apparently don't write package annotations anywhere
+        writer.println("<package name=\"${pkg.qualifiedName()}\"\n>")
+    }
+
+    override fun afterVisitPackage(pkg: PackageItem) {
+        writer.println("</package>")
+    }
+
+    override fun visitClass(cls: ClassItem) {
+        writer.print('<')
+        // XML format does not seem to special case annotations or enums
+        if (cls.isInterface()) {
+            writer.print("interface")
+        } else {
+            writer.print("class")
+        }
+        writer.print(" name=\"")
+        writer.print(cls.fullName())
+        // Note - to match doclava we don't write out the type parameter list
+        // (cls.typeParameterList()) in JDiff files!
+        writer.print("\"")
+
+        writeSuperClassAttribute(cls)
+
+        val modifiers = cls.modifiers
+        writer.print("\n abstract=\"")
+        writer.print(modifiers.isAbstract())
+        writer.print("\"\n static=\"")
+        writer.print(modifiers.isStatic())
+        writer.print("\"\n final=\"")
+        writer.print(modifiers.isFinal())
+        writer.print("\"\n deprecated=\"")
+        writer.print(deprecation(cls))
+        writer.print("\"\n visibility=\"")
+        writer.print(modifiers.getVisibilityModifiers())
+        writer.println("\"\n>")
+
+        writeInterfaceList(cls)
+    }
+
+    fun deprecation(item: Item): String {
+        return if (item.deprecated) {
+            "deprecated"
+        } else {
+            "not deprecated"
+        }
+    }
+
+    override fun afterVisitClass(cls: ClassItem) {
+        writer.print("</")
+        if (cls.isInterface()) {
+            writer.print("interface")
+        } else {
+            writer.print("class")
+        }
+        writer.println(">")
+    }
+
+    override fun visitConstructor(constructor: ConstructorItem) {
+        val modifiers = constructor.modifiers
+        writer.print("<constructor name=\"")
+        writer.print(constructor.containingClass().fullName())
+        writer.print("\"\n type=\"")
+        writer.print(constructor.containingClass().qualifiedName())
+        writer.print("\"\n static=\"")
+        writer.print(modifiers.isStatic())
+        writer.print("\"\n final=\"")
+        writer.print(modifiers.isFinal())
+        writer.print("\"\n deprecated=\"")
+        writer.print(deprecation(constructor))
+        writer.print("\"\n visibility=\"")
+        writer.print(modifiers.getVisibilityModifiers())
+        writer.println("\"\n>")
+
+        // Note - to match doclava we don't write out the type parameter list
+        // (constructor.typeParameterList()) in JDiff files!
+
+        writeParameterList(constructor)
+        writeThrowsList(constructor)
+        writer.println("</constructor>")
+    }
+
+    override fun visitField(field: FieldItem) {
+        val modifiers = field.modifiers
+
+        val initialValue = field.initialValue(true)
+        val value = if (initialValue != null) {
+            XmlUtils.toXmlAttributeValue(CodePrinter.constantToSource(initialValue))
+        } else null
+
+        val fullTypeName = XmlUtils.toXmlAttributeValue(field.type().toTypeString())
+
+        writer.print("<field name=\"")
+        writer.print(field.name())
+        writer.print("\"\n type=\"")
+        writer.print(fullTypeName)
+        writer.print("\"\n transient=\"")
+        writer.print(modifiers.isTransient())
+        writer.print("\"\n volatile=\"")
+        writer.print(modifiers.isVolatile())
+        value?.let {
+            writer.print("\"\n value=\"")
+            writer.print(it)
+        }
+        writer.print("\"\n static=\"")
+        writer.print(modifiers.isStatic())
+        writer.print("\"\n final=\"")
+        writer.print(modifiers.isFinal())
+        writer.print("\"\n deprecated=\"")
+        writer.print(deprecation(field))
+        writer.print("\"\n visibility=\"")
+        writer.print(modifiers.getVisibilityModifiers())
+        writer.println("\"\n>")
+
+        writer.println("</field>")
+    }
+
+    override fun visitProperty(property: PropertyItem) {
+        // Not supported by JDiff
+    }
+
+    override fun visitMethod(method: MethodItem) {
+        val modifiers = method.modifiers
+
+        // Note - to match doclava we don't write out the type parameter list
+        // (method.typeParameterList()) in JDiff files!
+
+        writer.print("<method name=\"")
+        writer.print(method.name())
+        method.returnType()?.let {
+            writer.print("\"\n return=\"")
+            writer.print(XmlUtils.toXmlAttributeValue(it.toTypeString()))
+        }
+        writer.print("\"\n abstract=\"")
+        writer.print(modifiers.isAbstract())
+        writer.print("\"\n native=\"")
+        writer.print(modifiers.isNative())
+        writer.print("\"\n synchronized=\"")
+        writer.print(modifiers.isSynchronized())
+        writer.print("\"\n static=\"")
+        writer.print(modifiers.isStatic())
+        writer.print("\"\n final=\"")
+        writer.print(modifiers.isFinal())
+        writer.print("\"\n deprecated=\"")
+        writer.print(deprecation(method))
+        writer.print("\"\n visibility=\"")
+        writer.print(modifiers.getVisibilityModifiers())
+        writer.println("\"\n>")
+
+        writeParameterList(method)
+        writeThrowsList(method)
+        writer.println("</method>")
+    }
+
+    private fun writeSuperClassAttribute(cls: ClassItem) {
+        val superClass = if (preFiltered)
+            cls.superClassType()
+        else cls.filteredSuperClassType(filterReference)
+
+        val superClassString =
+            when {
+                superClass != null -> {
+                    // doclava seems to include java.lang.Object for classes but not interfaces
+                    if (!cls.isClass() && superClass.isJavaLangObject()) {
+                        return
+                    }
+                    XmlUtils.toXmlAttributeValue(
+                        superClass.toTypeString(
+                            erased = compatibility.omitTypeParametersInInterfaces,
+                            context = superClass.asClass()
+                        )
+                    )
+                }
+                cls.isAnnotationType() -> JAVA_LANG_ANNOTATION
+                cls.isEnum() -> JAVA_LANG_ENUM
+                else -> return
+            }
+        writer.print("\n extends=\"")
+        writer.print(superClassString)
+        writer.print("\"")
+    }
+
+    private fun writeInterfaceList(cls: ClassItem) {
+        val interfaces = if (preFiltered)
+            cls.interfaceTypes().asSequence()
+        else cls.filteredInterfaceTypes(filterReference).asSequence()
+
+        if (interfaces.any()) {
+            interfaces.sortedWith(TypeItem.comparator).forEach { item ->
+                writer.print("<implements name=\"")
+                val type = item.toTypeString(erased = compatibility.omitTypeParametersInInterfaces, context = cls)
+                val escapedType = XmlUtils.toXmlAttributeValue(type)
+                writer.print(escapedType)
+                writer.println("\">\n</implements>")
+            }
+        }
+    }
+
+    private fun writeParameterList(method: MethodItem) {
+        method.parameters().asSequence().forEach { parameter ->
+            // 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(XmlUtils.toXmlAttributeValue(parameter.type().toTypeString()))
+            writer.println("\">")
+            writer.println("</parameter>")
+        }
+    }
+
+    private fun writeThrowsList(method: MethodItem) {
+        val throws = when {
+            preFiltered -> method.throwsTypes().asSequence()
+            compatibility.filterThrowsClasses -> method.filteredThrowsTypes(filterReference).asSequence()
+            else -> method.throwsTypes().asSequence()
+        }
+        if (throws.any()) {
+            throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEach { type ->
+                writer.print("<exception name=\"")
+                writer.print(type.fullName())
+                writer.print("\" type=\"")
+                writer.print(type.qualifiedName())
+                writer.println("\">")
+                writer.println("</exception>")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
index 7af08d1..ccfe367 100644
--- a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
+++ b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
@@ -35,10 +35,9 @@
 class KotlinInteropChecks {
     fun check(codebase: Codebase) {
         codebase.accept(object : ApiVisitor(
-            codebase,
             // Sort by source order such that warnings follow source line number order
-            fieldComparator = FieldItem.comparator,
-            methodComparator = MethodItem.sourceOrderComparator
+            methodComparator = MethodItem.sourceOrderComparator,
+            fieldComparator = FieldItem.comparator
         ) {
             override fun visitMethod(method: MethodItem) {
                 checkMethod(method)
@@ -82,7 +81,6 @@
             if (checked) {
                 val annotation = method.modifiers.findAnnotation("kotlin.jvm.Throws")
                 if (annotation != null) {
-                    annotation.attributes().first().name
                     val attribute =
                         annotation.findAttribute("exceptionClasses") ?: annotation.findAttribute("value")
                         ?: annotation.attributes().firstOrNull()
@@ -232,8 +230,7 @@
         if (parameters.isNotEmpty() && method.isJava()) {
             // Public java parameter names should also not use Kotlin keywords as names
             for (parameter in parameters) {
-                val defaultValue = parameter.defaultValue()
-                if (defaultValue != null) {
+                if (parameter.hasDefaultValue()) {
                     haveDefault = true
                     break
                 }
@@ -345,7 +342,7 @@
     }
 
     /** Returns true if the given string is a reserved Java keyword  */
-    fun isJavaKeyword(keyword: String): Boolean {
+    private fun isJavaKeyword(keyword: String): Boolean {
         // TODO when we built on top of IDEA core replace this with
         //   JavaLexer.isKeyword(candidate, LanguageLevel.JDK_1_5)
         when (keyword) {
diff --git a/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt b/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt
new file mode 100644
index 0000000..f80725e
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt
@@ -0,0 +1,227 @@
+/*
+ * 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.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
+import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import com.google.common.io.Files
+import java.io.File
+import java.io.PrintWriter
+import java.nio.charset.StandardCharsets
+
+private const val RETURN_LABEL = "return value"
+
+/**
+ * Class that validates nullability annotations in the codebase.
+ */
+class NullabilityAnnotationsValidator {
+
+    private enum class ErrorType {
+        MULTIPLE,
+        ON_PRIMITIVE,
+        BAD_TYPE_PARAM,
+    }
+
+    private interface Issue {
+        val method: MethodItem
+    }
+
+    private data class Error(
+        override val method: MethodItem,
+        val label: String,
+        val type: ErrorType
+    ) : Issue {
+        override fun toString(): String {
+            return "ERROR: $method, $label, $type"
+        }
+    }
+
+    private enum class WarningType {
+        MISSING,
+    }
+
+    private data class Warning(
+        override val method: MethodItem,
+        val label: String,
+        val type: WarningType
+    ) : Issue {
+        override fun toString(): String {
+            return "WARNING: $method, $label, $type"
+        }
+    }
+
+    private val errors: MutableList<Error> = mutableListOf()
+    private val warnings: MutableList<Warning> = mutableListOf()
+
+    /**
+     * Validate all of the methods in the classes named in [topLevelClassNames] and in all their
+     * nested classes. Violations are stored by the validator and will be reported by [report].
+     */
+    fun validateAll(codebase: Codebase, topLevelClassNames: List<String>) {
+        for (topLevelClassName in topLevelClassNames) {
+            val topLevelClass = codebase.findClass(topLevelClassName)
+                ?: throw DriverException("Trying to validate nullability annotations for class $topLevelClassName which could not be found in main codebase")
+            // Visit methods to check their return type, and parameters to check them. Don't visit
+            // constructors as we don't want to check their return types. This visits members of
+            // inner classes as well.
+            topLevelClass.accept(object : ApiVisitor(visitConstructorsAsMethods = false) {
+
+                override fun visitMethod(method: MethodItem) {
+                    checkItem(method, RETURN_LABEL, method.returnType(), method)
+                }
+
+                override fun visitParameter(parameter: ParameterItem) {
+                    checkItem(parameter.containingMethod(), parameter.toString(), parameter.type(), parameter)
+                }
+            })
+        }
+    }
+
+    /**
+     * As [validateAll], reading the list of class names from [topLevelClassesList]. The file names
+     * one top-level class per line, and lines starting with # are skipped. Does nothing if
+     * [topLevelClassesList] is null.
+     */
+    fun validateAllFrom(codebase: Codebase, topLevelClassesList: File?) {
+        if (topLevelClassesList != null) {
+            val classes =
+                Files.readLines(topLevelClassesList, StandardCharsets.UTF_8)
+                    .filterNot { it.isBlank() }
+                    .map { it.trim() }
+                    .filterNot { it.startsWith("#") }
+            validateAll(codebase, classes)
+        }
+    }
+
+    private fun checkItem(method: MethodItem, label: String, type: TypeItem?, item: Item) {
+        if (type == null) {
+            throw DriverException("Missing type on $method item $label")
+        }
+        if (method.isEnumValueOfString()) {
+            // Don't validate an enum's valueOf(String) method, which doesn't exist in source.
+            return
+        }
+        val annotations = item.modifiers.annotations()
+        val nullabilityAnnotations = annotations.filter(this::isAnyNullabilityAnnotation)
+        if (nullabilityAnnotations.size > 1) {
+            errors.add(Error(method, label, ErrorType.MULTIPLE))
+            return
+        }
+        checkItemNullability(type, nullabilityAnnotations.firstOrNull(), method, label)
+        // TODO: When type annotations are supported, we should check all the type parameters too.
+        // We can do invoke this method recursively, using a suitably descriptive label.
+        assert(!SUPPORT_TYPE_USE_ANNOTATIONS)
+    }
+
+    private fun isNullFromTypeParam(it: AnnotationItem) =
+        it.qualifiedName()?.endsWith("NullFromTypeParam") == true
+
+    private fun isAnyNullabilityAnnotation(it: AnnotationItem) =
+        it.isNullnessAnnotation() || isNullFromTypeParam(it)
+
+    private fun checkItemNullability(
+        type: TypeItem,
+        nullability: AnnotationItem?,
+        method: MethodItem,
+        label: String
+    ) {
+        when {
+            // Primitive (may not have nullability):
+            type.primitive -> {
+                if (nullability != null) {
+                    errors.add(Error(method, label, ErrorType.ON_PRIMITIVE))
+                }
+            }
+            // Array (see comment):
+            type.arrayDimensions() > 0 -> {
+                // TODO: When type annotations are supported, we should check the annotation on both
+                // the array itself and the component type. Until then, there's nothing we can
+                // safely do, because e.g. a method parameter declared as '@NonNull Object[]' means
+                // a non-null array of unspecified-nullability Objects if that is a PARAMETER
+                // annotation, but an unspecified-nullability array of non-null Objects if that is a
+                // TYPE_USE annotation.
+                assert(!SUPPORT_TYPE_USE_ANNOTATIONS)
+            }
+            // Type parameter reference (should have nullability):
+            type.asTypeParameter() != null -> {
+                if (nullability == null) {
+                    warnings.add(Warning(method, label, WarningType.MISSING))
+                }
+            }
+            // Anything else (should have nullability, may not be null-from-type-param):
+            else -> {
+                when {
+                    nullability == null -> warnings.add(Warning(method, label, WarningType.MISSING))
+                    isNullFromTypeParam(nullability) ->
+                        errors.add(Error(method, label, ErrorType.BAD_TYPE_PARAM))
+                }
+            }
+        }
+    }
+
+    /**
+     * Report on any violations found during earlier validation calls.
+     */
+    fun report() {
+        errors.sortBy { it.toString() }
+        warnings.sortBy { it.toString() }
+        val warningsTxtFile = options.nullabilityWarningsTxt
+        val fatalIssues = mutableListOf<Issue>()
+        val nonFatalIssues = mutableListOf<Issue>()
+
+        // Errors are fatal iff options.nullabilityErrorsFatal is set.
+        if (options.nullabilityErrorsFatal) {
+            fatalIssues.addAll(errors)
+        } else {
+            nonFatalIssues.addAll(errors)
+        }
+
+        // Warnings go to the configured .txt file if present, which means they're not fatal.
+        // Else they're fatal iff options.nullabilityErrorsFatal is set.
+        if (warningsTxtFile == null && options.nullabilityErrorsFatal) {
+            fatalIssues.addAll(warnings)
+        } else {
+            nonFatalIssues.addAll(warnings)
+        }
+
+        // Fatal issues are thrown.
+        if (fatalIssues.isNotEmpty()) {
+            fatalIssues.forEach { reporter.error(it.method, it.toString(), Errors.INVALID_NULLABILITY_ANNOTATION) }
+        }
+
+        // Non-fatal issues are written to the warnings .txt file if present, else logged.
+        if (warningsTxtFile != null) {
+            PrintWriter(Files.asCharSink(warningsTxtFile, Charsets.UTF_8).openBufferedStream()).use { w ->
+                nonFatalIssues.forEach { w.println(it) }
+            }
+        } else {
+            nonFatalIssues.forEach { reporter.warning(it.method, "Nullability issue: $it") }
+        }
+    }
+}
+
+private fun MethodItem.isEnumValueOfString() =
+    containingClass().isEnum() && name() == "valueOf" && parameters().map {
+        it.type().toTypeString()
+    } == listOf("java.lang.String")
diff --git a/src/main/java/com/android/tools/metalava/NullnessMigration.kt b/src/main/java/com/android/tools/metalava/NullnessMigration.kt
index 8cb8d6b..0f889da 100644
--- a/src/main/java/com/android/tools/metalava/NullnessMigration.kt
+++ b/src/main/java/com/android/tools/metalava/NullnessMigration.kt
@@ -21,6 +21,7 @@
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
 import com.android.tools.metalava.model.TypeItem
 
 /**
@@ -42,40 +43,35 @@
         }
     }
 
-    override fun added(new: Item) {
-        // Translate newly added items into RecentlyNull/RecentlyNonNull
-        if (hasNullnessInformation(new)) {
-            markRecent(new)
-        }
-    }
+    // Note: We don't override added(new: Item) to mark newly added methods as newly
+    // having nullness annotations: those APIs are themselves new, so there's no reason
+    // to mark the nullness contract as migration (warning- rather than error-severity)
+
     override fun compare(old: MethodItem, new: MethodItem) {
-        val newType = new.returnType() ?: return
-        val oldType = old.returnType() ?: return
-        checkType(oldType, newType)
+        @Suppress("ConstantConditionIf")
+        if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+            val newType = new.returnType() ?: return
+            val oldType = old.returnType() ?: return
+            checkType(oldType, newType)
+        }
     }
 
     override fun compare(old: FieldItem, new: FieldItem) {
-        val newType = new.type()
-        val oldType = old.type()
-        checkType(oldType, newType)
+        @Suppress("ConstantConditionIf")
+        if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+            val newType = new.type()
+            val oldType = old.type()
+            checkType(oldType, newType)
+        }
     }
 
     override fun compare(old: ParameterItem, new: ParameterItem) {
-        val newType = new.type()
-        val oldType = old.type()
-        checkType(oldType, newType)
-    }
-
-    override fun added(new: MethodItem) {
-        checkType(new.returnType() ?: return)
-    }
-
-    override fun added(new: FieldItem) {
-        checkType(new.type())
-    }
-
-    override fun added(new: ParameterItem) {
-        checkType(new.type())
+        @Suppress("ConstantConditionIf")
+        if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+            val newType = new.type()
+            val oldType = old.type()
+            checkType(oldType, newType)
+        }
     }
 
     private fun hasNullnessInformation(type: TypeItem): Boolean {
@@ -86,18 +82,13 @@
     private fun checkType(old: TypeItem, new: TypeItem) {
         if (hasNullnessInformation(new)) {
             if (old.toTypeString(false, true, false) !=
-                new.toTypeString(false, true, false)) {
+                new.toTypeString(false, true, false)
+            ) {
                 new.markRecent()
             }
         }
     }
 
-    private fun checkType(new: TypeItem) {
-        if (hasNullnessInformation(new)) {
-            new.markRecent()
-        }
-    }
-
     private fun markRecent(new: Item) {
         val annotation = findNullnessAnnotation(new) ?: return
         // Nullness information change: Add migration annotation
@@ -126,14 +117,5 @@
         private fun isNonNull(item: Item): Boolean {
             return item.modifiers.annotations().any { it.isNonNull() }
         }
-
-        private fun isRecentlyMigrated(item: Item): Boolean {
-            return item.modifiers.annotations().any { isRecentlyMigrated(it.qualifiedName() ?: "") }
-        }
-
-        private fun isRecentlyMigrated(qualifiedName: String): Boolean {
-            return qualifiedName.endsWith(".RecentlyNullable") ||
-                qualifiedName.endsWith(".RecentlyNonNull")
-        }
     }
 }
diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt
index 5bd8415..4de2b1d 100644
--- a/src/main/java/com/android/tools/metalava/Options.kt
+++ b/src/main/java/com/android/tools/metalava/Options.kt
@@ -18,6 +18,7 @@
 
 import com.android.SdkConstants
 import com.android.sdklib.SdkVersionInfo
+import com.android.tools.metalava.CompatibilityCheck.CheckRequest
 import com.android.tools.metalava.doclava1.Errors
 import com.android.utils.SdkUtils.wrap
 import com.google.common.base.CharMatcher
@@ -37,80 +38,97 @@
 
 private const val MAX_LINE_WIDTH = 90
 
-private const val ARGS_COMPAT_OUTPUT = "--compatible-output"
-private const val ARG_HELP = "--help"
-private const val ARG_VERSION = "--version"
-private const val ARG_QUIET = "--quiet"
-private const val ARG_VERBOSE = "--verbose"
-private const val ARG_CLASS_PATH = "--classpath"
-private const val ARG_SOURCE_PATH = "--source-path"
-private const val ARG_SOURCE_FILES = "--source-files"
-private const val ARG_API = "--api"
-private const val ARG_PRIVATE_API = "--private-api"
-private const val ARG_DEX_API = "--dex-api"
-private const val ARG_PRIVATE_DEX_API = "--private-dex-api"
-private const val ARG_SDK_VALUES = "--sdk-values"
-private const val ARG_REMOVED_API = "--removed-api"
-private const val ARG_REMOVED_DEX_API = "--removed-dex-api"
-private const val ARG_MERGE_ANNOTATIONS = "--merge-annotations"
-private const val ARG_INPUT_API_JAR = "--input-api-jar"
-private const val ARG_EXACT_API = "--exact-api"
-private const val ARG_STUBS = "--stubs"
-private const val ARG_DOC_STUBS = "--doc-stubs"
-private const val ARG_STUBS_SOURCE_LIST = "--write-stubs-source-list"
-private const val ARG_DOC_STUBS_SOURCE_LIST = "--write-doc-stubs-source-list"
-private const val ARG_PROGUARD = "--proguard"
-private const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations"
-private const val ARG_EXCLUDE_ANNOTATIONS = "--exclude-annotations"
-private const val ARG_HIDE_PACKAGE = "--hide-package"
-private const val ARG_MANIFEST = "--manifest"
-private const val ARG_PREVIOUS_API = "--previous-api"
-private const val ARG_CURRENT_API = "--current-api"
-private const val ARG_MIGRATE_NULLNESS = "--migrate-nullness"
-private const val ARG_CHECK_COMPATIBILITY = "--check-compatibility"
-private const val ARG_INPUT_KOTLIN_NULLS = "--input-kotlin-nulls"
-private const val ARG_OUTPUT_KOTLIN_NULLS = "--output-kotlin-nulls"
-private const val ARG_OUTPUT_DEFAULT_VALUES = "--output-default-values"
-private const val ARG_ANNOTATION_COVERAGE_STATS = "--annotation-coverage-stats"
-private const val ARG_ANNOTATION_COVERAGE_OF = "--annotation-coverage-of"
-private const val ARG_WRITE_CLASS_COVERAGE_TO = "--write-class-coverage-to"
-private const val ARG_WRITE_MEMBER_COVERAGE_TO = "--write-member-coverage-to"
-private const val ARG_WARNINGS_AS_ERRORS = "--warnings-as-errors"
-private const val ARG_LINTS_AS_ERRORS = "--lints-as-errors"
-private const val ARG_SHOW_ANNOTATION = "--show-annotation"
-private const val ARG_SHOW_UNANNOTATED = "--show-unannotated"
-private const val ARG_COLOR = "--color"
-private const val ARG_NO_COLOR = "--no-color"
-private const val ARG_OMIT_COMMON_PACKAGES = "--omit-common-packages"
-private const val ARG_SKIP_JAVA_IN_COVERAGE_REPORT = "--skip-java-in-coverage-report"
-private const val ARG_NO_BANNER = "--no-banner"
-private const val ARG_ERROR = "--error"
-private const val ARG_WARNING = "--warning"
-private const val ARG_LINT = "--lint"
-private const val ARG_HIDE = "--hide"
-private const val ARG_UNHIDE_CLASSPATH_CLASSES = "--unhide-classpath-classes"
-private const val ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES = "--allow-referencing-unknown-classes"
-private const val ARG_NO_UNKNOWN_CLASSES = "--no-unknown-classes"
-private const val ARG_APPLY_API_LEVELS = "--apply-api-levels"
-private const val ARG_GENERATE_API_LEVELS = "--generate-api-levels"
-private const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern"
-private const val ARG_CURRENT_VERSION = "--current-version"
-private const val ARG_CURRENT_CODENAME = "--current-codename"
-private const val ARG_CURRENT_JAR = "--current-jar"
-private const val ARG_CHECK_KOTLIN_INTEROP = "--check-kotlin-interop"
-private const val ARG_PUBLIC = "--public"
-private const val ARG_PROTECTED = "--protected"
-private const val ARG_PACKAGE = "--package"
-private const val ARG_PRIVATE = "--private"
-private const val ARG_HIDDEN = "--hidden"
-private const val ARG_NO_DOCS = "--no-docs"
-private const val ARG_GENERATE_DOCUMENTATION = "--generate-documentation"
-private const val ARG_JAVA_SOURCE = "--java-source"
-private const val ARG_REGISTER_ARTIFACT = "--register-artifact"
-private const val ARG_COPY_ANNOTATIONS = "--copy-annotations"
-private const val ARG_INCLUDE_ANNOTATION_CLASSES = "--include-annotation-classes"
-private const val ARG_REWRITE_ANNOTATIONS = "--rewrite-annotations"
-private const val ARG_INCLUDE_SOURCE_RETENTION = "--include-source-retention"
+const val ARG_COMPAT_OUTPUT = "--compatible-output"
+const val ARG_FORMAT = "--format"
+const val ARG_HELP = "--help"
+const val ARG_VERSION = "--version"
+const val ARG_QUIET = "--quiet"
+const val ARG_VERBOSE = "--verbose"
+const val ARG_CLASS_PATH = "--classpath"
+const val ARG_SOURCE_PATH = "--source-path"
+const val ARG_SOURCE_FILES = "--source-files"
+const val ARG_API = "--api"
+const val ARG_XML_API = "--api-xml"
+const val ARG_CONVERT_TO_JDIFF = "--convert-to-jdiff"
+const val ARG_PRIVATE_API = "--private-api"
+const val ARG_DEX_API = "--dex-api"
+const val ARG_PRIVATE_DEX_API = "--private-dex-api"
+const val ARG_SDK_VALUES = "--sdk-values"
+const val ARG_REMOVED_API = "--removed-api"
+const val ARG_REMOVED_DEX_API = "--removed-dex-api"
+const val ARG_MERGE_QUALIFIER_ANNOTATIONS = "--merge-qualifier-annotations"
+const val ARG_MERGE_INCLUSION_ANNOTATIONS = "--merge-inclusion-annotations"
+const val ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS = "--validate-nullability-from-merged-stubs"
+const val ARG_VALIDATE_NULLABILITY_FROM_LIST = "--validate-nullability-from-list"
+const val ARG_NULLABILITY_WARNINGS_TXT = "--nullability-warnings-txt"
+const val ARG_NULLABILITY_ERRORS_NON_FATAL = "--nullability-errors-non-fatal"
+const val ARG_INPUT_API_JAR = "--input-api-jar"
+const val ARG_EXACT_API = "--exact-api"
+const val ARG_STUBS = "--stubs"
+const val ARG_DOC_STUBS = "--doc-stubs"
+const val ARG_STUBS_SOURCE_LIST = "--write-stubs-source-list"
+const val ARG_DOC_STUBS_SOURCE_LIST = "--write-doc-stubs-source-list"
+const val ARG_PROGUARD = "--proguard"
+const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations"
+const val ARG_EXCLUDE_ANNOTATIONS = "--exclude-annotations"
+const val ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS = "--exclude-documentation-from-stubs"
+const val ARG_HIDE_PACKAGE = "--hide-package"
+const val ARG_MANIFEST = "--manifest"
+const val ARG_MIGRATE_NULLNESS = "--migrate-nullness"
+const val ARG_CHECK_COMPATIBILITY = "--check-compatibility"
+const val ARG_CHECK_COMPATIBILITY_API_CURRENT = "--check-compatibility:api:current"
+const val ARG_CHECK_COMPATIBILITY_API_RELEASED = "--check-compatibility:api:released"
+const val ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT = "--check-compatibility:removed:current"
+const val ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED = "--check-compatibility:removed:released"
+const val ARG_INPUT_KOTLIN_NULLS = "--input-kotlin-nulls"
+const val ARG_OUTPUT_KOTLIN_NULLS = "--output-kotlin-nulls"
+const val ARG_OUTPUT_DEFAULT_VALUES = "--output-default-values"
+const val ARG_ANNOTATION_COVERAGE_STATS = "--annotation-coverage-stats"
+const val ARG_ANNOTATION_COVERAGE_OF = "--annotation-coverage-of"
+const val ARG_WRITE_CLASS_COVERAGE_TO = "--write-class-coverage-to"
+const val ARG_WRITE_MEMBER_COVERAGE_TO = "--write-member-coverage-to"
+const val ARG_WARNINGS_AS_ERRORS = "--warnings-as-errors"
+const val ARG_LINTS_AS_ERRORS = "--lints-as-errors"
+const val ARG_SHOW_ANNOTATION = "--show-annotation"
+const val ARG_SHOW_SINGLE_ANNOTATION = "--show-single-annotation"
+const val ARG_HIDE_ANNOTATION = "--hide-annotation"
+const val ARG_SHOW_UNANNOTATED = "--show-unannotated"
+const val ARG_COLOR = "--color"
+const val ARG_NO_COLOR = "--no-color"
+const val ARG_OMIT_COMMON_PACKAGES = "--omit-common-packages"
+const val ARG_SKIP_JAVA_IN_COVERAGE_REPORT = "--skip-java-in-coverage-report"
+const val ARG_NO_BANNER = "--no-banner"
+const val ARG_ERROR = "--error"
+const val ARG_WARNING = "--warning"
+const val ARG_LINT = "--lint"
+const val ARG_HIDE = "--hide"
+const val ARG_UNHIDE_CLASSPATH_CLASSES = "--unhide-classpath-classes"
+const val ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES = "--allow-referencing-unknown-classes"
+const val ARG_NO_UNKNOWN_CLASSES = "--no-unknown-classes"
+const val ARG_APPLY_API_LEVELS = "--apply-api-levels"
+const val ARG_GENERATE_API_LEVELS = "--generate-api-levels"
+const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern"
+const val ARG_CURRENT_VERSION = "--current-version"
+const val ARG_CURRENT_CODENAME = "--current-codename"
+const val ARG_CURRENT_JAR = "--current-jar"
+const val ARG_CHECK_KOTLIN_INTEROP = "--check-kotlin-interop"
+const val ARG_PUBLIC = "--public"
+const val ARG_PROTECTED = "--protected"
+const val ARG_PACKAGE = "--package"
+const val ARG_PRIVATE = "--private"
+const val ARG_HIDDEN = "--hidden"
+const val ARG_NO_DOCS = "--no-docs"
+const val ARG_JAVA_SOURCE = "--java-source"
+const val ARG_REGISTER_ARTIFACT = "--register-artifact"
+const val ARG_INCLUDE_ANNOTATIONS = "--include-annotations"
+const val ARG_COPY_ANNOTATIONS = "--copy-annotations"
+const val ARG_INCLUDE_ANNOTATION_CLASSES = "--include-annotation-classes"
+const val ARG_REWRITE_ANNOTATIONS = "--rewrite-annotations"
+const val ARG_INCLUDE_SOURCE_RETENTION = "--include-source-retention"
+const val ARG_INCLUDE_SIG_VERSION = "--include-signature-version"
+const val ARG_UPDATE_API = "--update-api"
+const val ARG_DEX_API_MAPPING = "--dex-api-mapping"
+const val ARG_GENERATE_DOCUMENTATION = "--generate-documentation"
 
 class Options(
     args: Array<String>,
@@ -128,18 +146,24 @@
     private val mutableClassPath: MutableList<File> = mutableListOf()
     /** Internal list backing [showAnnotations] */
     private val mutableShowAnnotations: MutableList<String> = mutableListOf()
+    /** Internal list backing [showSingleAnnotations] */
+    private val mutableShowSingleAnnotations: MutableList<String> = mutableListOf()
     /** Internal list backing [hideAnnotations] */
     private val mutableHideAnnotations: MutableList<String> = mutableListOf()
     /** Internal list backing [stubImportPackages] */
     private val mutableStubImportPackages: MutableSet<String> = mutableSetOf()
-    /** Internal list backing [mergeAnnotations] */
-    private val mutableMergeAnnotations: MutableList<File> = mutableListOf()
+    /** Internal list backing [mergeQualifierAnnotations] */
+    private val mutableMergeQualifierAnnotations: MutableList<File> = mutableListOf()
+    /** Internal list backing [mergeInclusionAnnotations] */
+    private val mutableMergeInclusionAnnotations: MutableList<File> = mutableListOf()
     /** Internal list backing [annotationCoverageOf] */
     private val mutableAnnotationCoverageOf: MutableList<File> = mutableListOf()
     /** Internal list backing [hidePackages] */
     private val mutableHidePackages: MutableList<String> = mutableListOf()
     /** Internal list backing [skipEmitPackages] */
     private val mutableSkipEmitPackages: MutableList<String> = mutableListOf()
+    /** Internal list backing [convertToXmlFiles] */
+    private val mutableConvertToXmlFiles: MutableList<Pair<File, File>> = mutableListOf()
 
     /** Ignored flags we've already warned about - store here such that we don't keep reporting them */
     private val alreadyWarned: MutableSet<String> = mutableSetOf()
@@ -157,6 +181,52 @@
     var noDocs = false
 
     /**
+     * Validator for nullability annotations, if validation is enabled.
+     */
+    var nullabilityAnnotationsValidator: NullabilityAnnotationsValidator? = null
+
+    /**
+     * Whether nullability validation errors should be considered fatal.
+     */
+    var nullabilityErrorsFatal = true
+
+    /**
+     * A file to write non-fatal nullability validation issues to. If null, all issues are treated
+     * as fatal or else logged as warnings, depending on the value of [nullabilityErrorsFatal].
+     */
+    var nullabilityWarningsTxt: File? = null
+
+    /**
+     * Whether to validate nullability for all the classes where we are merging annotations from
+     * external java stub files. If true, [nullabilityAnnotationsValidator] must be set.
+     */
+    var validateNullabilityFromMergedStubs = false
+
+    /**
+     * A file containing a list of classes whose nullability annotations should be validated. If
+     * set, [nullabilityAnnotationsValidator] must also be set.
+     */
+    var validateNullabilityFromList: File? = null
+
+    /**
+     * Whether to include element documentation (javadoc and KDoc) is in the generated stubs.
+     * (Copyright notices are not affected by this, they are always included. Documentation stubs
+     * (--doc-stubs) are not affected.)
+     */
+    var includeDocumentationInStubs = true
+
+    /**
+     * Whether metalava is invoked as part of updating the API files. When this is true, metalava
+     * should *cancel* various other flags that are also being passed in, such as --check-compatibility.
+     * This is there to ease integration in the build system: for a given target, the build system will
+     * pass all the applicable flags (--stubs, --api, --check-compatibility, --generate-documentation, etc),
+     * and this integration is re-used for the update-api facility where we *only* want to generate the
+     * signature files. This avoids having duplicate metalava invocation logic where potentially newly
+     * added flags are missing in one of the invocations etc.
+     */
+    var updateApi = false
+
+    /**
      * Whether signature files should emit in "compat" mode, preserving the various
      * quirks of the previous signature file format -- this will for example use a non-standard
      * modifier ordering, it will call enums interfaces, etc. See the [Compatibility] class
@@ -196,9 +266,19 @@
     var sources: List<File> = mutableSources
 
     /** Whether to include APIs with annotations (intended for documentation purposes) */
-    var showAnnotations = mutableShowAnnotations
+    var showAnnotations: List<String> = mutableShowAnnotations
 
-    /** Whether to include unannotated elements if {@link #showAnnotations} is set */
+    /**
+     * Like [showAnnotations], but does not work recursively. Note that
+     * these annotations are *also* show annotations and will be added to the above list;
+     * this is a subset.
+     */
+    val showSingleAnnotations: List<String> = mutableShowSingleAnnotations
+
+    /**
+     * Whether to include unannotated elements if {@link #showAnnotations} is set.
+     * Note: This only applies to signature files, not stub files.
+     */
     var showUnannotated = false
 
     /** Whether to validate the API for Kotlin interop */
@@ -211,15 +291,15 @@
     var stubImportPackages: Set<String> = mutableStubImportPackages
 
     /** Packages to exclude/hide */
-    var hidePackages = mutableHidePackages
+    var hidePackages: List<String> = mutableHidePackages
 
     /** Packages that we should skip generating even if not hidden; typically only used by tests */
-    var skipEmitPackages = mutableSkipEmitPackages
+    var skipEmitPackages: List<String> = mutableSkipEmitPackages
 
     var showAnnotationOverridesVisibility: Boolean = false
 
     /** Annotations to hide */
-    var hideAnnotations = mutableHideAnnotations
+    var hideAnnotations: List<String> = mutableHideAnnotations
 
     /** Whether to report warnings and other diagnostics along the way */
     var quiet = false
@@ -247,12 +327,18 @@
     /** If set, a file to write an API file to. Corresponds to the --api/-api flag. */
     var apiFile: File? = null
 
+    /** Like [apiFile], but with JDiff xml format. */
+    var apiXmlFile: File? = null
+
     /** If set, a file to write the private API file to. Corresponds to the --private-api/-privateApi flag. */
     var privateApiFile: File? = null
 
-    /** If set, a file to write the DEX signatures to. Corresponds to --dex-api. */
+    /** If set, a file to write the DEX signatures to. Corresponds to [ARG_DEX_API]. */
     var dexApiFile: File? = null
 
+    /** If set, a file to write all DEX signatures and file locations to. Corresponds to [ARG_DEX_API_MAPPING]. */
+    var dexApiMappingFile: File? = null
+
     /** If set, a file to write the private DEX signatures to. Corresponds to --private-dex-api. */
     var privateDexApiFile: File? = null
 
@@ -300,27 +386,22 @@
     var omitRuntimePackageStats = false
 
     /** Whether to generate annotations into the stubs */
-    var generateAnnotations = true
+    var generateAnnotations = false
 
     /**
-     * A signature file for the previous version of this API (for nullness
-     * migration, possibly for compatibility checking (if [currentApi] is not defined), etc.)
+     * A signature file to migrate nullness data from
      */
-    var previousApi: File? = null
+    var migrateNullsFrom: File? = null
 
-    /**
-     * A signature file for the current version of this API (for compatibility checks).
-     */
-    var currentApi: File? = null
+    /** Private backing list for [compatibilityChecks]] */
+    private var mutableCompatibilityChecks: MutableList<CheckRequest> = mutableListOf()
 
-    /** Whether we should check API compatibility based on the previous API in [previousApi] */
-    var checkCompatibility: Boolean = false
-
-    /** Whether we should migrate nulls based on the previous API in [previousApi] */
-    var migrateNulls: Boolean = false
+    /** The list of compatibility checks to run */
+    val compatibilityChecks: List<CheckRequest> = mutableCompatibilityChecks
 
     /** Existing external annotation files to merge in */
-    var mergeAnnotations: List<File> = mutableMergeAnnotations
+    var mergeQualifierAnnotations: List<File> = mutableMergeQualifierAnnotations
+    var mergeInclusionAnnotations: List<File> = mutableMergeInclusionAnnotations
 
     /** Set of jars and class files for existing apps that we want to measure coverage of */
     var annotationCoverageOf: List<File> = mutableAnnotationCoverageOf
@@ -376,6 +457,9 @@
     /** Level to include for javadoc */
     var docLevel = DocLevel.PROTECTED
 
+    /** Whether to include the signature file format version number ([SIGNATURE_FORMAT]) in signature files */
+    var includeSignatureFormatVersion: Boolean = !compatOutput
+
     /**
      * Whether to omit locations for warnings and errors. This is not a flag exposed to users
      * or listed in help; this is intended for the unit test suite, used for example for the
@@ -383,6 +467,9 @@
      */
     var omitLocations = false
 
+    /** Directory to write signature files to, if any. */
+    var androidJarSignatureFiles: File? = null
+
     /**
      * The language level to use for Java files, set with [ARG_JAVA_SOURCE]
      */
@@ -391,6 +478,12 @@
     /** Map from XML API descriptor file to corresponding artifact id name */
     val artifactRegistrations = ArtifactTagger()
 
+    /** List of signature files to export as JDiff files */
+    val convertToXmlFiles: List<Pair<File, File>> = mutableConvertToXmlFiles
+
+    /** Temporary folder to use instead of the JDK default, if any */
+    var tempFolder: File? = null
+
     init {
         // Pre-check whether --color/--no-color is present and use that to decide how
         // to emit the banner even before we emit errors
@@ -410,9 +503,9 @@
             } else {
                 stdout.println(BANNER.trimIndent())
             }
+            stdout.println()
+            stdout.flush()
         }
-        stdout.println()
-        stdout.flush()
 
         var androidJarPatterns: MutableList<String>? = null
         var currentCodeName: String? = null
@@ -439,20 +532,26 @@
                     throw DriverException(stdout = "$PROGRAM_NAME version: ${Version.VERSION}")
                 }
 
-                ARGS_COMPAT_OUTPUT -> compatOutput = true
+                ARG_COMPAT_OUTPUT -> compatOutput = true
 
-            // For now we don't distinguish between bootclasspath and classpath
+                // For now we don't distinguish between bootclasspath and classpath
                 ARG_CLASS_PATH, "-classpath", "-bootclasspath" ->
                     mutableClassPath.addAll(stringToExistingDirsOrJars(getValue(args, ++index)))
 
                 ARG_SOURCE_PATH, "--sources", "--sourcepath", "-sourcepath" -> {
                     val path = getValue(args, ++index)
-                    if (path.endsWith(SdkConstants.DOT_JAVA)) {
-                        throw DriverException(
-                            "$arg should point to a source root directory, not a source file ($path)"
-                        )
+                    if (path.isBlank()) {
+                        // Don't compute absolute path; we want to skip this file later on.
+                        // For current directory one should use ".", not "".
+                        mutableSourcePath.add(File(""))
+                    } else {
+                        if (path.endsWith(SdkConstants.DOT_JAVA)) {
+                            throw DriverException(
+                                "$arg should point to a source root directory, not a source file ($path)"
+                            )
+                        }
+                        mutableSourcePath.addAll(stringToExistingDirsOrJars(path))
                     }
-                    mutableSourcePath.addAll(stringToExistingDirsOrJars(path))
                 }
 
                 ARG_SOURCE_FILES -> {
@@ -462,16 +561,39 @@
                     }
                 }
 
-                ARG_MERGE_ANNOTATIONS, "--merge-zips" -> mutableMergeAnnotations.addAll(
+                // TODO: Remove the legacy --merge-annotations flag once it's no longer used to update P docs
+                ARG_MERGE_QUALIFIER_ANNOTATIONS, "--merge-zips", "--merge-annotations" -> mutableMergeQualifierAnnotations.addAll(
                     stringToExistingDirsOrFiles(
                         getValue(args, ++index)
                     )
                 )
 
-                "-sdkvalues", ARG_SDK_VALUES -> sdkValueDir = stringToNewDir(getValue(args, ++index))
+                ARG_MERGE_INCLUSION_ANNOTATIONS -> mutableMergeInclusionAnnotations.addAll(
+                    stringToExistingDirsOrFiles(
+                        getValue(args, ++index)
+                    )
+                )
 
+                ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS -> {
+                    validateNullabilityFromMergedStubs = true
+                    nullabilityAnnotationsValidator =
+                        nullabilityAnnotationsValidator ?: NullabilityAnnotationsValidator()
+                }
+                ARG_VALIDATE_NULLABILITY_FROM_LIST -> {
+                    validateNullabilityFromList = stringToExistingFile(getValue(args, ++index))
+                    nullabilityAnnotationsValidator =
+                        nullabilityAnnotationsValidator ?: NullabilityAnnotationsValidator()
+                }
+                ARG_NULLABILITY_WARNINGS_TXT ->
+                    nullabilityWarningsTxt = stringToNewFile(getValue(args, ++index))
+                ARG_NULLABILITY_ERRORS_NON_FATAL ->
+                    nullabilityErrorsFatal = false
+
+                "-sdkvalues", ARG_SDK_VALUES -> sdkValueDir = stringToNewDir(getValue(args, ++index))
                 ARG_API, "-api" -> apiFile = stringToNewFile(getValue(args, ++index))
+                ARG_XML_API -> apiXmlFile = stringToNewFile(getValue(args, ++index))
                 ARG_DEX_API, "-dexApi" -> dexApiFile = stringToNewFile(getValue(args, ++index))
+                ARG_DEX_API_MAPPING, "-apiMapping" -> dexApiMappingFile = stringToNewFile(getValue(args, ++index))
 
                 ARG_PRIVATE_API, "-privateApi" -> privateApiFile = stringToNewFile(getValue(args, ++index))
                 ARG_PRIVATE_DEX_API, "-privateDexApi" -> privateDexApiFile = stringToNewFile(getValue(args, ++index))
@@ -488,6 +610,13 @@
 
                 ARG_SHOW_ANNOTATION, "-showAnnotation" -> mutableShowAnnotations.add(getValue(args, ++index))
 
+                ARG_SHOW_SINGLE_ANNOTATION -> {
+                    val annotation = getValue(args, ++index)
+                    mutableShowSingleAnnotations.add(annotation)
+                    // These should also be counted as show annotations
+                    mutableShowAnnotations.add(annotation)
+                }
+
                 ARG_SHOW_UNANNOTATED, "-showUnannotated" -> showUnannotated = true
 
                 "--showAnnotationOverridesVisibility" -> {
@@ -495,7 +624,8 @@
                     showAnnotationOverridesVisibility = true
                 }
 
-                "--hideAnnotations", "-hideAnnotation" -> mutableHideAnnotations.add(getValue(args, ++index))
+                ARG_HIDE_ANNOTATION, "--hideAnnotations", "-hideAnnotation" ->
+                    mutableHideAnnotations.add(getValue(args, ++index))
 
                 ARG_STUBS, "-stubs" -> stubsDir = stringToNewDir(getValue(args, ++index))
                 ARG_DOC_STUBS -> docStubsDir = stringToNewDir(getValue(args, ++index))
@@ -504,13 +634,15 @@
 
                 ARG_EXCLUDE_ANNOTATIONS -> generateAnnotations = false
 
-            // Note that this only affects stub generation, not signature files.
-            // For signature files, clear the compatibility mode
-            // (--annotations-in-signatures)
-                "--include-annotations" -> generateAnnotations = true // temporary for tests
+                ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS -> includeDocumentationInStubs = false
 
-            // Flag used by test suite to avoid including locations in
-            // the output when diffing against golden files
+                // Note that this only affects stub generation, not signature files.
+                // For signature files, clear the compatibility mode
+                // (--annotations-in-signatures)
+                ARG_INCLUDE_ANNOTATIONS -> generateAnnotations = true
+
+                // Flag used by test suite to avoid including locations in
+                // the output when diffing against golden files
                 "--omit-locations" -> omitLocations = true
 
                 ARG_PROGUARD, "-proguard" -> proguard = stringToNewFile(getValue(args, ++index))
@@ -557,13 +689,78 @@
                 ARG_INCLUDE_ANNOTATION_CLASSES -> copyStubAnnotationsFrom = stringToExistingDir(getValue(args, ++index))
                 ARG_INCLUDE_SOURCE_RETENTION -> includeSourceRetentionAnnotations = true
 
-                ARG_PREVIOUS_API -> previousApi = stringToExistingFile(getValue(args, ++index))
-                ARG_CURRENT_API -> currentApi = stringToExistingFile(getValue(args, ++index))
+                "--previous-api" -> {
+                    migrateNullsFrom = stringToExistingFile(getValue(args, ++index))
+                    reporter.report(
+                        Errors.DEPRECATED_OPTION, null as File?,
+                        "--previous-api is deprecated; instead " +
+                            "use $ARG_MIGRATE_NULLNESS $migrateNullsFrom"
+                    )
+                }
 
-                ARG_MIGRATE_NULLNESS -> migrateNulls = true
+                ARG_MIGRATE_NULLNESS -> {
+                    // See if the next argument specifies the nullness API codebase
+                    if (index < args.size - 1) {
+                        val nextArg = args[index + 1]
+                        if (!nextArg.startsWith("-")) {
+                            val file = fileForPath(nextArg)
+                            if (file.isFile) {
+                                index++
+                                migrateNullsFrom = file
+                            }
+                        }
+                    }
+                }
+
+                "--current-api" -> {
+                    val file = stringToExistingFile(getValue(args, ++index))
+                    mutableCompatibilityChecks.add(CheckRequest(file, ApiType.PUBLIC_API, ReleaseType.DEV))
+                    reporter.report(
+                        Errors.DEPRECATED_OPTION, null as File?,
+                        "--current-api is deprecated; instead " +
+                            "use $ARG_CHECK_COMPATIBILITY_API_CURRENT"
+                    )
+                }
 
                 ARG_CHECK_COMPATIBILITY -> {
-                    checkCompatibility = true
+                    // See if the next argument specifies the compatibility check.
+                    // Synonymous with ARG_CHECK_COMPATIBILITY_API_CURRENT, though
+                    // for backwards compatibility with earlier versions and usages
+                    // can also works in conjunction with ARG_CURRENT_API where the
+                    // usage was to use ARG_CURRENT_API to point to the API file and
+                    // then specify ARG_CHECK_COMPATIBILITY (without an argument) to
+                    // indicate that the current api should also be checked for
+                    // compatibility.
+                    if (index < args.size - 1) {
+                        val nextArg = args[index + 1]
+                        if (!nextArg.startsWith("-")) {
+                            val file = fileForPath(nextArg)
+                            if (file.isFile) {
+                                index++
+                                mutableCompatibilityChecks.add(CheckRequest(file, ApiType.PUBLIC_API, ReleaseType.DEV))
+                            }
+                        }
+                    }
+                }
+
+                ARG_CHECK_COMPATIBILITY_API_CURRENT -> {
+                    val file = stringToExistingFile(getValue(args, ++index))
+                    mutableCompatibilityChecks.add(CheckRequest(file, ApiType.PUBLIC_API, ReleaseType.DEV))
+                }
+
+                ARG_CHECK_COMPATIBILITY_API_RELEASED -> {
+                    val file = stringToExistingFile(getValue(args, ++index))
+                    mutableCompatibilityChecks.add(CheckRequest(file, ApiType.PUBLIC_API, ReleaseType.RELEASED))
+                }
+
+                ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT -> {
+                    val file = stringToExistingFile(getValue(args, ++index))
+                    mutableCompatibilityChecks.add(CheckRequest(file, ApiType.REMOVED, ReleaseType.DEV))
+                }
+
+                ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED -> {
+                    val file = stringToExistingFile(getValue(args, ++index))
+                    mutableCompatibilityChecks.add(CheckRequest(file, ApiType.REMOVED, ReleaseType.RELEASED))
                 }
 
                 ARG_ANNOTATION_COVERAGE_STATS -> dumpAnnotationStatistics = true
@@ -579,10 +776,10 @@
                     annotationCoverageMemberReport = stringToNewFile(getValue(args, ++index))
                 }
 
-                ARG_ERROR, "-error" -> Errors.setErrorLevel(getValue(args, ++index), Severity.ERROR)
-                ARG_WARNING, "-warning" -> Errors.setErrorLevel(getValue(args, ++index), Severity.WARNING)
-                ARG_LINT, "-lint" -> Errors.setErrorLevel(getValue(args, ++index), Severity.LINT)
-                ARG_HIDE, "-hide" -> Errors.setErrorLevel(getValue(args, ++index), Severity.HIDDEN)
+                ARG_ERROR, "-error" -> Errors.setErrorLevel(getValue(args, ++index), Severity.ERROR, true)
+                ARG_WARNING, "-warning" -> Errors.setErrorLevel(getValue(args, ++index), Severity.WARNING, true)
+                ARG_LINT, "-lint" -> Errors.setErrorLevel(getValue(args, ++index), Severity.LINT, true)
+                ARG_HIDE, "-hide" -> Errors.setErrorLevel(getValue(args, ++index), Severity.HIDDEN, true)
 
                 ARG_WARNINGS_AS_ERRORS -> warningsAreErrors = true
                 ARG_LINTS_AS_ERRORS -> lintsAreErrors = true
@@ -614,7 +811,7 @@
                 ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES -> allowReferencingUnknownClasses = true
                 ARG_NO_UNKNOWN_CLASSES -> noUnknownClasses = true
 
-            // Extracting API levels
+                // Extracting API levels
                 ARG_ANDROID_JAR_PATTERN -> {
                     val list = androidJarPatterns ?: run {
                         val list = arrayListOf<String>()
@@ -650,6 +847,8 @@
 
                 ARG_NO_DOCS, "-nodocs" -> noDocs = true
 
+                ARG_UPDATE_API -> updateApi = true
+
                 ARG_GENERATE_DOCUMENTATION -> {
                     // Digest all the remaining arguments.
                     // Allow "STUBS_DIR" to reference the stubs directory.
@@ -660,9 +859,10 @@
                         // original source path
                         val docStubsDir = docStubsDir
                         if (docStubsDir != null && (prev == ARG_SOURCE_PATH || prev == "-sourcepath") &&
-                            !argument.contains(docStubsDir.path)) {
+                            !argument.contains(docStubsDir.path)
+                        ) {
                             // Insert the doc stubs as the default place to look for sources
-                            argument = docStubsDir.path // + File.pathSeparatorChar + argument
+                            argument = docStubsDir.path
                         }
                         prev = it
 
@@ -692,25 +892,18 @@
                     artifactRegistrations.register(artifactId, descriptor)
                 }
 
-            // Unimplemented doclava1 flags (no arguments)
-                "-quiet",
-                "-yamlV2" -> {
-                    unimplemented(arg)
+                ARG_CONVERT_TO_JDIFF -> {
+                    val signatureFile = stringToExistingFile(getValue(args, ++index))
+                    val jDiffFile = stringToNewFile(getValue(args, ++index))
+                    mutableConvertToXmlFiles.add(Pair(signatureFile, jDiffFile))
                 }
 
-                "-android" -> { // partially implemented: Pick up the color hint, but there may be other implications
-                    color = true
-                    unimplemented(arg)
-                }
-
-                "-stubsourceonly" -> {
-                    /* noop */
-                }
-
-            // Unimplemented doclava1 flags (1 argument)
-                "-d" -> {
-                    unimplemented(arg)
-                    index++
+                "--write-android-jar-signatures" -> {
+                    val root = stringToExistingDir(getValue(args, ++index))
+                    if (!File(root, "prebuilts/sdk").isDirectory) {
+                        throw DriverException("$androidJarSignatureFiles does not point to an Android source tree")
+                    }
+                    androidJarSignatureFiles = root
                 }
 
                 "-encoding" -> {
@@ -730,14 +923,53 @@
                     }
                 }
 
-            // Unimplemented doclava1 flags (2 arguments)
+                "--temp-folder" -> {
+                    tempFolder = stringToNewOrExistingDir(getValue(args, ++index))
+                }
+
+                // Option only meant for tests (not documented); doesn't work in all cases (to do that we'd
+                // need JNA to call libc)
+                "--pwd" -> {
+                    val pwd = stringToExistingDir(getValue(args, ++index)).absoluteFile
+                    System.setProperty("user.dir", pwd.path)
+                }
+
+                "--noop", "--no-op" -> {
+                }
+
+                // Doclava1 flag: Already the behavior in metalava
+                "-keepstubcomments" -> {
+                }
+
+                // Unimplemented doclava1 flags (no arguments)
+                "-quiet",
+                "-yamlV2" -> {
+                    unimplemented(arg)
+                }
+
+                "-android" -> { // partially implemented: Pick up the color hint, but there may be other implications
+                    color = true
+                    unimplemented(arg)
+                }
+
+                "-stubsourceonly" -> {
+                    /* noop */
+                }
+
+                // Unimplemented doclava1 flags (1 argument)
+                "-d" -> {
+                    unimplemented(arg)
+                    index++
+                }
+
+                // Unimplemented doclava1 flags (2 arguments)
                 "-since" -> {
                     unimplemented(arg)
                     index += 2
                 }
 
-            // doclava1 doc-related flags: only supported here to make this command a drop-in
-            // replacement
+                // doclava1 doc-related flags: only supported here to make this command a drop-in
+                // replacement
                 "-referenceonly",
                 "-devsite",
                 "-ignoreJdLinks",
@@ -753,7 +985,7 @@
                     javadoc(arg)
                 }
 
-            // doclava1 flags with 1 argument
+                // doclava1 flags with 1 argument
                 "-doclet",
                 "-docletpath",
                 "-templatedir",
@@ -776,7 +1008,7 @@
                     index++
                 }
 
-            // doclava1 flags with two arguments
+                // doclava1 flags with two arguments
                 "-federate",
                 "-federationapi",
                 "-htmldir2" -> {
@@ -784,13 +1016,13 @@
                     index += 2
                 }
 
-            // doclava1 flags with three arguments
+                // doclava1 flags with three arguments
                 "-samplecode" -> {
                     javadoc(arg)
                     index += 3
                 }
 
-            // doclava1 flag with variable number of arguments; skip everything until next arg
+                // doclava1 flag with variable number of arguments; skip everything until next arg
                 "-hdf" -> {
                     javadoc(arg)
                     index++
@@ -834,10 +1066,21 @@
                         } else {
                             yesNo(arg.substring(ARG_OMIT_COMMON_PACKAGES.length + 1))
                         }
-                    } else if (arg.startsWith(ARGS_COMPAT_OUTPUT)) {
-                        compatOutput = if (arg == ARGS_COMPAT_OUTPUT)
+                    } else if (arg.startsWith(ARG_COMPAT_OUTPUT)) {
+                        compatOutput = if (arg == ARG_COMPAT_OUTPUT)
                             true
-                        else yesNo(arg.substring(ARGS_COMPAT_OUTPUT.length + 1))
+                        else yesNo(arg.substring(ARG_COMPAT_OUTPUT.length + 1))
+                    } else if (arg.startsWith(ARG_INCLUDE_SIG_VERSION)) {
+                        includeSignatureFormatVersion = if (arg == ARG_INCLUDE_SIG_VERSION)
+                            true
+                        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)
+                            else -> throw DriverException(stderr = "Unexpected signature format; expected v1, v2 or v3")
+                        }
                     } else if (arg.startsWith("-")) {
                         // Compatibility flag; map to mutable properties in the Compatibility
                         // class and assign it
@@ -886,9 +1129,42 @@
             allowReferencingUnknownClasses = false
         }
 
+        if (updateApi) {
+            // We're running in update API mode: cancel other "action" flags; only signature file generation
+            // flags count
+            annotationCoverageClassReport = null
+            annotationCoverageMemberReport = null
+            dumpAnnotationStatistics = false
+            apiLevelJars = null
+            generateApiLevelXml = null
+            applyApiLevelsXml = null
+            androidJarSignatureFiles = null
+            stubsDir = null
+            docStubsDir = null
+            stubsSourceList = null
+            docStubsSourceList = null
+            sdkValueDir = null
+            externalAnnotations = null
+            proguard = null
+            noDocs = true
+            invokeDocumentationToolArguments = emptyArray()
+            checkKotlinInterop = false
+            mutableCompatibilityChecks.clear()
+            mutableAnnotationCoverageOf.clear()
+            artifactRegistrations.clear()
+        }
+
         checkFlagConsistency()
     }
 
+    private fun setFormat(format: Int) {
+        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
@@ -918,7 +1194,7 @@
         }
 
         val apiLevelFiles = mutableListOf<File>()
-        apiLevelFiles.add(File("")) // api level 0: dummy
+        apiLevelFiles.add(File("there is no api 0")) // api level 0: dummy, should not be processed
         val minApi = 1
 
         // Get all the android.jar. They are in platforms-#
@@ -967,29 +1243,28 @@
 
     /** Makes sure that the flag combinations make sense */
     private fun checkFlagConsistency() {
-        if (checkCompatibility && currentApi == null && previousApi == null) {
-            throw DriverException(stderr = "$ARG_CHECK_COMPATIBILITY requires $ARG_CURRENT_API")
-        }
-
-        if (migrateNulls && previousApi == null) {
-            throw DriverException(stderr = "$ARG_MIGRATE_NULLNESS requires $ARG_PREVIOUS_API")
-        }
-
         if (apiJar != null && sources.isNotEmpty()) {
             throw DriverException(stderr = "Specify either $ARG_SOURCE_FILES or $ARG_INPUT_API_JAR, not both")
         }
 
         if (compatOutput && outputKotlinStyleNulls) {
             throw DriverException(
-                stderr = "$ARG_OUTPUT_KOTLIN_NULLS should not be combined with " +
-                    "$ARGS_COMPAT_OUTPUT=yes"
+                stderr = "$ARG_OUTPUT_KOTLIN_NULLS=yes should not be combined with " +
+                    "$ARG_COMPAT_OUTPUT=yes"
             )
         }
 
         if (compatOutput && outputDefaultValues) {
             throw DriverException(
-                stderr = "$ARG_OUTPUT_DEFAULT_VALUES should not be combined with " +
-                    "$ARGS_COMPAT_OUTPUT=yes"
+                stderr = "$ARG_OUTPUT_DEFAULT_VALUES=yes should not be combined with " +
+                    "$ARG_COMPAT_OUTPUT=yes"
+            )
+        }
+
+        if (compatOutput && includeSignatureFormatVersion) {
+            throw DriverException(
+                stderr = "$ARG_INCLUDE_SIG_VERSION=yes should not be combined with " +
+                    "$ARG_COMPAT_OUTPUT=yes"
             )
         }
     }
@@ -1040,6 +1315,7 @@
         return file
     }
 
+    @Suppress("unused")
     private fun stringToExistingDirs(value: String): List<File> {
         val files = mutableListOf<File>()
         for (path in value.split(File.pathSeparatorChar)) {
@@ -1084,6 +1360,7 @@
         return file
     }
 
+    @Suppress("unused")
     private fun stringToExistingFileOrDir(value: String): File {
         val file = fileForPath(value)
         if (!file.exists()) {
@@ -1146,18 +1423,34 @@
         return output
     }
 
+    private fun stringToNewOrExistingDir(value: String): File {
+        val dir = fileForPath(value)
+        if (!dir.isDirectory) {
+            val ok = dir.mkdirs()
+            if (!ok) {
+                throw DriverException("Could not create $dir")
+            }
+        }
+        return dir
+    }
+
     private fun stringToNewDir(value: String): File {
         val output = fileForPath(value)
-
-        if (output.exists()) {
-            if (output.isDirectory) {
-                output.deleteRecursively()
+        val ok =
+            if (output.exists()) {
+                if (output.isDirectory) {
+                    output.deleteRecursively()
+                }
+                if (output.exists()) {
+                    true
+                } else {
+                    output.mkdir()
+                }
+            } else {
+                output.mkdirs()
             }
-        } else if (output.parentFile != null && !output.parentFile.exists()) {
-            val ok = output.parentFile.mkdirs()
-            if (!ok) {
-                throw DriverException("Could not create ${output.parentFile}")
-            }
+        if (!ok) {
+            throw DriverException("Could not create $output")
         }
 
         return output
@@ -1171,9 +1464,11 @@
         if (path.startsWith("~/")) {
             val home = System.getProperty("user.home") ?: return File(path)
             return File(home + path.substring(1))
+        } else if (path.startsWith("@")) {
+            return File("@" + File(path.substring(1)).absolutePath)
         }
 
-        return File(path)
+        return File(path).absoluteFile
     }
 
     private fun getUsage(includeHeader: Boolean = true, colorize: Boolean = color): String {
@@ -1202,6 +1497,10 @@
             ARG_VERBOSE, "Include extra diagnostic output",
             ARG_COLOR, "Attempt to colorize the output (defaults to true if \$TERM is xterm)",
             ARG_NO_COLOR, "Do not attempt to colorize the output",
+            ARG_NO_DOCS, "Cancel any other documentation flags supplied to $PROGRAM_NAME. This is here " +
+                "to make it easier customize build system tasks.",
+            ARG_UPDATE_API, "Cancel any other \"action\" flags other than generating signature files. This is here " +
+                "to make it easier customize build system tasks, particularly for the \"make update-api\" task.",
 
             "", "\nAPI sources:",
             "$ARG_SOURCE_FILES <files>", "A comma separated list of source files to be parsed. Can also be " +
@@ -1214,9 +1513,29 @@
                 "`${File.pathSeparator}`) containing classes that should be on the classpath when parsing the " +
                 "source files",
 
-            "$ARG_MERGE_ANNOTATIONS <file>", "An external annotations file (using IntelliJ's external " +
-                "annotations database format) to merge and overlay the sources. A subset of .jaif files " +
-                "is also supported.",
+            "$ARG_MERGE_QUALIFIER_ANNOTATIONS <file>", "An external annotations file to merge and overlay " +
+                "the sources, or a directory of such files. Should be used for annotations intended for " +
+                "inclusion in the API to be written out, e.g. nullability. Formats supported are: IntelliJ's " +
+                "external annotations database format, .jar or .zip files containing those, Android signature " +
+                "files, and Java stub files.",
+
+            "$ARG_MERGE_INCLUSION_ANNOTATIONS <file>", "An external annotations file to merge and overlay " +
+                "the sources, or a directory of such files. Should be used for annotations which determine " +
+                "inclusion in the API to be written out, i.e. show and hide. The only format supported is " +
+                "Java stub files.",
+
+            ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS, "Triggers validation of nullability annotations " +
+                "for any class where $ARG_MERGE_QUALIFIER_ANNOTATIONS includes a Java stub file.",
+
+            ARG_VALIDATE_NULLABILITY_FROM_LIST, "Triggers validation of nullability annotations " +
+                "for any class listed in the named file (one top-level class per line, # prefix for comment line).",
+
+            "$ARG_NULLABILITY_WARNINGS_TXT <file>", "Specifies where to write warnings encountered during " +
+                "validation of nullability annotations. (Does not trigger validation by itself.)",
+
+            ARG_NULLABILITY_ERRORS_NON_FATAL, "Specifies that errors encountered during validation of " +
+                "nullability annotations should not be treated as errors. They will be written out to the " +
+                "file specified in $ARG_NULLABILITY_WARNINGS_TXT instead.",
 
             "$ARG_INPUT_API_JAR <file>", "A .jar file to read APIs from directly",
 
@@ -1225,7 +1544,12 @@
             "$ARG_HIDE_PACKAGE <package>", "Remove the given packages from the API even if they have not been " +
                 "marked with @hide",
 
-            "$ARG_SHOW_ANNOTATION <annotation class>", "Include the given annotation in the API analysis",
+            "$ARG_SHOW_ANNOTATION <annotation class>", "Unhide any hidden elements that are also annotated " +
+                "with the given annotation",
+            "$ARG_SHOW_SINGLE_ANNOTATION <annotation>", "Like $ARG_SHOW_ANNOTATION, but does not apply " +
+                "to members; these must also be explicitly annotated",
+            "$ARG_HIDE_ANNOTATION <annotation class>", "Treat any elements annotated with the given annotation " +
+                "as hidden",
             ARG_SHOW_UNANNOTATED, "Include un-annotated public APIs in the signature file as well",
             "$ARG_JAVA_SOURCE <level>", "Sets the source level for Java source files; default is 1.8.",
 
@@ -1242,18 +1566,22 @@
             "$ARG_PRIVATE_API <file>", "Generate a signature descriptor file listing the exact private APIs",
             "$ARG_DEX_API <file>", "Generate a DEX signature descriptor file listing the APIs",
             "$ARG_PRIVATE_DEX_API <file>", "Generate a DEX signature descriptor file listing the exact private APIs",
+            "$ARG_DEX_API_MAPPING <file>", "Generate a DEX signature descriptor along with file and line numbers",
             "$ARG_REMOVED_API <file>", "Generate a signature descriptor file for APIs that have been removed",
+            "$ARG_FORMAT=<v1,v2,v3,...>", "Sets the output signature file format to be the given version.",
             "$ARG_OUTPUT_KOTLIN_NULLS[=yes|no]", "Controls whether nullness annotations should be formatted as " +
                 "in Kotlin (with \"?\" for nullable types, \"\" for non nullable types, and \"!\" for unknown. " +
                 "The default is yes.",
             "$ARG_OUTPUT_DEFAULT_VALUES[=yes|no]", "Controls whether default values should be included in " +
                 "signature files. The default is yes.",
-            "$ARGS_COMPAT_OUTPUT=[yes|no]", "Controls whether to keep signature files compatible with the " +
+            "$ARG_COMPAT_OUTPUT=[yes|no]", "Controls whether to keep signature files compatible with the " +
                 "historical format (with its various quirks) or to generate the new format (which will also include " +
                 "annotations that are part of the API, etc.)",
             "$ARG_OMIT_COMMON_PACKAGES[=yes|no]", "Skip common package prefixes like java.lang.* and " +
                 "kotlin.* in signature files, along with packages for well known annotations like @Nullable and " +
                 "@NonNull.",
+            "$ARG_INCLUDE_SIG_VERSION[=yes|no]", "Whether the signature files should include a comment listing " +
+                "the format version of the signature file.",
 
             "$ARG_PROGUARD <file>", "Write a ProGuard keep file for the API",
             "$ARG_SDK_VALUES <dir>", "Write SDK values files to the given directory",
@@ -1267,6 +1595,9 @@
                 "just list this as @NonNull. Another difference is that @doconly elements are included in " +
                 "documentation stubs, but not regular stubs, etc.",
             ARG_EXCLUDE_ANNOTATIONS, "Exclude annotations such as @Nullable from the stub files",
+            ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS, "Exclude element documentation (javadoc and kdoc) " +
+                "from the generated stubs. (Copyright notices are not affected by this, they are always included. " +
+                "Documentation stubs (--doc-stubs) are not affected.)",
             "$ARG_STUBS_SOURCE_LIST <file>", "Write the list of generated stub files into the given source " +
                 "list file. If generating documentation stubs and you haven't also specified " +
                 "$ARG_DOC_STUBS_SOURCE_LIST, this list will refer to the documentation stubs; " +
@@ -1278,19 +1609,19 @@
                 "level stub class in that API.",
 
             "", "\nDiffs and Checks:",
-            "$ARG_PREVIOUS_API <signature file>", "A signature file for the previous version of this " +
-                "API to apply diffs with",
             "$ARG_INPUT_KOTLIN_NULLS[=yes|no]", "Whether the signature file being read should be " +
                 "interpreted as having encoded its types using Kotlin style types: a suffix of \"?\" for nullable " +
                 "types, no suffix for non nullable types, and \"!\" for unknown. The default is no.",
-            ARG_CHECK_COMPATIBILITY, "Check compatibility with the previous API",
+            "$ARG_CHECK_COMPATIBILITY:type:state <file>", "Check compatibility. Type is one of 'api' " +
+                "and 'removed', which checks either the public api or the removed api. State is one of " +
+                "'current' and 'released', to check either the currently in development API or the last publicly " +
+                "released API, respectively. Different compatibility checks apply in the two scenarios. " +
+                "For example, to check the code base against the current public API, use " +
+                "$ARG_CHECK_COMPATIBILITY:api:current.",
             ARG_CHECK_KOTLIN_INTEROP, "Check API intended to be used from both Kotlin and Java for interoperability " +
                 "issues",
-            "$ARG_CURRENT_API <signature file>", "A signature file for the current version of this " +
-                "API to check compatibility with. If not specified, $ARG_PREVIOUS_API will be used " +
-                "instead.",
-            ARG_MIGRATE_NULLNESS, "Compare nullness information with the previous API and mark newly " +
-                "annotated APIs as under migration.",
+            "$ARG_MIGRATE_NULLNESS <api file>", "Compare nullness information with the previous stable API " +
+                "and mark newly annotated APIs as under migration.",
             ARG_WARNINGS_AS_ERRORS, "Promote all warnings to errors",
             ARG_LINTS_AS_ERRORS, "Promote all API lint warnings to errors",
             "$ARG_ERROR <id>", "Report issues of the given id as errors",
@@ -1298,6 +1629,11 @@
             "$ARG_LINT <id>", "Report issues of the given id as having lint-severity",
             "$ARG_HIDE <id>", "Hide/skip issues of the given id",
 
+            "", "\nJDiff:",
+            "$ARG_XML_API <file>", "Like $ARG_API, but emits the API in the JDiff XML format instead",
+            "$ARG_CONVERT_TO_JDIFF <sig> <xml>", "Reads in the given signature file, and writes it out " +
+                "in the JDiff XML format. Can be specified multiple times.",
+
             "", "\nStatistics:",
             ARG_ANNOTATION_COVERAGE_STATS, "Whether $PROGRAM_NAME should emit coverage statistics for " +
                 "annotations, listing the percentage of the API that has been annotated with nullness information.",
@@ -1320,12 +1656,10 @@
                 "them into the given zip file",
             "$ARG_INCLUDE_ANNOTATION_CLASSES <dir>", "Copies the given stub annotation source files into the " +
                 "generated stub sources; <dir> is typically $PROGRAM_NAME/stub-annotations/src/main/java/.",
-
-// Soon to be removed; not documented:
-//            "$ARG_COPY_ANNOTATIONS <source> <dest>", "For a source folder full of annotation " +
-//                "sources, generates corresponding package private versions of the same annotations.",
             "$ARG_REWRITE_ANNOTATIONS <dir/jar>", "For a bytecode folder or output jar, rewrites the " +
                 "androidx annotations to be package private",
+            "$ARG_COPY_ANNOTATIONS <source> <dest>", "For a source folder full of annotation " +
+                "sources, generates corresponding package private versions of the same annotations.",
             ARG_INCLUDE_SOURCE_RETENTION, "If true, include source-retention annotations in the stub files. Does " +
                 "not apply to signature files. Source retention annotations are extracted into the external " +
                 "annotations files instead.",
@@ -1341,7 +1675,15 @@
                 "is \$ANDROID_HOME/platforms/android-%/android.jar.",
             ARG_CURRENT_VERSION, "Sets the current API level of the current source code",
             ARG_CURRENT_CODENAME, "Sets the code name for the current source code",
-            ARG_CURRENT_JAR, "Points to the current API jar, if any"
+            ARG_CURRENT_JAR, "Points to the current API jar, if any",
+
+            "", "\nEnvironment Variables:",
+            ENV_VAR_METALAVA_DUMP_ARGV, "Set to true to have metalava emit all the arguments it was invoked with. " +
+                "Helpful when debugging or reproducing under a debugger what the build system is doing.",
+            ENV_VAR_METALAVA_PREPEND_ARGS, "One or more arguments (concatenated by space) to insert into the " +
+                "command line, before the documentation flags.",
+            ENV_VAR_METALAVA_APPEND_ARGS, "One or more arguments (concatenated by space) to append to the " +
+                "end of the command line, after the generate documentation flags."
         )
 
         var argWidth = 0
@@ -1399,7 +1741,8 @@
     companion object {
         /** Whether we should use [Compatibility] mode */
         fun useCompatMode(args: Array<String>): Boolean {
-            return COMPAT_MODE_BY_DEFAULT && !args.contains("$ARGS_COMPAT_OUTPUT=no")
+            return COMPAT_MODE_BY_DEFAULT && !args.contains("$ARG_COMPAT_OUTPUT=no") &&
+                (args.none { it.startsWith("$ARG_FORMAT=") } || args.contains("--format=v1"))
         }
     }
 }
diff --git a/src/main/java/com/android/tools/metalava/ReleaseType.kt b/src/main/java/com/android/tools/metalava/ReleaseType.kt
new file mode 100644
index 0000000..5a4fb42
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/ReleaseType.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.ErrorConfiguration
+
+enum class ReleaseType(val flagName: String, private val displayName: String = flagName) {
+    DEV("current", "development") {
+        /**
+         * Customization of the severities to apply when doing compatibility checking against the
+         * current version of the API. Corresponds to the same flags passed into doclava's error
+         * check this way:
+         * args: "-error 2 -error 3 -error 4 -error 5 -error 6 " +
+         * "-error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 " +
+         * "-error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 " +
+         * "-error 25 -error 26 -error 27",
+         */
+        override fun getErrorConfiguration(): ErrorConfiguration {
+            return super.getErrorConfiguration().apply {
+                error(Errors.ADDED_CLASS)
+                error(Errors.ADDED_FIELD)
+                error(Errors.ADDED_FINAL_UNINSTANTIABLE)
+                error(Errors.ADDED_INTERFACE)
+                error(Errors.ADDED_METHOD)
+                error(Errors.ADDED_PACKAGE)
+                error(Errors.CHANGED_ABSTRACT)
+                error(Errors.CHANGED_CLASS)
+                error(Errors.CHANGED_DEPRECATED)
+                error(Errors.CHANGED_SCOPE)
+                error(Errors.CHANGED_SYNCHRONIZED)
+                error(Errors.CHANGED_THROWS)
+                error(Errors.REMOVED_FINAL)
+            }
+        }
+    },
+
+    RELEASED("released", "released") {
+        /**
+         * Customization of the severities to apply when doing compatibility checking against the
+         * previously released stable version of the API. Corresponds to the same flags passed into
+         * doclava's error check this way:
+         * args: "-hide 2 -hide 3 -hide 4 -hide 5 -hide 6 -hide 24 -hide 25 -hide 26 -hide 27 " +
+         * "-error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 " +
+         * "-error 16 -error 17 -error 18 -error 31",
+         */
+        override fun getErrorConfiguration(): ErrorConfiguration {
+            return super.getErrorConfiguration().apply {
+                error(Errors.ADDED_ABSTRACT_METHOD)
+                hide(Errors.ADDED_CLASS)
+                hide(Errors.ADDED_FIELD)
+                hide(Errors.ADDED_FINAL_UNINSTANTIABLE)
+                hide(Errors.ADDED_INTERFACE)
+                hide(Errors.ADDED_METHOD)
+                hide(Errors.ADDED_PACKAGE)
+                hide(Errors.CHANGED_DEPRECATED)
+                hide(Errors.CHANGED_SYNCHRONIZED)
+                hide(Errors.REMOVED_FINAL)
+            }
+        }
+    };
+
+    /** Returns the error configuration to use for the given release type */
+    open fun getErrorConfiguration(): ErrorConfiguration {
+        return ErrorConfiguration().apply {
+            error(Errors.ADDED_FINAL)
+            error(Errors.CHANGED_STATIC)
+            error(Errors.CHANGED_SUPERCLASS)
+            error(Errors.CHANGED_TRANSIENT)
+            error(Errors.CHANGED_TYPE)
+            error(Errors.CHANGED_VALUE)
+            error(Errors.CHANGED_VOLATILE)
+            error(Errors.REMOVED_CLASS)
+            error(Errors.REMOVED_FIELD)
+            error(Errors.REMOVED_INTERFACE)
+            error(Errors.REMOVED_METHOD)
+            error(Errors.REMOVED_PACKAGE)
+            error(Errors.ADDED_REIFIED)
+        }
+    }
+
+    override fun toString(): String = displayName
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/Reporter.kt b/src/main/java/com/android/tools/metalava/Reporter.kt
index 846b9ae..a11659e 100644
--- a/src/main/java/com/android/tools/metalava/Reporter.kt
+++ b/src/main/java/com/android/tools/metalava/Reporter.kt
@@ -19,13 +19,14 @@
 import com.android.SdkConstants.ATTR_VALUE
 import com.android.tools.metalava.Severity.ERROR
 import com.android.tools.metalava.Severity.HIDDEN
+import com.android.tools.metalava.Severity.INFO
 import com.android.tools.metalava.Severity.INHERIT
 import com.android.tools.metalava.Severity.LINT
 import com.android.tools.metalava.Severity.WARNING
 import com.android.tools.metalava.doclava1.Errors
 import com.android.tools.metalava.model.AnnotationArrayAttributeValue
 import com.android.tools.metalava.model.Item
-import com.android.tools.metalava.model.psi.PsiConstructorItem
+import com.android.tools.metalava.model.configuration
 import com.android.tools.metalava.model.psi.PsiItem
 import com.android.tools.metalava.model.text.TextItem
 import com.intellij.openapi.util.TextRange
@@ -34,6 +35,7 @@
 import com.intellij.openapi.vfs.VirtualFile
 import com.intellij.psi.PsiCompiledElement
 import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiModifierListOwner
 import com.intellij.psi.impl.light.LightElement
 import java.io.File
 
@@ -45,6 +47,12 @@
     HIDDEN("hidden"),
 
     /**
+     * Information level are for issues that are informational only; may or
+     * may not be a problem.
+     */
+    INFO("info"),
+
+    /**
      * Lint level means that we encountered inconsistent or broken documentation.
      * These should be resolved, but don't impact API compatibility.
      */
@@ -66,81 +74,78 @@
 }
 
 open class Reporter(private val rootFolder: File? = null) {
-    var hasErrors = false
+    private var hasErrors = false
 
-    fun error(item: Item?, message: String, id: Errors.Error? = null) {
-        error(item?.psi(), message, id)
+    fun error(item: Item?, message: String, id: Errors.Error? = null): Boolean {
+        return error(item?.psi(), message, id)
     }
 
-    fun warning(item: Item?, message: String, id: Errors.Error? = null) {
-        warning(item?.psi(), message, id)
+    fun warning(item: Item?, message: String, id: Errors.Error? = null): Boolean {
+        return warning(item?.psi(), message, id)
     }
 
-    fun error(element: PsiElement?, message: String, id: Errors.Error? = null) {
+    fun error(element: PsiElement?, message: String, id: Errors.Error? = null): Boolean {
         // Using lowercase since that's the convention doclava1 is using
-        report(ERROR, element, message, id)
+        return report(ERROR, element, message, id)
     }
 
-    fun warning(element: PsiElement?, message: String, id: Errors.Error? = null) {
-        report(WARNING, element, message, id)
+    fun warning(element: PsiElement?, message: String, id: Errors.Error? = null): Boolean {
+        return report(WARNING, element, message, id)
     }
 
-    fun report(id: Errors.Error, element: PsiElement?, message: String) {
-        report(id.level, element, message, id)
+    fun report(id: Errors.Error, element: PsiElement?, message: String): Boolean {
+        return report(configuration.getSeverity(id), element, message, id)
     }
 
-    fun report(id: Errors.Error, file: File?, message: String) {
-        report(id.level, file?.path, message, id)
+    fun report(id: Errors.Error, file: File?, message: String): Boolean {
+        return report(configuration.getSeverity(id), file?.path, message, id)
     }
 
-    fun report(id: Errors.Error, item: Item?, message: String) {
-        if (isSuppressed(id, item)) {
-            return
+    fun report(id: Errors.Error, item: Item?, message: String): Boolean {
+        if (isSuppressed(id, item, message)) {
+            return false
         }
 
-        when (item) {
+        val severity = configuration.getSeverity(id)
+        return when (item) {
             is PsiItem -> {
-                var psi = item.psi()
-
-                // If no PSI element, is this a synthetic/implicit constructor? If so
-                // grab the parent class' PSI element instead for file/location purposes
-
-                if (item is PsiConstructorItem && item.implicitConstructor &&
-                    psi?.containingFile?.virtualFile == null
-                ) {
-                    psi = item.containingClass().psi()
-                }
-
-                report(id.level, psi, message, id)
+                report(severity, item.psi(), message, id)
             }
-            is TextItem -> report(id.level, (item as? TextItem)?.position.toString(), message, id)
-            else -> report(id.level, "<unknown location>", message, id)
+            is TextItem -> report(severity, (item as? TextItem)?.position.toString(), message, id)
+            else -> report(severity, "<unknown location>", message, id)
         }
     }
 
-    private fun isSuppressed(id: Errors.Error, item: Item?): Boolean {
+    fun isSuppressed(id: Errors.Error, item: Item? = null, message: String? = null): Boolean {
+        val severity = configuration.getSeverity(id)
+        if (severity == HIDDEN) {
+            return true
+        }
+
         item ?: return false
 
-        if (id.level == LINT || id.level == WARNING || id.level == ERROR) {
-            val id1 = "Doclava${id.code}"
-            val id2 = id.name
+        if (severity == LINT || severity == WARNING || severity == ERROR) {
             val annotation = item.modifiers.findAnnotation("android.annotation.SuppressLint")
             if (annotation != null) {
                 val attribute = annotation.findAttribute(ATTR_VALUE)
                 if (attribute != null) {
+                    val id1 = "Doclava${id.code}"
+                    val id2 = id.name
                     val value = attribute.value
                     if (value is AnnotationArrayAttributeValue) {
                         // Example: @SuppressLint({"DocLava1", "DocLava2"})
                         for (innerValue in value.values) {
-                            val string = innerValue.value()
-                            if (id1 == string || id2 != null && id2 == string) {
+                            val string = innerValue.value()?.toString() ?: continue
+                            if (suppressMatches(string, id1, message) || suppressMatches(string, id2, message)) {
                                 return true
                             }
                         }
                     } else {
                         // Example: @SuppressLint("DocLava1")
-                        val string = value.value()
-                        if (id1 == string || id2 != null && id2 == string) {
+                        val string = value.value()?.toString()
+                        if (string != null && (
+                                suppressMatches(string, id1, message) || suppressMatches(string, id2, message))
+                        ) {
                             return true
                         }
                     }
@@ -151,6 +156,22 @@
         return false
     }
 
+    private fun suppressMatches(value: String, id: String?, message: String?): Boolean {
+        id ?: return false
+
+        if (value == id) {
+            return true
+        }
+
+        if (message != null && value.startsWith(id) && value.endsWith(message) &&
+            (value == "$id:$message" || value == "$id: $message")
+        ) {
+            return true
+        }
+
+        return false
+    }
+
     private fun getTextRange(element: PsiElement): TextRange? {
         var range: TextRange? = null
 
@@ -168,7 +189,7 @@
         return range
     }
 
-    private fun elementToLocation(element: PsiElement?): String? {
+    fun elementToLocation(element: PsiElement?, includeDocs: Boolean = true): String? {
         element ?: return null
         val psiFile = element.containingFile ?: return null
         val virtualFile = psiFile.virtualFile ?: return null
@@ -182,14 +203,21 @@
                 file.path
             }
 
-        val range = getTextRange(element)
-        return if (range == null) {
-            // No source offsets, just use filename
-            path
+        // Skip doc comments for classes, methods and fields; we usually want to point right to
+        // the class/method/field definition
+        val rangeElement = if (!includeDocs && element is PsiModifierListOwner) {
+            element.modifierList ?: element
+        } else
+            element
+
+        val range = getTextRange(rangeElement)
+        val lineNumber = if (range == null) {
+            // No source offsets, use invalid line number
+            -1
         } else {
-            val lineNumber = getLineNumber(psiFile.text, range.startOffset) + 1
-            "$path:$lineNumber"
+            getLineNumber(psiFile.text, range.startOffset) + 1
         }
+        return if (lineNumber > 0) "$path:$lineNumber" else path
     }
 
     /** Returns the 0-based line number */
@@ -205,12 +233,12 @@
         return line
     }
 
-    open fun report(severity: Severity, element: PsiElement?, message: String, id: Errors.Error? = null) {
+    open fun report(severity: Severity, element: PsiElement?, message: String, id: Errors.Error? = null): Boolean {
         if (severity == HIDDEN) {
-            return
+            return false
         }
 
-        report(severity, elementToLocation(element), message, id)
+        return report(severity, elementToLocation(element), message, id)
     }
 
     open fun report(
@@ -219,9 +247,9 @@
         message: String,
         id: Errors.Error? = null,
         color: Boolean = options.color
-    ) {
+    ): Boolean {
         if (severity == HIDDEN) {
-            return
+            return false
         }
 
         val effectiveSeverity =
@@ -246,6 +274,7 @@
             }
             when (effectiveSeverity) {
                 LINT -> sb.append(terminalAttributes(foreground = TerminalColor.CYAN)).append("lint: ")
+                INFO -> sb.append(terminalAttributes(foreground = TerminalColor.CYAN)).append("info: ")
                 WARNING -> sb.append(terminalAttributes(foreground = TerminalColor.YELLOW)).append("warning: ")
                 ERROR -> sb.append(terminalAttributes(foreground = TerminalColor.RED)).append("error: ")
                 INHERIT, HIDDEN -> {
@@ -262,6 +291,7 @@
                 // according to doclava1 there are some people or tools parsing old format
                 when (effectiveSeverity) {
                     LINT -> sb.append("lint ")
+                    INFO -> sb.append("info ")
                     WARNING -> sb.append("warning ")
                     ERROR -> sb.append("error ")
                     INHERIT, HIDDEN -> {
@@ -272,6 +302,7 @@
             } else {
                 when (effectiveSeverity) {
                     LINT -> sb.append("lint: ")
+                    INFO -> sb.append("info: ")
                     WARNING -> sb.append("warning: ")
                     ERROR -> sb.append("error: ")
                     INHERIT, HIDDEN -> {
@@ -289,6 +320,7 @@
             }
         }
         print(sb.toString())
+        return true
     }
 
     open fun print(message: String) {
diff --git a/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt b/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt
index 78117c1..38f6a26 100644
--- a/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt
+++ b/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt
@@ -19,7 +19,8 @@
 import com.android.SdkConstants
 import com.android.SdkConstants.DOT_CLASS
 import com.android.SdkConstants.DOT_JAR
-import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.AnnotationRetention
+import com.android.tools.metalava.model.Codebase
 import com.google.common.io.Closer
 import org.objectweb.asm.ClassReader
 import org.objectweb.asm.ClassVisitor
@@ -44,16 +45,43 @@
  * package private when compiled and packaged on their own such that annotation processors
  * can find them. See b/110532131 for details.
  */
-
 class RewriteAnnotations {
-    /** Copies annotation source files from [source] to [target] */
-    fun copyAnnotations(source: File, target: File, pkg: String = "") {
+    /** Modifies annotation source files such that they are package private */
+    fun modifyAnnotationSources(codebase: Codebase?, source: File, target: File, pkg: String = "") {
         val fileName = source.name
         if (fileName.endsWith(SdkConstants.DOT_JAVA)) {
             if (!options.includeSourceRetentionAnnotations) {
                 // Only copy non-source retention annotation classes
                 val qualifiedName = pkg + "." + fileName.substring(0, fileName.indexOf('.'))
-                if (!AnnotationItem.hasClassRetention(qualifiedName)) {
+                if (hasSourceRetention(codebase, qualifiedName)) {
+                    return
+                }
+            }
+
+            // Copy and convert
+            target.parentFile.mkdirs()
+            target.writeText(
+                source.readText(Charsets.UTF_8).replace(
+                    "\npublic @interface",
+                    "\n@interface"
+                )
+            )
+        } else if (source.isDirectory) {
+            val newPackage = if (pkg.isEmpty()) fileName else "$pkg.$fileName"
+            source.listFiles()?.forEach {
+                modifyAnnotationSources(codebase, it, File(target, it.name), newPackage)
+            }
+        }
+    }
+
+    /** Copies annotation source files from [source] to [target] */
+    fun copyAnnotations(codebase: Codebase, source: File, target: File, pkg: String = "") {
+        val fileName = source.name
+        if (fileName.endsWith(SdkConstants.DOT_JAVA)) {
+            if (!options.includeSourceRetentionAnnotations) {
+                // Only copy non-source retention annotation classes
+                val qualifiedName = pkg + "." + fileName.substring(0, fileName.indexOf('.'))
+                if (hasSourceRetention(codebase, qualifiedName)) {
                     return
                 }
             }
@@ -64,25 +92,7 @@
         } else if (source.isDirectory) {
             val newPackage = if (pkg.isEmpty()) fileName else "$pkg.$fileName"
             source.listFiles()?.forEach {
-                copyAnnotations(it, File(target, it.name), newPackage)
-            }
-        }
-    }
-
-    /** Modifies annotation source files such that they are package private */
-    fun modifyAnnotationSources(source: File, target: File) {
-        if (source.name.endsWith(SdkConstants.DOT_JAVA)) {
-            // Copy and convert
-            target.parentFile.mkdirs()
-            target.writeText(
-                source.readText(Charsets.UTF_8).replace(
-                    "\npublic @interface",
-                    "\n@interface"
-                )
-            )
-        } else if (source.isDirectory) {
-            source.listFiles()?.forEach {
-                modifyAnnotationSources(it, File(target, it.name))
+                copyAnnotations(codebase, it, File(target, it.name), newPackage)
             }
         }
     }
@@ -103,6 +113,25 @@
         }
     }
 
+    /** Returns true if the given annotation class name has source retention as far as the stub
+     * annotations are concerned.
+     */
+    private fun hasSourceRetention(codebase: Codebase?, qualifiedName: String): Boolean {
+        when {
+            qualifiedName == "androidx.annotation.RecentlyNullable" ||
+                qualifiedName == "androidx.annotation.RecentlyNonNull" -> return false
+            qualifiedName.startsWith("androidx.annotation.") -> return true
+        }
+
+        // See if the annotation is pointing to an annotation class that is part of the API; if not, skip it.
+        if (codebase != null) {
+            val cls = codebase.findClass(qualifiedName) ?: return true
+            return cls.isAnnotationType() && cls.getRetention() == AnnotationRetention.SOURCE
+        }
+
+        return false
+    }
+
     /** Writes the bytecode for the compiled annotations in the given file such that they are package private */
     private fun rewriteAnnotations(file: File) {
         when {
@@ -163,7 +192,7 @@
     }
 
     private fun rewriteJar(file: File) {
-        val temp = File(file.name + ".temp-$PROGRAM_NAME")
+        val temp = File(file.path + ".temp-$PROGRAM_NAME")
         rewriteJar(file, temp)
         file.delete()
         temp.renameTo(file)
@@ -211,7 +240,7 @@
                     name.indexOf("$") == -1 &&
                     !entry.isDirectory
                 ) {
-                    val bytes = zis.readBytes(entry.size.toInt())
+                    val bytes = zis.readBytes()
                     val rewritten = rewriteClass(bytes, name)
                     if (rewritten != null) {
                         zos.write(rewritten)
diff --git a/src/main/java/com/android/tools/metalava/SdkFileWriter.kt b/src/main/java/com/android/tools/metalava/SdkFileWriter.kt
index 0d68845..23104fb 100644
--- a/src/main/java/com/android/tools/metalava/SdkFileWriter.kt
+++ b/src/main/java/com/android/tools/metalava/SdkFileWriter.kt
@@ -203,13 +203,13 @@
             // pass for now
         } finally {
             try {
-                if (bw != null) bw.close()
+                bw?.close()
             } catch (e: IOException) {
                 // pass for now
             }
 
             try {
-                if (fw != null) fw.close()
+                fw?.close()
             } catch (e: IOException) {
                 // pass for now
             }
diff --git a/src/main/java/com/android/tools/metalava/SignatureFileLoader.kt b/src/main/java/com/android/tools/metalava/SignatureFileLoader.kt
new file mode 100644
index 0000000..97f04d4
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/SignatureFileLoader.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.tools.metalava.doclava1.ApiFile
+import com.android.tools.metalava.doclava1.ApiParseException
+import com.android.tools.metalava.model.Codebase
+import java.io.File
+
+object SignatureFileLoader {
+    private val map = mutableMapOf<File, Codebase>()
+    fun load(
+        file: File,
+        kotlinStyleNulls: Boolean = false,
+        supportsStagedNullability: Boolean = false
+    ): Codebase {
+        return map[file] ?: run {
+            val loaded = loadFromSignatureFiles(file, kotlinStyleNulls, supportsStagedNullability)
+            map[file] = loaded
+            loaded
+        }
+    }
+
+    private fun loadFromSignatureFiles(
+        file: File,
+        kotlinStyleNulls: Boolean,
+        supportsStagedNullability: Boolean = false
+    ): Codebase {
+        try {
+            val codebase = ApiFile.parseApi(File(file.path), kotlinStyleNulls, supportsStagedNullability)
+            codebase.description = "Codebase loaded from ${file.path}"
+            return codebase
+        } catch (ex: ApiParseException) {
+            val message = "Unable to parse signature file $file: ${ex.message}"
+            throw DriverException(message)
+        }
+    }
+}
\ 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 5d7be95..65ff3b2 100644
--- a/src/main/java/com/android/tools/metalava/SignatureWriter.kt
+++ b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
@@ -16,6 +16,8 @@
 
 package com.android.tools.metalava
 
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.AnnotationTarget
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.ConstructorItem
 import com.android.tools.metalava.model.FieldItem
@@ -24,13 +26,19 @@
 import com.android.tools.metalava.model.ModifierList
 import com.android.tools.metalava.model.PackageItem
 import com.android.tools.metalava.model.ParameterItem
+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.model.javaEscapeString
 import com.android.tools.metalava.model.visitors.ApiVisitor
 import java.io.PrintWriter
 import java.util.function.Predicate
 
+/** Current signature format. */
+const val SIGNATURE_FORMAT = "2.0"
+
+/** Marker comment at the beginning of the signature file */
+const val SIGNATURE_FORMAT_PREFIX = "// Signature format: "
+
 class SignatureWriter(
     private val writer: PrintWriter,
     filterEmit: Predicate<Item>,
@@ -43,11 +51,20 @@
     methodComparator = MethodItem.comparator,
     fieldComparator = FieldItem.comparator,
     filterEmit = filterEmit,
-    filterReference = filterReference
+    filterReference = filterReference,
+    showUnannotated = options.showUnannotated
 ) {
+    init {
+        if (options.includeSignatureFormatVersion) {
+            writer.print(SIGNATURE_FORMAT_PREFIX)
+            writer.println(SIGNATURE_FORMAT)
+        }
+    }
 
     override fun visitPackage(pkg: PackageItem) {
-        writer.print("package ${pkg.qualifiedName()} {\n\n")
+        writer.print("package ")
+        writeModifiers(pkg)
+        writer.print("${pkg.qualifiedName()} {\n\n")
     }
 
     override fun afterVisitPackage(pkg: PackageItem) {
@@ -78,6 +95,15 @@
         writer.print("\n")
     }
 
+    override fun visitProperty(property: PropertyItem) {
+        writer.print("    property ")
+        writeModifiers(property)
+        writeType(property, property.type(), property.modifiers)
+        writer.print(' ')
+        writer.print(property.name())
+        writer.print(";\n")
+    }
+
     override fun visitMethod(method: MethodItem) {
         if (compatibility.skipAnnotationInstanceMethods && method.containingClass().isAnnotationType() &&
             !method.modifiers.isStatic()
@@ -157,13 +183,11 @@
             writer = writer,
             modifiers = item.modifiers,
             item = item,
+            target = AnnotationTarget.SIGNATURE_FILE,
             includeDeprecated = true,
             includeAnnotations = compatibility.annotationsInSignatures,
             skipNullnessAnnotations = options.outputKotlinStyleNulls,
-            omitCommonPackages = options.omitCommonPackages,
-            onlyIncludeSignatureAnnotations = true,
-            onlyIncludeStubAnnotations = false,
-            onlyIncludeClassRetentionAnnotations = false
+            omitCommonPackages = options.omitCommonPackages
         )
     }
 
@@ -182,7 +206,10 @@
         else cls.filteredSuperClassType(filterReference)
         if (superClass != null && !superClass.isJavaLangObject()) {
             val superClassString =
-                superClass.toTypeString(erased = compatibility.omitTypeParametersInInterfaces)
+                superClass.toTypeString(
+                    erased = compatibility.omitTypeParametersInInterfaces,
+                    context = superClass.asClass()
+                )
             writer.print(" extends ")
             writer.print(superClassString)
         }
@@ -228,7 +255,12 @@
             writer.print(label)
             all.sortedWith(TypeItem.comparator).forEach { item ->
                 writer.print(" ")
-                writer.print(item.toTypeString(erased = compatibility.omitTypeParametersInInterfaces))
+                writer.print(
+                    item.toTypeString(
+                        erased = compatibility.omitTypeParametersInInterfaces,
+                        context = item.asClass()
+                    )
+                )
             }
         }
     }
@@ -260,15 +292,14 @@
                 }
             }
             if (options.outputDefaultValues && parameter.hasDefaultValue()) {
-                writer.print(" = \"")
+                writer.print(" = ")
                 val defaultValue = parameter.defaultValue()
                 if (defaultValue != null) {
-                    writer.print(javaEscapeString(defaultValue))
+                    writer.print(defaultValue)
                 } else {
                     // null is a valid default value!
                     writer.print("null")
                 }
-                writer.print("\"")
             }
         }
         writer.print(")")
@@ -282,9 +313,9 @@
         type ?: return
 
         var typeString = type.toTypeString(
-            erased = false,
             outerAnnotations = false,
-            innerAnnotations = compatibility.annotationsInSignatures
+            innerAnnotations = compatibility.annotationsInSignatures,
+            erased = false
         )
 
         // Strip java.lang. prefix?
@@ -313,18 +344,21 @@
         writer.print(typeString)
 
         if (options.outputKotlinStyleNulls && !type.primitive) {
-            var nullable: Boolean? = null
-            for (annotation in modifiers.annotations()) {
-                if (annotation.isNullable()) {
-                    nullable = true
-                } else if (annotation.isNonNull()) {
-                    nullable = false
+            var nullable: Boolean? = AnnotationItem.getImplicitNullness(item)
+
+            if (nullable == null) {
+                for (annotation in modifiers.annotations()) {
+                    if (annotation.isNullable()) {
+                        nullable = true
+                    } else if (annotation.isNonNull()) {
+                        nullable = false
+                    }
                 }
             }
             when (nullable) {
                 null -> writer.write("!")
                 true -> writer.write("?")
-            // else: non-null: nothing to write
+                // else: non-null: nothing to write
             }
         }
     }
diff --git a/src/main/java/com/android/tools/metalava/StubWriter.kt b/src/main/java/com/android/tools/metalava/StubWriter.kt
index de41742..a5c500a 100644
--- a/src/main/java/com/android/tools/metalava/StubWriter.kt
+++ b/src/main/java/com/android/tools/metalava/StubWriter.kt
@@ -19,6 +19,7 @@
 import com.android.tools.metalava.doclava1.ApiPredicate
 import com.android.tools.metalava.doclava1.Errors
 import com.android.tools.metalava.doclava1.FilterPredicate
+import com.android.tools.metalava.model.AnnotationTarget
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.ConstructorItem
@@ -29,23 +30,22 @@
 import com.android.tools.metalava.model.ModifierList
 import com.android.tools.metalava.model.PackageItem
 import com.android.tools.metalava.model.TypeParameterList
+import com.android.tools.metalava.model.psi.EXPAND_DOCUMENTATION
 import com.android.tools.metalava.model.psi.PsiClassItem
 import com.android.tools.metalava.model.psi.trimDocIndent
 import com.android.tools.metalava.model.visitors.ApiVisitor
-import com.google.common.io.Files
 import java.io.BufferedWriter
 import java.io.File
 import java.io.FileWriter
 import java.io.IOException
 import java.io.PrintWriter
-import kotlin.text.Charsets.UTF_8
 
 class StubWriter(
     private val codebase: Codebase,
     private val stubsDir: File,
     private val generateAnnotations: Boolean = false,
     private val preFiltered: Boolean = true,
-    docStubs: Boolean
+    private val docStubs: Boolean
 ) : ApiVisitor(
     visitConstructorsAsMethods = false,
     nestInnerClasses = true,
@@ -54,10 +54,11 @@
     // Methods are by default sorted in source order in stubs, to encourage methods
     // that are near each other in the source to show up near each other in the documentation
     methodComparator = MethodItem.sourceOrderComparator,
-    filterEmit = FilterPredicate(ApiPredicate(codebase, ignoreShown = true, includeDocOnly = docStubs)),
-    filterReference = ApiPredicate(codebase, ignoreShown = true, includeDocOnly = docStubs),
+    filterEmit = FilterPredicate(ApiPredicate(ignoreShown = true, includeDocOnly = docStubs)),
+    filterReference = ApiPredicate(ignoreShown = true, includeDocOnly = docStubs),
     includeEmptyOuterClasses = true
 ) {
+    private val annotationTarget = if (docStubs) AnnotationTarget.DOC_STUBS_FILE else AnnotationTarget.SDK_STUBS_FILE
 
     private val sourceList = StringBuilder(20000)
 
@@ -73,12 +74,12 @@
     fun writeSourceList(target: File, root: File?) {
         target.parentFile?.mkdirs()
         val contents = if (root != null) {
-            val path = root.path.replace('\\', '/')
+            val path = root.path.replace('\\', '/') + "/"
             sourceList.toString().replace(path, "")
         } else {
             sourceList.toString()
         }
-        Files.asCharSink(target, UTF_8).write(contents)
+        target.writeText(contents)
     }
 
     private fun startFile(sourceFile: File) {
@@ -93,10 +94,14 @@
 
         writePackageInfo(pkg)
 
-        codebase.getPackageDocs()?.getDocs(pkg)?.let { writeDocOverview(pkg, it) }
+        if (docStubs) {
+            codebase.getPackageDocs()?.let { packageDocs ->
+                packageDocs.getOverviewDocumentation(pkg)?.let { writeDocOverview(pkg, it) }
+            }
+        }
     }
 
-    private fun writeDocOverview(pkg: PackageItem, content: String) {
+    fun writeDocOverview(pkg: PackageItem, content: String) {
         if (content.isBlank()) {
             return
         }
@@ -118,13 +123,8 @@
     }
 
     private fun writePackageInfo(pkg: PackageItem) {
-        if (!generateAnnotations) {
-            // package-info,java is only needed to record annotations
-            return
-        }
-
         val annotations = pkg.modifiers.annotations()
-        if (annotations.isNotEmpty()) {
+        if (annotations.isNotEmpty() && generateAnnotations || !pkg.documentation.isBlank()) {
             val sourceFile = File(getPackageDir(pkg), "package-info.java")
             val writer = try {
                 PrintWriter(BufferedWriter(FileWriter(sourceFile)))
@@ -134,17 +134,19 @@
             }
             startFile(sourceFile)
 
-            ModifierList.writeAnnotations(
-                list = pkg.modifiers,
-                separateLines = true,
-                // Some bug in UAST triggers duplicate nullability annotations
-                // here; make sure the are filtered out
-                filterDuplicates = true,
-                onlyIncludeSignatureAnnotations = false,
-                onlyIncludeStubAnnotations = true,
-                onlyIncludeClassRetentionAnnotations = true,
-                writer = writer
-            )
+            appendDocumentation(pkg, writer)
+
+            if (annotations.isNotEmpty()) {
+                ModifierList.writeAnnotations(
+                    list = pkg.modifiers,
+                    separateLines = true,
+                    // Some bug in UAST triggers duplicate nullability annotations
+                    // here; make sure the are filtered out
+                    filterDuplicates = true,
+                    target = annotationTarget,
+                    writer = writer
+                )
+            }
             writer.println("package ${pkg.qualifiedName()};")
 
             writer.flush()
@@ -203,16 +205,21 @@
                 writer.println()
             }
 
-            compilationUnit?.getImportStatements(filterReference)?.let {
-                for (item in it) {
-                    when (item) {
-                        is ClassItem ->
-                            writer.println("import ${item.qualifiedName()};")
-                        is MemberItem ->
-                            writer.println("import static ${item.containingClass().qualifiedName()}.${item.name()};")
+            @Suppress("ConstantConditionIf")
+            if (EXPAND_DOCUMENTATION) {
+                compilationUnit?.getImportStatements(filterReference)?.let {
+                    for (item in it) {
+                        when (item) {
+                            is PackageItem ->
+                                writer.println("import ${item.qualifiedName()}.*;")
+                            is ClassItem ->
+                                writer.println("import ${item.qualifiedName()};")
+                            is MemberItem ->
+                                writer.println("import static ${item.containingClass().qualifiedName()}.${item.name()};")
+                        }
                     }
+                    writer.println()
                 }
-                writer.println()
             }
         }
 
@@ -246,13 +253,29 @@
         if (cls.isEnum()) {
             var first = true
             // Enums should preserve the original source order, not alphabetical etc sort
-            for (field in cls.fields().sortedBy { it.sortingRank }) {
+            for (field in cls.filteredFields(filterReference, true).sortedBy { it.sortingRank }) {
                 if (field.isEnumConstant()) {
                     if (first) {
                         first = false
                     } else {
-                        writer.write(", ")
+                        writer.write(",\n")
                     }
+                    appendDocumentation(field, writer)
+
+                    // Can't just appendModifiers(field, true, true): enum constants
+                    // don't take modifier lists, only annotations
+                    ModifierList.writeAnnotations(
+                        item = field,
+                        target = annotationTarget,
+                        runtimeAnnotationsOnly = !generateAnnotations,
+                        includeDeprecated = true,
+                        writer = writer,
+                        separateLines = true,
+                        list = field.modifiers,
+                        skipNullnessAnnotations = false,
+                        omitCommonPackages = false
+                    )
+
                     writer.write(field.name())
                 }
             }
@@ -263,11 +286,17 @@
     }
 
     private fun appendDocumentation(item: Item, writer: PrintWriter) {
-        val documentation = item.documentation
-        if (documentation.isNotBlank()) {
-            val trimmed = trimDocIndent(documentation)
-            writer.println(trimmed)
-            writer.println()
+        if (options.includeDocumentationInStubs || docStubs) {
+            val documentation = if (docStubs && EXPAND_DOCUMENTATION) {
+                item.fullyQualifiedDocumentation()
+            } else {
+                item.documentation
+            }
+            if (documentation.isNotBlank()) {
+                val trimmed = trimDocIndent(documentation)
+                writer.println(trimmed)
+                writer.println()
+            }
         }
     }
 
@@ -297,16 +326,18 @@
         removeFinal: Boolean = false,
         addPublic: Boolean = false
     ) {
-        if (item.deprecated && generateAnnotations) {
-            writer.write("@Deprecated ")
-        }
+        val separateLines = item is ClassItem || item is MethodItem
 
         ModifierList.write(
-            writer, modifiers, item, removeAbstract = removeAbstract, removeFinal = removeFinal,
-            addPublic = addPublic, includeAnnotations = generateAnnotations,
-            onlyIncludeSignatureAnnotations = false,
-            onlyIncludeStubAnnotations = true,
-            onlyIncludeClassRetentionAnnotations = true
+            writer, modifiers, item,
+            target = annotationTarget,
+            includeAnnotations = true,
+            includeDeprecated = true,
+            runtimeAnnotationsOnly = !generateAnnotations,
+            removeAbstract = removeAbstract,
+            removeFinal = removeFinal,
+            addPublic = addPublic,
+            separateLines = separateLines
         )
     }
 
@@ -426,7 +457,7 @@
                         writer.write(", ")
                     }
                     val type = parameter.type()
-                    val typeString = type.toErasedTypeString()
+                    val typeString = type.toErasedTypeString(it)
                     if (!type.primitive) {
                         if (includeCasts) {
                             writer.write("(")
@@ -496,7 +527,12 @@
         generateTypeParameterList(typeList = method.typeParameterList(), addSpace = true)
 
         val returnType = method.returnType()
-        writer.print(returnType?.toTypeString(outerAnnotations = false, innerAnnotations = generateAnnotations))
+        writer.print(
+            returnType?.toTypeString(
+                outerAnnotations = false,
+                innerAnnotations = generateAnnotations
+            )
+        )
 
         writer.print(' ')
         writer.print(method.name())
@@ -530,7 +566,12 @@
 
         appendDocumentation(field, writer)
         appendModifiers(field, false, false)
-        writer.print(field.type().toTypeString(outerAnnotations = false, innerAnnotations = generateAnnotations))
+        writer.print(
+            field.type().toTypeString(
+                outerAnnotations = false,
+                innerAnnotations = generateAnnotations
+            )
+        )
         writer.print(' ')
         writer.print(field.name())
         val needsInitialization =
@@ -592,4 +633,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/android/tools/metalava/apilevels/AddApisFromCodebase.kt b/src/main/java/com/android/tools/metalava/apilevels/AddApisFromCodebase.kt
index f46ca3a..58afb9a 100644
--- a/src/main/java/com/android/tools/metalava/apilevels/AddApisFromCodebase.kt
+++ b/src/main/java/com/android/tools/metalava/apilevels/AddApisFromCodebase.kt
@@ -25,7 +25,6 @@
 /** Visits the API codebase and inserts into the [Api] the classes, methods and fields */
 fun addApisFromCodebase(api: Api, apiLevel: Int, codebase: Codebase) {
     codebase.accept(object : ApiVisitor(
-        codebase,
         visitConstructorsAsMethods = true,
         nestInnerClasses = false
     ) {
@@ -63,6 +62,9 @@
         }
 
         override fun visitMethod(method: MethodItem) {
+            if (method.isPrivate || method.isPackagePrivate) {
+                return
+            }
             currentClass?.addMethod(
                 method.internalName() +
                     // Use "V" instead of the type of the constructor for backwards compatibility
@@ -72,6 +74,10 @@
         }
 
         override fun visitField(field: FieldItem) {
+            if (field.isPrivate || field.isPackagePrivate) {
+                return
+            }
+
             // We end up moving constants from interfaces in the codebase but that's not the
             // case in older bytecode
             if (field.isCloned()) {
diff --git a/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java b/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java
index c5a7d59..59f467b 100644
--- a/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java
+++ b/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java
@@ -36,7 +36,7 @@
 /**
  * Reads all the android.jar files found in an SDK and generate a map of {@link ApiClass}.
  */
-public class AndroidJarReader {
+class AndroidJarReader {
     private int mMinApi;
     private int mCurrentApi;
     private File mCurrentJar;
@@ -120,16 +120,12 @@
 
             if (name.endsWith(".class")) {
                 byte[] bytes = ByteStreams.toByteArray(zis);
-                if (bytes == null) {
-                    System.err.println("Warning: Couldn't read " + name);
-                    entry = zis.getNextEntry();
-                    continue;
-                }
-
                 ClassReader reader = new ClassReader(bytes);
                 ClassNode classNode = new ClassNode(Opcodes.ASM5);
                 reader.accept(classNode, 0 /*flags*/);
 
+                // TODO: Skip package private classes; use metalava's heuristics
+
                 ApiClass theClass = api.addClass(classNode.name, apiLevel,
                     (classNode.access & Opcodes.ACC_DEPRECATED) != 0);
 
@@ -146,7 +142,7 @@
                 // fields
                 for (Object field : classNode.fields) {
                     FieldNode fieldNode = (FieldNode) field;
-                    if ((fieldNode.access & Opcodes.ACC_PRIVATE) != 0) {
+                    if (((fieldNode.access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0)) {
                         continue;
                     }
                     if (!fieldNode.name.startsWith("this$") &&
@@ -159,7 +155,7 @@
                 // methods
                 for (Object method : classNode.methods) {
                     MethodNode methodNode = (MethodNode) method;
-                    if ((methodNode.access & Opcodes.ACC_PRIVATE) != 0) {
+                    if (((methodNode.access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0)) {
                         continue;
                     }
                     if (!methodNode.name.equals("<clinit>")) {
diff --git a/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java b/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java
index ab5af66..3e06a74 100644
--- a/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java
+++ b/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java
@@ -37,7 +37,7 @@
      * @param version    an API version for which the API element existed
      * @param deprecated whether the API element was deprecated in the API version in question
      */
-    public ApiElement(String name, int version, boolean deprecated) {
+    ApiElement(String name, int version, boolean deprecated) {
         assert name != null;
         assert version > 0;
         mName = name;
@@ -52,11 +52,11 @@
      * @param name    the name of the API element
      * @param version an API version for which the API element existed
      */
-    public ApiElement(String name, int version) {
+    ApiElement(String name, int version) {
         this(name, version, false);
     }
 
-    protected ApiElement(String name) {
+    ApiElement(String name) {
         assert name != null;
         mName = name;
     }
@@ -74,7 +74,7 @@
      * @param other the API element to compare to
      * @return true if this API element was introduced not later than {@code other}
      */
-    public final boolean introducedNotLaterThan(ApiElement other) {
+    final boolean introducedNotLaterThan(ApiElement other) {
         return mSince <= other.mSince;
     }
 
@@ -84,7 +84,7 @@
      * @param version    an API version for which the API element existed
      * @param deprecated whether the API element was deprecated in the API version in question
      */
-    public void update(int version, boolean deprecated) {
+    void update(int version, boolean deprecated) {
         assert version > 0;
         if (mSince > version) {
             mSince = version;
@@ -124,7 +124,7 @@
      * @param indent        the whitespace prefix to insert before the XML element
      * @param stream        the stream to print the XML element to
      */
-    public void print(String tag, ApiElement parentElement, String indent, PrintStream stream) {
+    void print(String tag, ApiElement parentElement, String indent, PrintStream stream) {
         print(tag, true, parentElement, indent, stream);
     }
 
@@ -140,8 +140,8 @@
      * @param stream        the stream to print the XML element to
      * @see #printClosingTag(String, String, PrintStream)
      */
-    protected void print(String tag, boolean closeTag, ApiElement parentElement, String indent,
-                         PrintStream stream) {
+    void print(String tag, boolean closeTag, ApiElement parentElement, String indent,
+               PrintStream stream) {
         stream.print(indent);
         stream.print('<');
         stream.print(tag);
@@ -175,7 +175,7 @@
      * @param indent   the whitespace prefix to insert before each XML element
      * @param stream   the stream to print the XML elements to
      */
-    protected void print(Collection<? extends ApiElement> elements, String tag, String indent, PrintStream stream) {
+    void print(Collection<? extends ApiElement> elements, String tag, String indent, PrintStream stream) {
         for (ApiElement element : sortedList(elements)) {
             element.print(tag, this, indent, stream);
         }
@@ -194,14 +194,14 @@
      * @param indent the whitespace prefix to insert before the closing tag
      * @param stream the stream to print the XML element to
      */
-    protected static void printClosingTag(String tag, String indent, PrintStream stream) {
+    static void printClosingTag(String tag, String indent, PrintStream stream) {
         stream.print(indent);
         stream.print("</");
         stream.print(tag);
         stream.println('>');
     }
 
-    protected static String encodeAttribute(String attribute) {
+    private static String encodeAttribute(String attribute) {
         StringBuilder sb = new StringBuilder();
         int n = attribute.length();
         // &, ", ' and < are illegal in attributes; see http://www.w3.org/TR/REC-xml/#NT-AttValue
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 5456d75..f676af4 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
@@ -16,16 +16,20 @@
 
 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.model.AnnotationItem;
+import com.android.tools.metalava.model.DefaultModifierList;
 import com.android.tools.metalava.model.TypeParameterList;
 import com.android.tools.metalava.model.text.TextClassItem;
 import com.android.tools.metalava.model.text.TextConstructorItem;
 import com.android.tools.metalava.model.text.TextFieldItem;
 import com.android.tools.metalava.model.text.TextMethodItem;
+import com.android.tools.metalava.model.text.TextModifiers;
 import com.android.tools.metalava.model.text.TextPackageItem;
 import com.android.tools.metalava.model.text.TextParameterItem;
 import com.android.tools.metalava.model.text.TextParameterItemKt;
+import com.android.tools.metalava.model.text.TextPropertyItem;
 import com.android.tools.metalava.model.text.TextTypeItem;
 import com.android.tools.metalava.model.text.TextTypeParameterList;
 import com.google.common.base.Charsets;
@@ -38,11 +42,12 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.android.tools.metalava.ConstantsKt.ANDROIDX_NOTNULL;
+import static com.android.tools.metalava.ConstantsKt.ANDROIDX_NONNULL;
 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.SignatureWriterKt.SIGNATURE_FORMAT_PREFIX;
 import static com.android.tools.metalava.model.FieldItemKt.javaUnescapeString;
 
 //
@@ -64,12 +69,31 @@
     public static TextCodebase parseApi(String filename, String apiText,
                                         boolean kotlinStyleNulls,
                                         boolean supportsStagedNullability) 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 (apiText.contains("/*")) {
             apiText = ClassNameKt.stripComments(apiText, false); // line comments are used to stash field constants
         }
 
         final Tokenizer tokenizer = new Tokenizer(filename, apiText.toCharArray());
-        final TextCodebase api = new TextCodebase();
+        final TextCodebase api = new TextCodebase(new File(filename));
+        api.setDescription("Codebase loaded from " + filename);
+        if (format != null) {
+            api.setFormat(format);
+        }
         api.setSupportsStagedNullability(supportsStagedNullability);
         api.setKotlinStyleNulls(kotlinStyleNulls);
 
@@ -97,9 +121,20 @@
         TextPackageItem pkg;
 
         token = tokenizer.requireToken();
+
+        // Metalava: including annotations in file now
+        List<String> annotations = getAnnotations(tokenizer, token);
+        TextModifiers modifiers = new TextModifiers(api, DefaultModifierList.PUBLIC, null);
+        if (annotations != null) {
+            modifiers.addAnnotations(annotations);
+        }
+
+        token = tokenizer.getCurrent();
+
         assertIdent(tokenizer, token);
         name = token;
-        pkg = new TextPackageItem(api, name, tokenizer.pos());
+        pkg = new TextPackageItem(api, name, modifiers, tokenizer.pos());
+
         token = tokenizer.requireToken();
         if (!"{".equals(token)) {
             throw new ApiParseException("expected '{' got " + token, tokenizer);
@@ -117,74 +152,20 @@
 
     private static void parseClass(TextCodebase api, TextPackageItem pkg, Tokenizer tokenizer, String token)
         throws ApiParseException {
-        boolean isPublic = false;
-        boolean isProtected = false;
-        boolean isPrivate = false;
-        boolean internal = false;
-        boolean isStatic = false;
-        boolean isFinal = false;
-        boolean isAbstract = false;
-        boolean isDeprecated = false;
         boolean isInterface = false;
         boolean isAnnotation = false;
         boolean isEnum = false;
-        boolean sealed = false;
         String name;
         String qualifiedName;
         String ext = null;
         TextClassItem cl;
 
         // Metalava: including annotations in file now
-        List<String> annotations = null;
-        Pair<String, List<String>> result = getAnnotations(tokenizer, token);
-        if (result != null) {
-            token = result.component1();
-            annotations = result.component2();
-        }
+        List<String> annotations = getAnnotations(tokenizer, token);
+        token = tokenizer.getCurrent();
 
-        processModifiers:
-        while (true) {
-            switch (token) {
-                case "public":
-                    isPublic = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "protected":
-                    isProtected = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "private":
-                    isPrivate = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "internal":
-                    internal = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "static":
-                    isStatic = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "final":
-                    isFinal = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "abstract":
-                    isAbstract = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "deprecated":
-                    isDeprecated = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "sealed":
-                    sealed = true;
-                    token = tokenizer.requireToken();
-                    break;
-                default:
-                    break processModifiers;
-            }
-        }
+        TextModifiers modifiers = parseModifiers(api, tokenizer, token, annotations);
+        token = tokenizer.getCurrent();
 
         if ("class".equals(token)) {
             token = tokenizer.requireToken();
@@ -193,12 +174,12 @@
             token = tokenizer.requireToken();
         } else if ("@interface".equals(token)) {
             // Annotation
-            isAbstract = true;
+            modifiers.setAbstract(true);
             isAnnotation = true;
             token = tokenizer.requireToken();
         } else if ("enum".equals(token)) {
             isEnum = true;
-            isFinal = true;
+            modifiers.setFinal(true);
             ext = JAVA_LANG_ENUM;
             token = tokenizer.requireToken();
         } else {
@@ -218,13 +199,12 @@
 
         token = tokenizer.requireToken();
 
-        cl = new TextClassItem(api, tokenizer.pos(), isPublic, isProtected,
-            isPrivate, internal, isStatic, isInterface, isAbstract, isEnum, isAnnotation,
-            isFinal, sealed, typeInfo.toErasedTypeString(), typeInfo.qualifiedTypeName(),
+        cl = new TextClassItem(api, tokenizer.pos(), modifiers, isInterface, isEnum, isAnnotation,
+            typeInfo.toErasedTypeString(null), typeInfo.qualifiedTypeName(),
             rawName, annotations);
         cl.setContainingPackage(pkg);
         cl.setTypeInfo(typeInfo);
-        cl.setDeprecated(isDeprecated);
+        cl.setDeprecated(modifiers.isDeprecated());
         if ("extends".equals(token)) {
             token = tokenizer.requireToken();
             assertIdent(tokenizer, token);
@@ -276,6 +256,9 @@
             } else if ("enum_constant".equals(token)) {
                 token = tokenizer.requireToken();
                 parseField(api, tokenizer, cl, token, true);
+            } else if ("property".equals(token)) {
+                token = tokenizer.requireToken();
+                parseProperty(api, tokenizer, cl, token);
             } else {
                 throw new ApiParseException("expected ctor, enum_constant, field or method", tokenizer);
             }
@@ -293,18 +276,17 @@
                 type = type.substring(0, type.length() - 1);
             } else if (!type.endsWith("!")) {
                 if (!TextTypeItem.Companion.isPrimitive(type)) { // Don't add nullness on primitive types like void
-                    annotations = mergeAnnotations(annotations, ANDROIDX_NOTNULL);
+                    annotations = mergeAnnotations(annotations, ANDROIDX_NONNULL);
                 }
             }
         } else if (type.endsWith("?") || type.endsWith("!")) {
             throw new ApiParseException("Did you forget to supply --input-kotlin-nulls? Found Kotlin-style null type suffix when parser was not configured " +
                 "to interpret signature file that way: " + type);
         }
-        //noinspection unchecked
         return new Pair<>(type, annotations);
     }
 
-    private static Pair<String, List<String>> getAnnotations(Tokenizer tokenizer, String token) throws ApiParseException {
+    private static List<String> getAnnotations(Tokenizer tokenizer, String token) throws ApiParseException {
         List<String> annotations = null;
 
         while (true) {
@@ -334,59 +316,20 @@
             }
         }
 
-        if (annotations != null) {
-            //noinspection unchecked
-            return new Pair<>(token, annotations);
-        } else {
-            return null;
-        }
+        return annotations;
     }
 
     private static void parseConstructor(TextCodebase api, Tokenizer tokenizer, TextClassItem cl, String token)
         throws ApiParseException {
-        boolean isPublic = false;
-        boolean isProtected = false;
-        boolean isPrivate = false;
-        boolean isInternal = false;
-        boolean isDeprecated = false;
         String name;
         TextConstructorItem method;
 
         // Metalava: including annotations in file now
-        List<String> annotations = null;
-        Pair<String, List<String>> result = getAnnotations(tokenizer, token);
-        if (result != null) {
-            token = result.component1();
-            annotations = result.component2();
-        }
+        List<String> annotations = getAnnotations(tokenizer, token);
+        token = tokenizer.getCurrent();
 
-        processModifiers:
-        while (true) {
-            switch (token) {
-                case "public":
-                    isPublic = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "protected":
-                    isProtected = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "private":
-                    isPrivate = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "internal":
-                    isInternal = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "deprecated":
-                    isDeprecated = true;
-                    token = tokenizer.requireToken();
-                    break;
-                default:
-                    break processModifiers;
-            }
-        }
+        TextModifiers modifiers = parseModifiers(api, tokenizer, token, annotations);
+        token = tokenizer.getCurrent();
 
         assertIdent(tokenizer, token);
         name = token.substring(token.lastIndexOf('.') + 1); // For inner classes, strip outer classes from name
@@ -394,14 +337,8 @@
         if (!"(".equals(token)) {
             throw new ApiParseException("expected (", tokenizer);
         }
-        method = new TextConstructorItem(api, /*typeParameters*/
-            name, /*signature*/ cl, isPublic, isProtected, isPrivate, isInternal, false/*isFinal*/,
-            false/*isStatic*/, /*isSynthetic*/ false/*isAbstract*/, false/*isSynthetic*/,
-            false/*isNative*/, false/* isDefault */,
-            /*isAnnotationElement*/  /*flatSignature*/
-            /*overriddenMethod*/ cl.asTypeInfo(),
-            /*thrownExceptions*/ tokenizer.pos(), annotations);
-        method.setDeprecated(isDeprecated);
+        method = new TextConstructorItem(api, name, cl, modifiers, cl.asTypeInfo(), tokenizer.pos());
+        method.setDeprecated(modifiers.isDeprecated());
         parseParameterList(api, tokenizer, method);
         token = tokenizer.requireToken();
         if ("throws".equals(token)) {
@@ -415,104 +352,17 @@
 
     private static void parseMethod(TextCodebase api, Tokenizer tokenizer, TextClassItem cl, String token)
         throws ApiParseException {
-        boolean isPublic = false;
-        boolean isProtected = false;
-        boolean isPrivate = false;
-        boolean isInternal = false;
-        boolean isStatic = false;
-        boolean isFinal = false;
-        boolean isAbstract = false;
-        boolean isDeprecated = false;
-        boolean isSynchronized = false;
-        boolean isDefault = false;
-        boolean isInfix = false;
-        boolean isOperator = false;
-        boolean isInline = false;
-        boolean isNative = false;
-        boolean isStrictFp = false;
         TextTypeItem returnType;
         String name;
         TextMethodItem method;
         TypeParameterList typeParameterList = TypeParameterList.Companion.getNONE();
 
         // Metalava: including annotations in file now
-        List<String> annotations = null;
-        Pair<String, List<String>> result = getAnnotations(tokenizer, token);
-        if (result != null) {
-            token = result.component1();
-            annotations = result.component2();
-        }
+        List<String> annotations = getAnnotations(tokenizer, token);
+        token = tokenizer.getCurrent();
 
-        // Parse modifiers; we loop here because we're not sure about the order (which can vary
-        // based on metalava's preference flags)
-
-        processModifiers:
-        while (true) {
-            switch (token) {
-                case "public":
-                    isPublic = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "protected":
-                    isProtected = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "private":
-                    isPrivate = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "internal":
-                    isInternal = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "default":
-                    isDefault = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "static":
-                    isStatic = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "final":
-                    isFinal = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "abstract":
-                    isAbstract = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "deprecated":
-                    isDeprecated = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "synchronized":
-                    isSynchronized = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "native":
-                    isNative = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "strictfp":
-                    isStrictFp = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "infix":
-                    isInfix = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "operator":
-                    isOperator = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "inline":
-                    isInline = true;
-                    token = tokenizer.requireToken();
-                    break;
-                default:
-                    break processModifiers;
-            }
-        }
+        TextModifiers modifiers = parseModifiers(api, tokenizer, token, null);
+        token = tokenizer.getCurrent();
 
         if ("<".equals(token)) {
             typeParameterList = parseTypeParameterList(api, tokenizer);
@@ -523,13 +373,13 @@
         Pair<String, List<String>> kotlinTypeSuffix = processKotlinTypeSuffix(api, token, annotations);
         token = kotlinTypeSuffix.getFirst();
         annotations = kotlinTypeSuffix.getSecond();
+        modifiers.addAnnotations(annotations);
         String returnTypeString = token;
 
         token = tokenizer.requireToken();
 
         if (returnTypeString.contains("@") && (returnTypeString.indexOf('<') == -1 ||
                 returnTypeString.indexOf('@') < returnTypeString.indexOf('<'))) {
-            //noinspection StringConcatenationInLoop
             returnTypeString += " " + token;
             token = tokenizer.requireToken();
         }
@@ -552,13 +402,12 @@
 
         assertIdent(tokenizer, token);
         name = token;
-        method = new TextMethodItem(
-            api, name, /*signature*/ cl,
-            isPublic, isProtected, isPrivate, isInternal, isFinal, isStatic, isAbstract,
-            isSynchronized, isNative, isDefault, isStrictFp, isInfix, isOperator, isInline,
-            returnType, tokenizer.pos(), annotations);
-        method.setDeprecated(isDeprecated);
+        method = new TextMethodItem(api, name, cl, modifiers, returnType, tokenizer.pos());
+        method.setDeprecated(modifiers.isDeprecated());
         method.setTypeParameterList(typeParameterList);
+        if (typeParameterList instanceof TextTypeParameterList) {
+            ((TextTypeParameterList) typeParameterList).setOwner(method);
+        }
         token = tokenizer.requireToken();
         if (!"(".equals(token)) {
             throw new ApiParseException("expected (, was " + token, tokenizer);
@@ -592,104 +441,36 @@
 
     private static void parseField(TextCodebase api, Tokenizer tokenizer, TextClassItem cl, String token, boolean isEnum)
         throws ApiParseException {
-        boolean isPublic = false;
-        boolean isProtected = false;
-        boolean isPrivate = false;
-        boolean isInternal = false;
-        boolean isStatic = false;
-        boolean isFinal = false;
-        boolean isDeprecated = false;
-        boolean isTransient = false;
-        boolean isVolatile = false;
-        String type;
-        String name;
-        String val = null;
-        Object v;
-        TextFieldItem field;
+        List<String> annotations = getAnnotations(tokenizer, token);
+        token = tokenizer.getCurrent();
 
-        // Metalava: including annotations in file now
-        List<String> annotations = null;
-        Pair<String, List<String>> result = getAnnotations(tokenizer, token);
-        if (result != null) {
-            token = result.component1();
-            annotations = result.component2();
-        }
-
-        processModifiers:
-        while (true) {
-            switch (token) {
-                case "public":
-                    isPublic = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "protected":
-                    isProtected = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "private":
-                    isPrivate = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "internal":
-                    isInternal = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "static":
-                    isStatic = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "final":
-                    isFinal = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "deprecated":
-                    isDeprecated = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "transient":
-                    isTransient = true;
-                    token = tokenizer.requireToken();
-                    break;
-                case "volatile":
-                    isVolatile = true;
-                    token = tokenizer.requireToken();
-                    break;
-                default:
-                    break processModifiers;
-            }
-        }
-
+        TextModifiers modifiers = parseModifiers(api, tokenizer, token, null);
+        token = tokenizer.getCurrent();
         assertIdent(tokenizer, token);
 
         Pair<String, List<String>> kotlinTypeSuffix = processKotlinTypeSuffix(api, token, annotations);
         token = kotlinTypeSuffix.getFirst();
         annotations = kotlinTypeSuffix.getSecond();
-        type = token;
+        modifiers.addAnnotations(annotations);
+
+        String type = token;
         TextTypeItem typeInfo = api.obtainTypeFromString(type);
 
         token = tokenizer.requireToken();
         assertIdent(tokenizer, token);
-        name = token;
+        String name = token;
         token = tokenizer.requireToken();
+        Object value = null;
         if ("=".equals(token)) {
             token = tokenizer.requireToken(false);
-            val = token;
+            value = parseValue(type, token);
             token = tokenizer.requireToken();
         }
         if (!";".equals(token)) {
             throw new ApiParseException("expected ; found " + token, tokenizer);
         }
-        try {
-            v = parseValue(type, val);
-        } catch (ApiParseException ex) {
-            ex.line = tokenizer.getLine();
-            throw ex;
-        }
-
-        field = new TextFieldItem(api, name, cl, isPublic, isProtected, isPrivate, isInternal, isFinal, isStatic,
-            isTransient, isVolatile, typeInfo, v, tokenizer.pos(),
-            annotations);
-        field.setDeprecated(isDeprecated);
+        TextFieldItem field = new TextFieldItem(api, name, cl, modifiers, typeInfo, value, tokenizer.pos());
+        field.setDeprecated(modifiers.isDeprecated());
         if (isEnum) {
             cl.addEnumConstant(field);
         } else {
@@ -697,53 +478,199 @@
         }
     }
 
-    public static Object parseValue(String type, String val) throws ApiParseException {
-        if (val != null) {
-            if ("boolean".equals(type)) {
-                return "true".equals(val) ? Boolean.TRUE : Boolean.FALSE;
-            } else if ("byte".equals(type)) {
-                return Integer.valueOf(val);
-            } else if ("short".equals(type)) {
-                return Integer.valueOf(val);
-            } else if ("int".equals(type)) {
-                return Integer.valueOf(val);
-            } else if ("long".equals(type)) {
-                return Long.valueOf(val.substring(0, val.length() - 1));
-            } else if ("float".equals(type)) {
-                if ("(1.0f/0.0f)".equals(val) || "(1.0f / 0.0f)".equals(val)) {
-                    return Float.POSITIVE_INFINITY;
-                } else if ("(-1.0f/0.0f)".equals(val) || "(-1.0f / 0.0f)".equals(val)) {
-                    return Float.NEGATIVE_INFINITY;
-                } else if ("(0.0f/0.0f)".equals(val) || "(0.0f / 0.0f)".equals(val)) {
-                    return Float.NaN;
-                } else {
-                    return Float.valueOf(val);
-                }
-            } else if ("double".equals(type)) {
-                if ("(1.0/0.0)".equals(val) || "(1.0 / 0.0)".equals(val)) {
-                    return Double.POSITIVE_INFINITY;
-                } else if ("(-1.0/0.0)".equals(val) || "(-1.0 / 0.0)".equals(val)) {
-                    return Double.NEGATIVE_INFINITY;
-                } else if ("(0.0/0.0)".equals(val) || "(0.0 / 0.0)".equals(val)) {
-                    return Double.NaN;
-                } else {
-                    return Double.valueOf(val);
-                }
-            } else if ("char".equals(type)) {
-                return (char) Integer.parseInt(val);
-            } else if (JAVA_LANG_STRING.equals(type)) {
-                if ("null".equals(val)) {
-                    return null;
-                } else {
-                    return javaUnescapeString(val.substring(1, val.length() - 1));
-                }
+    private static TextModifiers parseModifiers(
+        TextCodebase api,
+        Tokenizer tokenizer,
+        String token,
+        List<String> annotations) throws ApiParseException {
+
+        TextModifiers modifiers = new TextModifiers(api, 0, null);
+
+        processModifiers:
+        while (true) {
+            switch (token) {
+                case "public":
+                    modifiers.setPublic(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "protected":
+                    modifiers.setProtected(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "private":
+                    modifiers.setPrivate(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "internal":
+                    modifiers.setInternal(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "static":
+                    modifiers.setStatic(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "final":
+                    modifiers.setFinal(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "deprecated":
+                    modifiers.setDeprecated(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "abstract":
+                    modifiers.setAbstract(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "transient":
+                    modifiers.setTransient(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "volatile":
+                    modifiers.setVolatile(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "sealed":
+                    modifiers.setSealed(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "default":
+                    modifiers.setDefault(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "synchronized":
+                    modifiers.setSynchronized(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "native":
+                    modifiers.setNative(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "strictfp":
+                    modifiers.setStrictFp(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "infix":
+                    modifiers.setInfix(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "operator":
+                    modifiers.setOperator(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "inline":
+                    modifiers.setInline(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "suspend":
+                    modifiers.setSuspend(true);
+                    token = tokenizer.requireToken();
+                    break;
+                case "vararg":
+                    modifiers.setVarArg(true);
+                    token = tokenizer.requireToken();
+                    break;
+                default:
+                    break processModifiers;
             }
         }
-        if ("null".equals(val)) {
-            return null;
-        } else {
-            return val;
+
+        if (annotations != null) {
+            modifiers.addAnnotations(annotations);
         }
+
+        return modifiers;
+    }
+
+    private static Object parseValue(String type, String val) {
+        if (val != null) {
+            switch (type) {
+                case "boolean":
+                    return "true".equals(val) ? Boolean.TRUE : Boolean.FALSE;
+                case "byte":
+                    return Integer.valueOf(val);
+                case "short":
+                    return Integer.valueOf(val);
+                case "int":
+                    return Integer.valueOf(val);
+                case "long":
+                    return Long.valueOf(val.substring(0, val.length() - 1));
+                case "float":
+                    switch (val) {
+                        case "(1.0f/0.0f)":
+                        case "(1.0f / 0.0f)":
+                            return Float.POSITIVE_INFINITY;
+                        case "(-1.0f/0.0f)":
+                        case "(-1.0f / 0.0f)":
+                            return Float.NEGATIVE_INFINITY;
+                        case "(0.0f/0.0f)":
+                        case "(0.0f / 0.0f)":
+                            return Float.NaN;
+                        default:
+                            return Float.valueOf(val);
+                    }
+                case "double":
+                    switch (val) {
+                        case "(1.0/0.0)":
+                        case "(1.0 / 0.0)":
+                            return Double.POSITIVE_INFINITY;
+                        case "(-1.0/0.0)":
+                        case "(-1.0 / 0.0)":
+                            return Double.NEGATIVE_INFINITY;
+                        case "(0.0/0.0)":
+                        case "(0.0 / 0.0)":
+                            return Double.NaN;
+                        default:
+                            return Double.valueOf(val);
+                    }
+                case "char":
+                    return (char) Integer.parseInt(val);
+                case JAVA_LANG_STRING:
+                case "String":
+                    if ("null".equals(val)) {
+                        return null;
+                    } else {
+                        return javaUnescapeString(val.substring(1, val.length() - 1));
+                    }
+                case "null":
+                    return null;
+                default:
+                    return val;
+            }
+        }
+        return null;
+    }
+
+    private static void parseProperty(TextCodebase api, Tokenizer tokenizer, TextClassItem cl, String token)
+        throws ApiParseException {
+        String type;
+        String name;
+
+        // Metalava: including annotations in file now
+        List<String> annotations = getAnnotations(tokenizer, token);
+        token = tokenizer.getCurrent();
+
+        TextModifiers modifiers = parseModifiers(api, tokenizer, token, null);
+        token = tokenizer.getCurrent();
+        assertIdent(tokenizer, token);
+
+        Pair<String, List<String>> kotlinTypeSuffix = processKotlinTypeSuffix(api, token, annotations);
+        token = kotlinTypeSuffix.getFirst();
+        annotations = kotlinTypeSuffix.getSecond();
+        modifiers.addAnnotations(annotations);
+        type = token;
+        TextTypeItem typeInfo = api.obtainTypeFromString(type);
+
+        token = tokenizer.requireToken();
+        assertIdent(tokenizer, token);
+        name = token;
+        token = tokenizer.requireToken();
+        if (!";".equals(token)) {
+            throw new ApiParseException("expected ; found " + token, tokenizer);
+        }
+
+        TextPropertyItem property = new TextPropertyItem(api, name, cl, modifiers, typeInfo, tokenizer.pos());
+        property.setDeprecated(modifiers.isDeprecated());
+        cl.addProperty(property);
     }
 
     private static TypeParameterList parseTypeParameterList(TextCodebase codebase, Tokenizer tokenizer) throws ApiParseException {
@@ -764,7 +691,7 @@
         if (typeParameterList.isEmpty()) {
             return TypeParameterList.Companion.getNONE();
         } else {
-            return TextTypeParameterList.Companion.create(codebase, typeParameterList);
+            return TextTypeParameterList.Companion.create(codebase, null, typeParameterList);
         }
     }
 
@@ -781,18 +708,11 @@
             // annotations optional-modifiers type-with-use-annotations-and-generics optional-name optional-equals-default-value
 
             // Metalava: including annotations in file now
-            List<String> annotations = null;
-            Pair<String, List<String>> result = getAnnotations(tokenizer, token);
-            if (result != null) {
-                token = result.component1();
-                annotations = result.component2();
-            }
+            List<String> annotations = getAnnotations(tokenizer, token);
+            token = tokenizer.getCurrent();
 
-            boolean vararg = false;
-            if ("vararg".equals(token)) {
-                vararg = true;
-                token = tokenizer.requireToken();
-            }
+            TextModifiers modifiers = parseModifiers(api, tokenizer, token, null);
+            token = tokenizer.getCurrent();
 
             // Token should now represent the type
             String type = token;
@@ -811,6 +731,10 @@
             Pair<String, List<String>> kotlinTypeSuffix = processKotlinTypeSuffix(api, type, annotations);
             String typeString = kotlinTypeSuffix.getFirst();
             annotations = kotlinTypeSuffix.getSecond();
+            modifiers.addAnnotations(annotations);
+            if (typeString.endsWith("...")) {
+                modifiers.setVarArg(true);
+            }
             TextTypeItem typeInfo = api.obtainTypeFromString(typeString);
 
             String name;
@@ -826,14 +750,43 @@
 
             String defaultValue = TextParameterItemKt.NO_DEFAULT_VALUE;
             if ("=".equals(token)) {
-                token = tokenizer.requireToken(false);
-                try {
-                    defaultValue = (String) parseValue(JAVA_LANG_STRING, token);
-                } catch (ApiParseException ex) {
-                    ex.line = tokenizer.getLine();
-                    throw ex;
+                defaultValue = tokenizer.requireToken(true);
+                StringBuilder sb = new StringBuilder(defaultValue);
+                if (defaultValue.equals("{")) {
+                    int balance = 1;
+                    while (balance > 0) {
+                        token = tokenizer.requireToken(false, false);
+                        sb.append(token);
+                        if (token.equals("{")) {
+                            balance++;
+                        } else if (token.equals("}")) {
+                            balance--;
+                            if (balance == 0) {
+                                break;
+                            }
+                        }
+                    }
+                    token = tokenizer.requireToken();
+                } else {
+                    int balance = defaultValue.equals("(") ? 1 : 0;
+                    while (true) {
+                        token = tokenizer.requireToken(true, false);
+                        if (token.endsWith(",") || token.endsWith(")") && balance <= 0) {
+                            if (token.length() > 1) {
+                                sb.append(token, 0, token.length() - 1);
+                                token = Character.toString(token.charAt(token.length() - 1));
+                            }
+                            break;
+                        }
+                        sb.append(token);
+                        if (token.equals("(")) {
+                            balance++;
+                        } else if (token.equals(")")) {
+                            balance--;
+                        }
+                    }
                 }
-                token = tokenizer.requireToken();
+                defaultValue = sb.toString();
             }
 
             if (",".equals(token)) {
@@ -843,12 +796,9 @@
                 throw new ApiParseException("expected , or ), found " + token, tokenizer);
             }
 
-            method.addParameter(new TextParameterItem(api, method, name, publicName, defaultValue, index, typeString,
-                typeInfo,
-                vararg || typeString.endsWith("..."),
-                tokenizer.pos(),
-                annotations));
-            if (typeString.endsWith("...")) {
+            method.addParameter(new TextParameterItem(api, method, name, publicName, defaultValue, index,
+                typeInfo, modifiers, tokenizer.pos()));
+            if (modifiers.isVarArg()) {
                 method.setVarargs(true);
             }
             index++;
@@ -908,7 +858,7 @@
 
     static class Tokenizer {
         final char[] mBuf;
-        String mFilename;
+        final String mFilename;
         int mPos;
         int mLine = 1;
 
@@ -917,7 +867,7 @@
             mBuf = buf;
         }
 
-        public SourcePositionInfo pos() {
+        SourcePositionInfo pos() {
             return new SourcePositionInfo(mFilename, mLine, 0);
         }
 
@@ -955,12 +905,16 @@
             }
         }
 
-        public String requireToken() throws ApiParseException {
+        String requireToken() throws ApiParseException {
             return requireToken(true);
         }
 
-        public String requireToken(boolean parenIsSep) throws ApiParseException {
-            final String token = getToken(parenIsSep);
+        String requireToken(boolean parenIsSep) throws ApiParseException {
+            return requireToken(parenIsSep, true);
+        }
+
+        String requireToken(boolean parenIsSep, boolean eatWhitespace) throws ApiParseException {
+            final String token = getToken(parenIsSep, eatWhitespace);
             if (token != null) {
                 return token;
             } else {
@@ -968,20 +922,32 @@
             }
         }
 
-        public String getToken() throws ApiParseException {
+        String getToken() throws ApiParseException {
             return getToken(true);
         }
 
-        public int offset() {
+        int offset() {
             return mPos;
         }
 
-        public String getStringFromOffset(int offset) {
+        String getStringFromOffset(int offset) {
             return new String(mBuf, offset, mPos - offset);
         }
 
-        public String getToken(boolean parenIsSep) throws ApiParseException {
-            eatWhitespaceAndComments();
+        String getToken(boolean parenIsSep) throws ApiParseException {
+            return getToken(parenIsSep, true);
+        }
+
+        String getCurrent() {
+            return mCurrent;
+        }
+
+        private String mCurrent = null;
+
+        String getToken(boolean parenIsSep, boolean eatWhitespace) throws ApiParseException {
+            if (eatWhitespace) {
+                eatWhitespaceAndComments();
+            }
             if (mPos >= mBuf.length) {
                 return null;
             }
@@ -1010,7 +976,8 @@
                                     mPos++;
                                     break;
                                 case '"':
-                                    return new String(mBuf, start, mPos - start);
+                                    mCurrent = new String(mBuf, start, mPos - start);
+                                    return mCurrent;
                             }
                         case STATE_ESCAPE:
                             state = STATE_BEGIN;
@@ -1018,7 +985,8 @@
                     }
                 }
             } else if (isSeparator(c, parenIsSep)) {
-                return "" + c;
+                mCurrent = Character.toString(c);
+                return mCurrent;
             } else {
                 int genericDepth = 0;
                 do {
@@ -1058,7 +1026,8 @@
                 if (mPos >= mBuf.length) {
                     throw new ApiParseException("Unexpected end of file for \" starting at " + line, mLine);
                 }
-                return new String(mBuf, start, mPos - start);
+                mCurrent = new String(mBuf, start, mPos - start);
+                return mCurrent;
             }
         }
 
@@ -1068,15 +1037,15 @@
         }
     }
 
-    static boolean isSpace(char c) {
+    private static boolean isSpace(char c) {
         return c == ' ' || c == '\t' || c == '\n' || c == '\r';
     }
 
-    static boolean isNewline(char c) {
+    private static boolean isNewline(char c) {
         return c == '\n' || c == '\r';
     }
 
-    static boolean isSeparator(char c, boolean parenIsSep) {
+    private static boolean isSeparator(char c, boolean parenIsSep) {
         if (parenIsSep) {
             if (c == '(' || c == ')') {
                 return true;
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java b/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java
index 23bd38e..0b3a658 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java
@@ -23,8 +23,8 @@
 // Copied from doclava1, but adapted to metalava's code model
 //
 public final class ApiParseException extends Exception {
-    public String file;
-    public int line;
+    private String file;
+    private int line;
 
     ApiParseException(@NotNull String message) {
         super(message);
@@ -58,7 +58,7 @@
             sb.append(file).append(':');
         }
         if (line > 0) {
-            sb.append(Integer.toString(line)).append(':');
+            sb.append(line).append(':');
         }
         if (sb.length() > 0) {
             sb.append(' ');
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt b/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt
index bdace72..b922fe6 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt
@@ -2,7 +2,6 @@
 
 import com.android.tools.metalava.Options
 import com.android.tools.metalava.model.ClassItem
-import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.MemberItem
 import com.android.tools.metalava.model.PackageItem
@@ -17,7 +16,6 @@
  * signals on the member, all containing classes, and all containing packages.
  */
 class ApiPredicate(
-    val codebase: Codebase,
     /**
      * Set if the value of [MemberItem.hasShowAnnotation] should be
      * ignored. That is, this predicate will assume that all encountered members
@@ -26,7 +24,7 @@
      * This is typically useful when generating "current.txt", when no
      * [Options.showAnnotations] have been defined.
      */
-    private val ignoreShown: Boolean = options.showUnannotated,
+    val ignoreShown: Boolean = options.showUnannotated,
 
     /**
      * Set if the value of [MemberItem.removed] should be ignored.
diff --git a/src/main/java/com/android/tools/metalava/doclava1/Errors.java b/src/main/java/com/android/tools/metalava/doclava1/Errors.java
index 4f48174..fc9ddf4 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/Errors.java
+++ b/src/main/java/com/android/tools/metalava/doclava1/Errors.java
@@ -21,23 +21,31 @@
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 
 import static com.android.sdklib.SdkVersionInfo.underlinesToCamelCase;
 import static com.android.tools.metalava.Severity.ERROR;
 import static com.android.tools.metalava.Severity.HIDDEN;
+import static com.android.tools.metalava.Severity.INFO;
 import static com.android.tools.metalava.Severity.INHERIT;
 import static com.android.tools.metalava.Severity.LINT;
 import static com.android.tools.metalava.Severity.WARNING;
 
 // Copied from doclava1 (and a bunch of stuff left alone; preserving to have same error id's)
 public class Errors {
+    // Consider renaming to Issue; "Error" is special in Kotlin, and what does it mean for
+    // an "error" to have severity "warning" ? The severity shouldn't be implied by the name.
     public static class Error {
         public final int code;
+        @Nullable
+        String fieldName;
 
         private Severity level;
         private final Severity defaultLevel;
+        boolean setByUser;
 
         /**
          * The name of this error if known
@@ -51,20 +59,20 @@
          */
         private final Error parent;
 
-        public Error(int code, Severity level) {
+        Error(int code, Severity level) {
             this.code = code;
             this.level = level;
             this.defaultLevel = level;
             this.parent = null;
-            sErrors.add(this);
+            errors.add(this);
         }
 
-        public Error(int code, Error parent) {
+        Error(int code, Error parent) {
             this.code = code;
             this.level = Severity.INHERIT;
             this.defaultLevel = Severity.INHERIT;
             this.parent = parent;
-            sErrors.add(this);
+            errors.add(this);
         }
 
         /**
@@ -98,11 +106,12 @@
          *
          * @param level the level to set
          */
-        public void setLevel(Severity level) {
+        void setLevel(Severity level) {
             if (level == INHERIT) {
                 throw new IllegalArgumentException("Error level may not be set to INHERIT");
             }
             this.level = level;
+            this.setByUser = true;
         }
 
         public String toString() {
@@ -110,7 +119,9 @@
         }
     }
 
-    private static final List<Error> sErrors = new ArrayList<>();
+    private static final List<Error> errors = new ArrayList<>(100);
+    private static final Map<String, Error> nameToError = new HashMap<>(100);
+    private static final Map<Integer, Error> idToError = new HashMap<>(100);
 
     // Errors for API verification
     public static final Error PARSE_ERROR = new Error(1, ERROR);
@@ -119,10 +130,10 @@
     public static final Error ADDED_METHOD = new Error(4, WARNING);
     public static final Error ADDED_FIELD = new Error(5, WARNING);
     public static final Error ADDED_INTERFACE = new Error(6, WARNING);
-    public static final Error REMOVED_PACKAGE = new Error(7, ERROR);
-    public static final Error REMOVED_CLASS = new Error(8, ERROR);
-    public static final Error REMOVED_METHOD = new Error(9, ERROR);
-    public static final Error REMOVED_FIELD = new Error(10, ERROR);
+    public static final Error REMOVED_PACKAGE = new Error(7, WARNING);
+    public static final Error REMOVED_CLASS = new Error(8, WARNING);
+    public static final Error REMOVED_METHOD = new Error(9, WARNING);
+    public static final Error REMOVED_FIELD = new Error(10, WARNING);
     public static final Error REMOVED_INTERFACE = new Error(11, WARNING);
     public static final Error CHANGED_STATIC = new Error(12, WARNING);
     public static final Error ADDED_FINAL = new Error(13, WARNING);
@@ -135,7 +146,7 @@
     public static final Error CHANGED_ABSTRACT = new Error(20, WARNING);
     public static final Error CHANGED_THROWS = new Error(21, WARNING);
     public static final Error CHANGED_NATIVE = new Error(22, HIDDEN);
-    public static final Error CHANGED_CLASS = new Error(23, ERROR);
+    public static final Error CHANGED_CLASS = new Error(23, WARNING);
     public static final Error CHANGED_DEPRECATED = new Error(24, WARNING);
     public static final Error CHANGED_SYNCHRONIZED = new Error(25, WARNING);
     public static final Error ADDED_FINAL_UNINSTANTIABLE = new Error(26, WARNING);
@@ -144,6 +155,7 @@
     public static final Error REMOVED_DEPRECATED_METHOD = new Error(29, REMOVED_METHOD);
     public static final Error REMOVED_DEPRECATED_FIELD = new Error(30, REMOVED_FIELD);
     public static final Error ADDED_ABSTRACT_METHOD = new Error(31, ADDED_METHOD);
+    public static final Error ADDED_REIFIED = new Error(32, WARNING);
 
     // Errors in javadoc generation
     public static final Error UNRESOLVED_LINK = new Error(101, LINT);
@@ -197,8 +209,22 @@
     public static final Error ANNOTATION_EXTRACTION = new Error(146, ERROR);
     public static final Error SUPERFLUOUS_PREFIX = new Error(147, WARNING);
     public static final Error HIDDEN_TYPEDEF_CONSTANT = new Error(148, ERROR);
-    public static final Error MISSING_TYPEDEF_CONSTANT = new Error(149, LINT);
+    public static final Error EXPECTED_PLATFORM_TYPE = new Error(149, HIDDEN);
     public static final Error INTERNAL_ERROR = new Error(150, ERROR);
+    public static final Error RETURNING_UNEXPECTED_CONSTANT = new Error(151, WARNING);
+    public static final Error DEPRECATED_OPTION = new Error(152, WARNING);
+    public static final Error BOTH_PACKAGE_INFO_AND_HTML = new Error(153, WARNING);
+    // The plan is for this to be set as an error once (1) existing code is marked as @deprecated
+    // and (2) the principle is adopted by the API council
+    public static final Error REFERENCES_DEPRECATED = new Error(154, HIDDEN);
+    // Hidden until (1) API council agrees this should be an error, and (2) existing
+    // violations are annotated as @hide
+    public static final Error UNHIDDEN_SYSTEM_API = new Error(155, HIDDEN);
+    public static final Error SHOWING_MEMBER_IN_HIDDEN_CLASS = new Error(156, ERROR);
+    public static final Error INVALID_NULLABILITY_ANNOTATION = new Error(157, ERROR);
+    // Temporarily hidden until source code is updated to be compliant
+    public static final Error REFERENCES_HIDDEN = new Error(158, HIDDEN);
+    public static final Error IGNORING_SYMLINK = new Error(159, INFO);
 
     static {
         // Attempt to initialize error names based on the field names
@@ -208,7 +234,10 @@
                 if (o instanceof Error) {
                     Error error = (Error) o;
                     String fieldName = field.getName();
+                    error.fieldName = fieldName;
                     error.name = underlinesToCamelCase(fieldName.toLowerCase(Locale.US));
+                    nameToError.put(error.name, error);
+                    idToError.put(error.code, error);
                 }
             }
         } catch (Throwable unexpected) {
@@ -216,11 +245,16 @@
         }
     }
 
-    public static boolean setErrorLevel(String id, Severity level) {
+    @Nullable
+    public static Error findErrorById(int id) {
+        return idToError.get(id);
+    }
+
+    public static boolean setErrorLevel(String id, Severity level, boolean setByUser) {
         if (id.contains(",")) { // Handle being passed in multiple comma separated id's
             boolean ok = true;
             for (String individualId : Splitter.on(',').trimResults().split(id)) {
-                ok = setErrorLevel(individualId, level) && ok;
+                ok = setErrorLevel(individualId, level, setByUser) && ok;
             }
             return ok;
         }
@@ -228,19 +262,37 @@
         if (Character.isDigit(id.charAt(0))) {
             code = Integer.parseInt(id);
         }
-        for (Error e : sErrors) {
-            if (e.code == code || e.name.equalsIgnoreCase(id)) {
-                e.setLevel(level);
-                return true;
+
+        Error error = nameToError.get(id);
+        if (error == null) {
+            try {
+                int n = Integer.parseInt(id);
+                error = idToError.get(n);
+            } catch (NumberFormatException ignore) {
             }
         }
+
+        if (error == null) {
+            for (Error e : errors) {
+                if (e.code == code || id.equalsIgnoreCase(e.name)) {
+                    error = e;
+                    break;
+                }
+            }
+        }
+
+        if (error != null) {
+            error.setLevel(level);
+            error.setByUser = setByUser;
+            return true;
+        }
         return false;
     }
 
     // Primary needed by unit tests; ensure that a previous test doesn't influence
     // a later one
     public static void resetLevels() {
-        for (Error error : sErrors) {
+        for (Error error : errors) {
             error.level = error.defaultLevel;
         }
     }
diff --git a/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java b/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java
index a7f098e..efb3cc8 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java
+++ b/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java
@@ -62,7 +62,7 @@
         return this.line - that.line;
     }
 
-    public final String file;
-    public final int line;
-    public final int column;
+    private final String file;
+    private final int line;
+    private final int column;
 }
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 2b66cf6..8e42bcb 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt
+++ b/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt
@@ -16,15 +16,17 @@
 
 package com.android.tools.metalava.doclava1
 
+import com.android.ide.common.repository.GradleVersion
 import com.android.tools.metalava.CodebaseComparator
 import com.android.tools.metalava.ComparisonVisitor
 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.JAVA_LANG_THROWABLE
 import com.android.tools.metalava.model.AnnotationItem
-import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.DefaultCodebase
+import com.android.tools.metalava.model.DefaultModifierList
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.PackageItem
@@ -33,17 +35,19 @@
 import com.android.tools.metalava.model.text.TextBackedAnnotationItem
 import com.android.tools.metalava.model.text.TextClassItem
 import com.android.tools.metalava.model.text.TextMethodItem
+import com.android.tools.metalava.model.text.TextModifiers
 import com.android.tools.metalava.model.text.TextPackageItem
 import com.android.tools.metalava.model.text.TextTypeItem
 import com.android.tools.metalava.model.visitors.ItemVisitor
 import com.android.tools.metalava.model.visitors.TypeVisitor
+import java.io.File
 import java.util.ArrayList
 import java.util.HashMap
 import java.util.function.Predicate
 
 // Copy of ApiInfo in doclava1 (converted to Kotlin + some cleanup to make it work with metalava's data structures.
 // (Converted to Kotlin such that I can inherit behavior via interfaces, in particular Codebase.)
-class TextCodebase : DefaultCodebase() {
+class TextCodebase(location: File) : DefaultCodebase(location) {
     /**
      * Whether types should be interpreted to be in Kotlin format (e.g. ? suffix means nullable,
      * ! suffix means unknown, and absence of a suffix means not nullable.
@@ -56,13 +60,20 @@
     private val mClassToInterface = HashMap<TextClassItem, ArrayList<String>>(10000)
 
     override var description = "Codebase"
+    override var preFiltered: Boolean = true
 
     override fun trustedApi(): Boolean = true
 
+    /**
+     * 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
+
     override fun getPackages(): PackageList {
         val list = ArrayList<PackageItem>(mPackages.values)
         list.sortWith(PackageItem.comparator)
-        return PackageList(list)
+        return PackageList(this, list)
     }
 
     override fun size(): Int {
@@ -73,16 +84,12 @@
         return mAllClasses[className]
     }
 
-    private fun resolveInterfaces() {
-        for (cl in mAllClasses.values) {
+    private fun resolveInterfaces(all: List<TextClassItem>) {
+        for (cl in all) {
             val interfaces = mClassToInterface[cl] ?: continue
-            for (iface in interfaces) {
-                var ci: TextClassItem? = mAllClasses[iface]
-                if (ci == null) {
-                    // Interface not provided by this codebase. Inject a stub.
-                    ci = TextClassItem.createInterfaceStub(this, iface)
-                }
-                cl.addInterface(ci)
+            for (interfaceName in interfaces) {
+                getOrCreateClass(interfaceName, isInterface = true)
+                cl.addInterface(obtainTypeFromString(interfaceName))
             }
         }
     }
@@ -114,8 +121,8 @@
         }
     }
 
-    private fun resolveSuperclasses() {
-        for (cl in mAllClasses.values) {
+    private fun resolveSuperclasses(allClasses: List<TextClassItem>) {
+        for (cl in allClasses) {
             // java.lang.Object has no superclass
             if (cl.isJavaLangObject()) {
                 continue
@@ -128,17 +135,14 @@
                     else -> JAVA_LANG_OBJECT
                 }
             }
-            var superclass: TextClassItem? = mAllClasses[scName]
-            if (superclass == null) {
-                // Superclass not provided by this codebase. Inject a stub.
-                superclass = TextClassItem.createClassStub(this, scName)
-            }
-            cl.setSuperClass(superclass)
+
+            val superclass = getOrCreateClass(scName)
+            cl.setSuperClass(superclass, obtainTypeFromString(scName))
         }
     }
 
-    private fun resolveThrowsClasses() {
-        for (cl in mAllClasses.values) {
+    private fun resolveThrowsClasses(all: List<TextClassItem>) {
+        for (cl in all) {
             for (methodItem in cl.constructors()) {
                 resolveThrowsClasses(methodItem)
             }
@@ -155,11 +159,7 @@
                 }
                 scName = JAVA_LANG_OBJECT
             }
-            var superclass: TextClassItem? = mAllClasses[scName]
-            if (superclass == null) {
-                // Superclass not provided by this codebase. Inject a stub.
-                superclass = TextClassItem.createClassStub(this, scName)
-            }
+            val superclass = getOrCreateClass(scName)
             cl.setSuperClass(superclass)
         }
     }
@@ -173,9 +173,14 @@
                 var exceptionClass: TextClassItem? = mAllClasses[exception]
                 if (exceptionClass == null) {
                     // Exception not provided by this codebase. Inject a stub.
-                    exceptionClass = TextClassItem.createClassStub(
-                        this, exception
-                    )
+                    exceptionClass = getOrCreateClass(exception)
+                    // Set super class to throwable?
+                    if (exception != JAVA_LANG_THROWABLE) {
+                        exceptionClass.setSuperClass(
+                            getOrCreateClass(JAVA_LANG_THROWABLE),
+                            TextTypeItem(this, JAVA_LANG_THROWABLE)
+                        )
+                    }
                 }
                 result.add(exceptionClass)
             }
@@ -183,37 +188,83 @@
         }
     }
 
-    private fun resolveInnerClasses() {
-        mPackages.values
-            .asSequence()
-            .map { it.classList().listIterator() as MutableListIterator<ClassItem> }
-            .forEach {
-                while (it.hasNext()) {
-                    val cl = it.next() as TextClassItem
-                    val name = cl.name
-                    var index = name.lastIndexOf('.')
-                    if (index != -1) {
-                        cl.name = name.substring(index + 1)
-                        val qualifiedName = cl.qualifiedName
-                        index = qualifiedName.lastIndexOf('.')
-                        assert(index != -1) { qualifiedName }
-                        val outerClassName = qualifiedName.substring(0, index)
-                        val outerClass = mAllClasses[outerClassName]!!
-                        cl.containingClass = outerClass
-                        outerClass.addInnerClass(cl)
-
-                        // Should no longer be listed as top level
-                        it.remove()
-                    }
+    private fun resolveInnerClasses(packages: List<TextPackageItem>) {
+        for (pkg in packages) {
+            // make copy: we'll be removing non-top level classes during iteration
+            val classes = ArrayList(pkg.classList())
+            for (cls in classes) {
+                val cl = cls as TextClassItem
+                val name = cl.name
+                var index = name.lastIndexOf('.')
+                if (index != -1) {
+                    cl.name = name.substring(index + 1)
+                    val qualifiedName = cl.qualifiedName
+                    index = qualifiedName.lastIndexOf('.')
+                    assert(index != -1) { qualifiedName }
+                    val outerClassName = qualifiedName.substring(0, index)
+                    val outerClass = getOrCreateClass(outerClassName)
+                    cl.containingClass = outerClass
+                    outerClass.addInnerClass(cl)
                 }
             }
+        }
+
+        for (pkg in packages) {
+            pkg.pruneClassList()
+        }
+    }
+
+    fun getOrCreateClass(name: String, isInterface: Boolean = false): TextClassItem {
+        val erased = TextTypeItem.eraseTypeArguments(name)
+        val cls = mAllClasses[erased]
+        if (cls != null) {
+            return cls
+        }
+        val newClass = if (isInterface) {
+            TextClassItem.createInterfaceStub(this, name)
+        } else {
+            TextClassItem.createClassStub(this, name)
+        }
+        mAllClasses[erased] = newClass
+        newClass.emit = false
+
+        val fullName = newClass.fullName()
+        if (fullName.contains('.')) {
+            // We created a new inner class stub. We need to fully initialize it with outer classes, themselves
+            // possibly stubs
+            val outerName = erased.substring(0, erased.lastIndexOf('.'))
+            val outerClass = getOrCreateClass(outerName, false)
+            newClass.containingClass = outerClass
+            outerClass.addInnerClass(newClass)
+        } else {
+            // Add to package
+            val endIndex = erased.lastIndexOf('.')
+            val pkgPath = if (endIndex != -1) erased.substring(0, endIndex) else ""
+            val pkg = findPackage(pkgPath) as? TextPackageItem ?: run {
+                val newPkg = TextPackageItem(
+                    this,
+                    pkgPath,
+                    TextModifiers(this, DefaultModifierList.PUBLIC),
+                    SourcePositionInfo.UNKNOWN
+                )
+                addPackage(newPkg)
+                newPkg.emit = false
+                newPkg
+            }
+            newClass.setContainingPackage(pkg)
+            pkg.addClass(newClass)
+        }
+
+        return newClass
     }
 
     fun postProcess() {
-        resolveSuperclasses()
-        resolveInterfaces()
-        resolveThrowsClasses()
-        resolveInnerClasses()
+        val classes = mAllClasses.values.toList()
+        val packages = mPackages.values.toList()
+        resolveSuperclasses(classes)
+        resolveInterfaces(classes)
+        resolveThrowsClasses(classes)
+        resolveInnerClasses(packages)
     }
 
     override fun findPackage(pkgName: String): PackageItem? {
@@ -244,10 +295,6 @@
         error(desc ?: "Not supported for a signature-file based codebase")
     }
 
-    override fun filter(filterEmit: Predicate<Item>, filterReference: Predicate<Item>): Codebase {
-        unsupported()
-    }
-
     fun obtainTypeFromString(
         type: String,
         cl: TextClassItem,
diff --git a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
index a3d9647..5a61cb9 100644
--- a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
@@ -25,11 +25,14 @@
 import com.android.tools.lint.annotations.Extractor.ANDROID_LONG_DEF
 import com.android.tools.lint.annotations.Extractor.ANDROID_STRING_DEF
 import com.android.tools.metalava.ANDROIDX_ANNOTATION_PREFIX
+import com.android.tools.metalava.ANDROIDX_NONNULL
+import com.android.tools.metalava.ANDROIDX_NULLABLE
 import com.android.tools.metalava.ANDROID_SUPPORT_ANNOTATION_PREFIX
 import com.android.tools.metalava.JAVA_LANG_PREFIX
 import com.android.tools.metalava.Options
 import com.android.tools.metalava.RECENTLY_NONNULL
 import com.android.tools.metalava.RECENTLY_NULLABLE
+import com.android.tools.metalava.doclava1.ApiPredicate
 import com.android.tools.metalava.options
 import java.util.function.Predicate
 
@@ -52,23 +55,8 @@
     /** Generates source code for this annotation (using fully qualified names) */
     fun toSource(): String
 
-    /** Whether this annotation is significant and should be included in signature files */
-    fun isSignificantInSignatures(): Boolean {
-        return includeInSignatures(qualifiedName() ?: return false)
-    }
-
-    /** Whether this annotation is significant and should be included in stub files etc */
-    fun isSignificantInStubs(): Boolean {
-        return includeInStubs(qualifiedName() ?: return false)
-    }
-
-    /**
-     * Whether this annotation has class retention. Only class retention annotations are
-     * inserted into the stubs, the rest are extracted into the separate external annotations file.
-     */
-    fun hasClassRetention(): Boolean {
-        return hasClassRetention(qualifiedName())
-    }
+    /** The applicable targets for this annotation */
+    fun targets(): Set<AnnotationTarget>
 
     /** Attributes of the annotation (may be empty) */
     fun attributes(): List<AnnotationAttribute>
@@ -126,51 +114,21 @@
         return codebase.findClass(qualifiedName() ?: return null)
     }
 
-    companion object {
-        /** Whether the given annotation name is "significant", e.g. should be included in signature files */
-        fun includeInSignatures(qualifiedName: String?): Boolean {
-            qualifiedName ?: return false
-            if (qualifiedName.startsWith(ANDROID_SUPPORT_ANNOTATION_PREFIX) ||
-                qualifiedName.startsWith(ANDROIDX_ANNOTATION_PREFIX)
-            ) {
-
-                // Don't include typedefs in the stub files.
-                if (qualifiedName.endsWith("IntDef") || qualifiedName.endsWith("StringDef")) {
-                    return false
+    /** Returns the retention of this annotation */
+    val retention: AnnotationRetention
+        get() {
+            val qualifiedName = qualifiedName()
+            if (qualifiedName != null) {
+                val cls = codebase.findClass(qualifiedName)
+                if (cls != null && cls.isAnnotationType()) {
+                    return cls.getRetention()
                 }
-
-                return true
             }
-            return false
+
+            return AnnotationRetention.CLASS
         }
 
-        /** Whether the given annotation name is "significant", e.g. should be included in signature files */
-        fun includeInStubs(qualifiedName: String?): Boolean {
-            qualifiedName ?: return false
-            if (includeInSignatures(qualifiedName)) {
-                return true
-            }
-
-            // These are the significant annotations that should be included in the stubs.
-            // This is a hardcoded list here to minimize risk in the P release branch;
-            // in master the check is more general (we keep only runtime retention annotations
-            // that match the API filter, plus the retention one)
-            return when (qualifiedName) {
-                "android.view.ViewDebug.ExportedProperty",
-                "android.widget.RemoteViews.RemoteView",
-                "android.view.ViewDebug.CapturedViewProperty",
-
-                "java.lang.FunctionalInterface",
-                "java.lang.SafeVarargs",
-                "java.lang.annotation.Documented",
-                "java.lang.annotation.Inherited",
-                "java.lang.annotation.Repeatable",
-                "java.lang.annotation.Retention",
-                "java.lang.annotation.Target" -> true
-                else -> false
-            }
-        }
-
+    companion object {
         /** The simple name of an annotation, which is the annotation name (not qualified name) prefixed by @ */
         fun simpleName(item: AnnotationItem): String {
             val qualifiedName = item.qualifiedName() ?: return ""
@@ -185,7 +143,7 @@
             qualifiedName ?: return null
 
             when (qualifiedName) {
-            // Resource annotations
+                // Resource annotations
                 "android.support.annotation.AnimRes",
                 "android.annotation.AnimRes" -> return "androidx.annotation.AnimRes"
                 "android.support.annotation.AnimatorRes",
@@ -233,7 +191,7 @@
                 "android.support.annotation.XmlRes",
                 "android.annotation.XmlRes" -> return "androidx.annotation.XmlRes"
 
-            // Threading
+                // Threading
                 "android.support.annotation.AnyThread",
                 "android.annotation.AnyThread" -> return "androidx.annotation.AnyThread"
                 "android.support.annotation.BinderThread",
@@ -245,7 +203,7 @@
                 "android.support.annotation.WorkerThread",
                 "android.annotation.WorkerThread" -> return "androidx.annotation.WorkerThread"
 
-            // Colors
+                // Colors
                 "android.support.annotation.ColorInt",
                 "android.annotation.ColorInt" -> return "androidx.annotation.ColorInt"
                 "android.support.annotation.ColorLong",
@@ -253,7 +211,7 @@
                 "android.support.annotation.HalfFloat",
                 "android.annotation.HalfFloat" -> return "androidx.annotation.HalfFloat"
 
-            // Ranges and sizes
+                // Ranges and sizes
                 "android.support.annotation.FloatRange",
                 "android.annotation.FloatRange" -> return "androidx.annotation.FloatRange"
                 "android.support.annotation.IntRange",
@@ -265,7 +223,7 @@
                 "android.support.annotation.Dimension",
                 "android.annotation.Dimension" -> return "androidx.annotation.Dimension"
 
-            // Null
+                // Null
                 "android.support.annotation.NonNull",
                 "android.annotation.NonNull" -> return "androidx.annotation.NonNull"
                 "android.support.annotation.Nullable",
@@ -275,7 +233,7 @@
                 "org.jetbrains.annotations.NotNull" -> return "androidx.annotation.NonNull"
                 "org.jetbrains.annotations.Nullable" -> return "androidx.annotation.Nullable"
 
-            // Typedefs
+                // Typedefs
                 "android.support.annotation.IntDef",
                 "android.annotation.IntDef" -> return "androidx.annotation.IntDef"
                 "android.support.annotation.StringDef",
@@ -283,7 +241,7 @@
                 "android.support.annotation.LongDef",
                 "android.annotation.LongDef" -> return "androidx.annotation.LongDef"
 
-            // Misc
+                // Misc
                 "android.support.annotation.CallSuper",
                 "android.annotation.CallSuper" -> return "androidx.annotation.CallSuper"
                 "android.support.annotation.CheckResult",
@@ -293,7 +251,7 @@
                 "android.annotation.RequiresPermission.Read" -> return "androidx.annotation.RequiresPermission.Read"
                 "android.annotation.RequiresPermission.Write" -> return "androidx.annotation.RequiresPermission.Write"
 
-            // These aren't support annotations, but could/should be:
+                // These aren't support annotations, but could/should be:
                 "android.annotation.CurrentTimeMillisLong",
                 "android.annotation.DurationMillisLong",
                 "android.annotation.ElapsedRealtimeLong",
@@ -324,36 +282,36 @@
                     }
                 }
 
-            // Included for analysis, but should not be exported:
+                // Included for analysis, but should not be exported:
                 "android.annotation.BroadcastBehavior",
                 "android.annotation.SdkConstant",
                 "android.annotation.RequiresFeature",
                 "android.annotation.SystemService" -> return qualifiedName
 
-            // Should not be mapped to a different package name:
+                // Should not be mapped to a different package name:
                 "android.annotation.TargetApi",
                 "android.annotation.SuppressLint" -> return qualifiedName
 
-            // We only change recently/newly nullable annotation if the codebase supports it
-                RECENTLY_NULLABLE -> return if (codebase.supportsStagedNullability) qualifiedName else "androidx.annotation.Nullable"
-                RECENTLY_NONNULL -> return if (codebase.supportsStagedNullability) qualifiedName else "androidx.annotation.NonNull"
+                // We only change recently/newly nullable annotation if the codebase supports it
+                RECENTLY_NULLABLE -> return if (codebase.supportsStagedNullability) qualifiedName else ANDROIDX_NULLABLE
+                RECENTLY_NONNULL -> return if (codebase.supportsStagedNullability) qualifiedName else ANDROIDX_NONNULL
 
                 else -> {
                     // Some new annotations added to the platform: assume they are support annotations?
                     return when {
-                    // Special Kotlin annotations recognized by the compiler: map to supported package name
+                        // Special Kotlin annotations recognized by the compiler: map to supported package name
                         qualifiedName.endsWith(".ParameterName") || qualifiedName.endsWith(".DefaultValue") ->
                             "kotlin.annotations.jvm.internal${qualifiedName.substring(qualifiedName.lastIndexOf('.'))}"
 
-                    // Other third party nullness annotations?
+                        // Other third party nullness annotations?
                         isNullableAnnotation(qualifiedName) -> "androidx.annotation.Nullable"
                         isNonNullAnnotation(qualifiedName) -> "androidx.annotation.NonNull"
 
-                    // Support library annotations are all included, as is the built-in stuff like @Retention
+                        // Support library annotations are all included, as is the built-in stuff like @Retention
                         qualifiedName.startsWith(ANDROIDX_ANNOTATION_PREFIX) -> return qualifiedName
                         qualifiedName.startsWith(JAVA_LANG_PREFIX) -> return qualifiedName
 
-                    // Unknown Android platform annotations
+                        // Unknown Android platform annotations
                         qualifiedName.startsWith("android.annotation.") -> {
                             // Remove, unless specifically included in --showAnnotations
                             return if (options.showAnnotations.contains(qualifiedName)) {
@@ -391,6 +349,101 @@
             }
         }
 
+        /** The applicable targets for this annotation */
+        fun computeTargets(annotation: AnnotationItem, codebase: Codebase): Set<AnnotationTarget> {
+            val qualifiedName = annotation.qualifiedName() ?: return NO_ANNOTATION_TARGETS
+            when (qualifiedName) {
+
+                // The typedef annotations are special: they should not be in the signature
+                // files, but we want to include them in the external annotations file such that tools
+                // can enforce them.
+                "android.support.annotation.IntDef",
+                "android.annotation.IntDef",
+                "androidx.annotation.IntDef",
+                "android.support.annotation.StringDef",
+                "android.annotation.StringDef",
+                "androidx.annotation.StringDef",
+                "android.support.annotation.LongDef",
+                "android.annotation.LongDef",
+                "androidx.annotation.LongDef" -> return ANNOTATION_EXTERNAL_ONLY
+
+                // Skip known annotations that we (a) never want in external annotations and (b) we are
+                // specially overwriting anyway in the stubs (and which are (c) not API significant)
+                "java.lang.annotation.Native",
+                "java.lang.SuppressWarnings",
+                "java.lang.Override" -> return NO_ANNOTATION_TARGETS
+
+                "java.lang.Deprecated", // tracked separately as a pseudo-modifier
+
+                // Below this when-statement we perform the correct lookup: check API predicate, and check
+                // that retention is class or runtime, but we've hardcoded the answers here
+                // for some common annotations.
+
+                "android.view.ViewDebug.ExportedProperty",
+                "android.widget.RemoteViews.RemoteView",
+                "android.view.ViewDebug.CapturedViewProperty",
+
+                "java.lang.FunctionalInterface",
+                "java.lang.SafeVarargs",
+                "java.lang.annotation.Documented",
+                "java.lang.annotation.Inherited",
+                "java.lang.annotation.Repeatable",
+                "java.lang.annotation.Retention",
+                "java.lang.annotation.Target" -> return ANNOTATION_IN_ALL_STUBS
+            }
+
+            if (qualifiedName.startsWith("android.annotation.")) {
+                // internal annotations not mapped to androidx: things like @SystemApi. Skip from
+                // stubs, external annotations, signature files, etc.
+                return NO_ANNOTATION_TARGETS
+            }
+
+            // @RecentlyNullable and @RecentlyNonNull are specially recognized annotations by the Kotlin
+            // compiler: they always go in the stubs.
+            if (qualifiedName == "androidx.annotation.RecentlyNullable" ||
+                qualifiedName == "androidx.annotation.RecentlyNonNull"
+            ) {
+                return ANNOTATION_IN_SDK_STUBS
+            }
+
+            // Determine the retention of the annotation: source retention annotations go
+            // in the external annotations file, class and runtime annotations go in
+            // the stubs files (except for the androidx annotations which are not included
+            // in the SDK and therefore cannot be referenced from it due to apt's unfortunate
+            // habit of loading all annotation classes it encounters.)
+
+            if (qualifiedName.startsWith("androidx.annotation.")) {
+                if (options.includeSourceRetentionAnnotations) {
+                    return ANNOTATION_IN_ALL_STUBS
+                }
+
+                if (qualifiedName == ANDROIDX_NULLABLE || qualifiedName == ANDROIDX_NONNULL) {
+                    // Right now, nullness annotations (other than @RecentlyNullable and @RecentlyNonNull)
+                    // have to go in external annotations since they aren't in the class path for
+                    // annotation processors. However, we do want them showing up in the documentation using
+                    // their real annotation names.
+                    return ANNOTATION_IN_DOC_STUBS_AND_EXTERNAL
+                }
+
+                return ANNOTATION_EXTERNAL
+            }
+
+            // See if the annotation is pointing to an annotation class that is part of the API; if not, skip it.
+            val cls = codebase.findClass(qualifiedName) ?: return NO_ANNOTATION_TARGETS
+            if (!ApiPredicate().test(cls)) {
+                return NO_ANNOTATION_TARGETS
+            }
+
+            if (cls.isAnnotationType()) {
+                val retention = cls.getRetention()
+                if (retention == AnnotationRetention.RUNTIME || retention == AnnotationRetention.CLASS) {
+                    return ANNOTATION_IN_SDK_STUBS
+                }
+            }
+
+            return ANNOTATION_EXTERNAL
+        }
+
         /**
          * Given a "full" annotation name, shortens it by removing redundant package names.
          * This is intended to be used by the [Options.omitCommonPackages] flag
@@ -401,6 +454,7 @@
          */
         fun shortenAnnotation(source: String): String {
             return when {
+                source == "@java.lang.Deprecated" -> "@Deprecated"
                 source.startsWith("android.annotation.", 1) -> {
                     "@" + source.substring("@android.annotation.".length)
                 }
@@ -420,7 +474,8 @@
          */
         fun unshortenAnnotation(source: String): String {
             return when {
-            // These 3 annotations are in the android.annotation. package, not android.support.annotation
+                source == "@Deprecated" -> "@java.lang.Deprecated"
+                // These 3 annotations are in the android.annotation. package, not android.support.annotation
                 source.startsWith("@SystemService") ||
                     source.startsWith("@TargetApi") ||
                     source.startsWith("@SuppressLint") ->
@@ -431,32 +486,54 @@
             }
         }
 
-        fun hasClassRetention(qualifiedName: String?): Boolean {
-            // For now, we treat everything except the recently nullable annotations
-            // as source retention; this works around the bug that we don't want to
-            // reference (from the .class files) annotations that aren't part of the SDK
-            // except for those that we include with the stubs
+        /**
+         * If the given element has an *implicit* nullness, return it. This returns
+         * true for implicitly nullable elements, such as the parameter to the equals
+         * method, false for implicitly non null elements (such as annotation type
+         * members), and null if there is no implicit nullness.
+         */
+        fun getImplicitNullness(item: Item): Boolean? {
+            var nullable: Boolean? = null
 
-            qualifiedName ?: return false
-
-            return when (qualifiedName) {
-                // Hardcoded list for now; in master, this is generalized
-                "android.view.ViewDebug.ExportedProperty",
-                "android.widget.RemoteViews.RemoteView",
-                "android.view.ViewDebug.CapturedViewProperty",
-
-                "androidx.annotation.RecentlyNullable",
-                "androidx.annotation.RecentlyNonNull" -> return true
-
-                else -> qualifiedName.startsWith("java.") ||
-                    qualifiedName.startsWith("javax.") ||
-                    qualifiedName.startsWith("kotlin.") ||
-                    qualifiedName.startsWith("kotlinx.")
+            // Constant field not initialized to null?
+            if (item is FieldItem &&
+                (item.isEnumConstant() || item.modifiers.isFinal() && item.initialValue(false) != null)
+            ) {
+                // Assigned to constant: not nullable
+                nullable = false
             }
+
+            // Annotation type members cannot be null
+            if (item is MemberItem && item.containingClass().isAnnotationType()) {
+                nullable = false
+            }
+
+            // Equals and toString have known nullness
+            if (item is MethodItem && item.name() == "toString" && item.parameters().isEmpty()) {
+                nullable = false
+            } else if (item is ParameterItem && item.containingMethod().name() == "equals" &&
+                item.containingMethod().parameters().size == 1
+            ) {
+                nullable = true
+            }
+
+            return nullable
         }
     }
 }
 
+/** Default implementation of an annotation item */
+abstract class DefaultAnnotationItem(override val codebase: Codebase) : AnnotationItem {
+    private var targets: Set<AnnotationTarget>? = null
+
+    override fun targets(): Set<AnnotationTarget> {
+        if (targets == null) {
+            targets = AnnotationItem.computeTargets(this, codebase)
+        }
+        return targets!!
+    }
+}
+
 /** An attribute of an annotation, such as "value" */
 interface AnnotationAttribute {
     /** The name of the annotation */
diff --git a/src/main/java/com/android/tools/metalava/model/AnnotationRetention.kt b/src/main/java/com/android/tools/metalava/model/AnnotationRetention.kt
new file mode 100644
index 0000000..3de1336
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/AnnotationRetention.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.model
+
+/** Retention for an [AnnotationItem] */
+enum class AnnotationRetention {
+    SOURCE,
+    CLASS,
+    RUNTIME
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt b/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt
new file mode 100644
index 0000000..82671f0
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.model
+
+/** Various places where a given annotation can be written */
+enum class AnnotationTarget {
+    /** Write the annotation into the signature file */
+    SIGNATURE_FILE,
+    /** Write the annotation into stub source files */
+    SDK_STUBS_FILE,
+    /** Write the annotation into doc stub source files */
+    DOC_STUBS_FILE,
+    /** Write the annotation into external annotation files */
+    EXTERNAL_ANNOTATIONS_FILE,
+    /** Don't write the annotation anywhere */
+    NONE;
+
+    /** Is this target a stubs file? */
+    fun isStubsFile(): Boolean {
+        return this == SDK_STUBS_FILE || this == DOC_STUBS_FILE
+    }
+}
+
+/** Don't write this annotation anywhere; it is not API significant. */
+val NO_ANNOTATION_TARGETS = setOf(AnnotationTarget.NONE)
+
+/**
+ * Annotation is API significant: write it into the signature file and stub source code.
+ * This would normally be the case for all (API significant) class-retention annotations,
+ * but unfortunately due to apt (the annotation processor) attempting to load all
+ * classes for annotation references that it comes across, that means we cannot
+ * compile the stubs with the androidx annotations and leave those in the SDK; apt
+ * would also need to have androidx on the classpath. So instead we put all these
+ * annotations (except for @RecentlyNullable and @RecentlyNonNull, which are not part
+ * of androidx, and which we include as package private in the SDK, something we cannot
+ * do with the others since their class definitions conflict with the real androidx library
+ * when present) into the external annotations file.
+ *
+ * Also includes documentation stubs.
+ */
+val ANNOTATION_IN_ALL_STUBS = setOf(
+    AnnotationTarget.SIGNATURE_FILE,
+    AnnotationTarget.SDK_STUBS_FILE,
+    AnnotationTarget.DOC_STUBS_FILE
+)
+
+/**
+ * Like [ANNOTATION_IN_ALL_STUBS], but limited to SDK stubs, not included in documentation stubs.
+ * Example: RecentlyNonNull.
+ */
+val ANNOTATION_IN_SDK_STUBS = setOf(AnnotationTarget.SIGNATURE_FILE, AnnotationTarget.SDK_STUBS_FILE)
+
+/**
+ * Like [ANNOTATION_IN_ALL_STUBS], but limited to documentation stubs, not included in SDK stubs.
+ * These are also placed in external annotations since they don't appear in the SDK.
+ *
+ * Example: NonNull.
+ */
+val ANNOTATION_IN_DOC_STUBS_AND_EXTERNAL = setOf(
+    AnnotationTarget.SIGNATURE_FILE,
+    AnnotationTarget.DOC_STUBS_FILE,
+    AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE
+)
+
+/** Annotation is API significant: write it into the signature file and into external annotations file. */
+val ANNOTATION_EXTERNAL = setOf(AnnotationTarget.SIGNATURE_FILE, AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE)
+
+/** Write it only into the external annotations file, not the signature file */
+val ANNOTATION_EXTERNAL_ONLY = setOf(AnnotationTarget.SIGNATURE_FILE, AnnotationTarget.EXTERNAL_ANNOTATIONS_FILE)
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 de95759..daf665c 100644
--- a/src/main/java/com/android/tools/metalava/model/ClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/ClassItem.kt
@@ -16,6 +16,7 @@
 
 package com.android.tools.metalava.model
 
+import com.android.SdkConstants
 import com.android.tools.metalava.ApiAnalyzer
 import com.android.tools.metalava.JAVA_LANG_ANNOTATION
 import com.android.tools.metalava.JAVA_LANG_ENUM
@@ -24,7 +25,6 @@
 import com.android.tools.metalava.model.visitors.ApiVisitor
 import com.android.tools.metalava.model.visitors.ItemVisitor
 import com.android.tools.metalava.model.visitors.TypeVisitor
-import com.android.tools.metalava.options
 import com.google.common.base.Splitter
 import java.util.ArrayList
 import java.util.LinkedHashSet
@@ -161,6 +161,9 @@
     /** The non-constructor methods in this class */
     fun methods(): List<MethodItem>
 
+    /** The properties in this class */
+    fun properties(): List<PropertyItem>
+
     /** The fields in this class */
     fun fields(): List<FieldItem>
 
@@ -178,7 +181,7 @@
     /** Whether this class is an enum */
     fun isEnum(): Boolean
 
-    /** Whether this class is an interface */
+    /** Whether this class is a regular class (not an interface, not an enum, etc) */
     fun isClass(): Boolean = !isInterface() && !isAnnotationType() && !isEnum()
 
     /** The containing class, for inner classes */
@@ -243,6 +246,10 @@
             method.accept(visitor)
         }
 
+        for (property in properties()) {
+            property.accept(visitor)
+        }
+
         if (isEnum()) {
             // In enums, visit the enum constants first, then the fields
             for (field in fields()) {
@@ -333,6 +340,21 @@
     }
 
     companion object {
+        /** Looks up the retention policy for the given class */
+        fun findRetention(cls: ClassItem): AnnotationRetention {
+            val modifiers = cls.modifiers
+            val annotation = modifiers.findAnnotation("java.lang.annotation.Retention")
+                ?: modifiers.findAnnotation("kotlin.annotation.Retention")
+            val value = annotation?.findAttribute(SdkConstants.ATTR_VALUE)
+            val source = value?.value?.toSource()
+            return when {
+                source == null -> AnnotationRetention.CLASS // default
+                source.contains("RUNTIME") -> AnnotationRetention.RUNTIME
+                source.contains("SOURCE") -> AnnotationRetention.SOURCE
+                else -> AnnotationRetention.CLASS // default
+            }
+        }
+
         // Same as doclava1 (modulo the new handling when class names match)
         val comparator: Comparator<in ClassItem> = Comparator { o1, o2 ->
             val delta = o1.fullName().compareTo(o2.fullName())
@@ -475,7 +497,7 @@
             if (index != -1) {
                 parameterString = parameterString.substring(0, index)
             }
-            val parameter = parameters[i].type().toErasedTypeString()
+            val parameter = parameters[i].type().toErasedTypeString(method)
             if (parameter != parameterString) {
                 return false
             }
@@ -487,6 +509,9 @@
     /** Returns the corresponding compilation unit, if any */
     fun getCompilationUnit(): CompilationUnit? = null
 
+    /** If this class is an annotation type, returns the retention of this class */
+    fun getRetention(): AnnotationRetention
+
     /**
      * Return superclass matching the given predicate. When a superclass doesn't
      * match, we'll keep crawling up the tree until we find someone who matches.
@@ -551,9 +576,9 @@
      * Return fields matching the given predicate. Also clones fields from
      * ancestors that would match had they been defined in this class.
      */
-    fun filteredFields(predicate: Predicate<Item>): List<FieldItem> {
+    fun filteredFields(predicate: Predicate<Item>, showUnannotated: Boolean): List<FieldItem> {
         val fields = LinkedHashSet<FieldItem>()
-        if (options.showUnannotated) {
+        if (showUnannotated) {
             for (clazz in allInterfaces()) {
                 if (!clazz.isInterface()) {
                     continue
@@ -691,7 +716,7 @@
      * If [reverse] is true, compute the reverse map: keys are the variables in
      * the target and the values are the variables in the source.
      */
-    fun mapTypeVariables(target: ClassItem, reverse: Boolean = false): Map<String, String> = codebase.unsupported()
+    fun mapTypeVariables(target: ClassItem): Map<String, String> = codebase.unsupported()
 
     /**
      * Creates a constructor in this class
@@ -712,6 +737,7 @@
     private val methods: Sequence<MethodItem>
     private val fields: Sequence<FieldItem>
     private val enums: Sequence<FieldItem>
+    private val properties: Sequence<PropertyItem>
 
     init {
         val filterEmit = visitor.filterEmit
@@ -726,7 +752,7 @@
 
         val fieldSequence =
             if (visitor.inlineInheritedFields) {
-                cls.filteredFields(filterEmit).asSequence()
+                cls.filteredFields(filterEmit, visitor.showUnannotated).asSequence()
             } else {
                 cls.fields().asSequence()
                     .filter { filterEmit.test(it) }
@@ -744,6 +770,14 @@
             enums = emptySequence()
         }
 
+        properties = if (cls.properties().isEmpty()) {
+            emptySequence()
+        } else {
+            cls.properties().asSequence()
+                .filter { filterEmit.test(it) }
+                .sortedWith(PropertyItem.comparator)
+        }
+
         innerClasses = cls.innerClasses()
             .asSequence()
             .sortedWith(ClassItem.classNameSorter())
@@ -762,7 +796,7 @@
 
     /** Does the body of this class (everything other than the inner classes) emit anything? */
     private fun emitClass(): Boolean {
-        val classEmpty = (constructors.none() && methods.none() && enums.none() && fields.none())
+        val classEmpty = (constructors.none() && methods.none() && enums.none() && fields.none() && properties.none())
         return if (visitor.filterEmit.test(cls)) {
             true
         } else if (!classEmpty) {
@@ -821,6 +855,9 @@
                 method.accept(visitor)
             }
 
+            for (property in properties) {
+                property.accept(visitor)
+            }
             for (enumConstant in enums) {
                 enumConstant.accept(visitor)
             }
diff --git a/src/main/java/com/android/tools/metalava/model/Codebase.kt b/src/main/java/com/android/tools/metalava/model/Codebase.kt
index 3c86e61..24dbb2a 100644
--- a/src/main/java/com/android/tools/metalava/model/Codebase.kt
+++ b/src/main/java/com/android/tools/metalava/model/Codebase.kt
@@ -22,15 +22,21 @@
 import com.android.tools.metalava.CodebaseComparator
 import com.android.tools.metalava.ComparisonVisitor
 import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.psi.CodePrinter
 import com.android.tools.metalava.model.text.TextBackedAnnotationItem
 import com.android.tools.metalava.model.visitors.ItemVisitor
 import com.android.tools.metalava.model.visitors.TypeVisitor
 import com.android.tools.metalava.reporter
-import com.android.utils.XmlUtils
 import com.android.utils.XmlUtils.getFirstSubTagByName
 import com.android.utils.XmlUtils.getNextTagByName
 import com.intellij.psi.PsiFile
 import org.intellij.lang.annotations.Language
+import org.objectweb.asm.Type
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.FieldInsnNode
+import org.objectweb.asm.tree.FieldNode
+import org.objectweb.asm.tree.MethodInsnNode
+import org.objectweb.asm.tree.MethodNode
 import java.io.File
 import java.util.function.Predicate
 import kotlin.text.Charsets.UTF_8
@@ -44,6 +50,12 @@
     /** Description of what this codebase is (useful during debugging) */
     var description: String
 
+    /**
+     * The location of the API. Could point to a signature file, or a directory
+     * root for source files, or a jar file, etc.
+     */
+    var location: File
+
     /** The API level of this codebase, or -1 if not known */
     var apiLevel: Int
 
@@ -131,14 +143,14 @@
         getPackages().packages.forEach { pkg -> pkg.allClasses().forEach { cls -> cls.tag = false } }
     }
 
-    /**
-     * Creates a filtered version of this codebase
-     */
-    fun filter(filterEmit: Predicate<Item>, filterReference: Predicate<Item>): Codebase
-
     /** Reports that the given operation is unsupported for this codebase type */
     fun unsupported(desc: String? = null): Nothing
 
+    /** Discards this model */
+    fun dispose() {
+        description += " [disposed]"
+    }
+
     /** Whether this codebase supports staged nullability (RecentlyNullable etc) */
     var supportsStagedNullability: Boolean
 
@@ -149,15 +161,111 @@
      * when the codebase is not loaded from source, such as from .jar files or
      * from signature files) */
     var units: List<PsiFile>
+
+    /**
+     * Printer which can convert PSI, UAST and constants into source code,
+     * with ability to filter out elements that are not part of a codebase etc
+     */
+    val printer: CodePrinter
+
+    /** If true, this codebase has already been filtered */
+    val preFiltered: Boolean
+
+    /** Finds the given class by JVM owner */
+    fun findClassByOwner(owner: String, apiFilter: Predicate<Item>): ClassItem? {
+        val className = owner.replace('/', '.').replace('$', '.')
+        val cls = findClass(className)
+        return if (cls != null && apiFilter.test(cls)) {
+            cls
+        } else {
+            null
+        }
+    }
+
+    fun findClass(node: ClassNode, apiFilter: Predicate<Item>): ClassItem? {
+        return findClassByOwner(node.name, apiFilter)
+    }
+
+    fun findMethod(node: MethodInsnNode, apiFilter: Predicate<Item>): MethodItem? {
+        val cls = findClassByOwner(node.owner, apiFilter) ?: return null
+        val types = Type.getArgumentTypes(node.desc)
+        val parameters = if (types.isNotEmpty()) {
+            val sb = StringBuilder()
+            for (type in types) {
+                if (!sb.isEmpty()) {
+                    sb.append(", ")
+                }
+                sb.append(type.className.replace('/', '.').replace('$', '.'))
+            }
+            sb.toString()
+        } else {
+            ""
+        }
+        val methodName = if (node.name == "<init>") cls.simpleName() else node.name
+        val method = cls.findMethod(methodName, parameters)
+        return if (method != null && apiFilter.test(method)) {
+            method
+        } else {
+            null
+        }
+    }
+
+    fun findMethod(classNode: ClassNode, node: MethodNode, apiFilter: Predicate<Item>): MethodItem? {
+        val cls = findClass(classNode, apiFilter) ?: return null
+        val types = Type.getArgumentTypes(node.desc)
+        val parameters = if (types.isNotEmpty()) {
+            val sb = StringBuilder()
+            for (type in types) {
+                if (!sb.isEmpty()) {
+                    sb.append(", ")
+                }
+                sb.append(type.className.replace('/', '.').replace('$', '.'))
+            }
+            sb.toString()
+        } else {
+            ""
+        }
+        val methodName = if (node.name == "<init>") cls.simpleName() else node.name
+        val method = cls.findMethod(methodName, parameters)
+        return if (method != null && apiFilter.test(method)) {
+            method
+        } else {
+            null
+        }
+    }
+
+    fun findField(classNode: ClassNode, node: FieldNode, apiFilter: Predicate<Item>): FieldItem? {
+        val cls = findClass(classNode, apiFilter) ?: return null
+        val field = cls.findField(node.name)
+        return if (field != null && apiFilter.test(field)) {
+            field
+        } else {
+            null
+        }
+    }
+
+    fun findField(node: FieldInsnNode, apiFilter: Predicate<Item>): FieldItem? {
+        val cls = findClassByOwner(node.owner, apiFilter) ?: return null
+        val field = cls.findField(node.name)
+        return if (field != null && apiFilter.test(field)) {
+            field
+        } else {
+            null
+        }
+    }
 }
 
-abstract class DefaultCodebase : Codebase {
+abstract class DefaultCodebase(override var location: File) : Codebase {
     override var manifest: File? = null
     private var permissions: Map<String, String>? = null
     override var original: Codebase? = null
     override var supportsStagedNullability: Boolean = true
     override var units: List<PsiFile> = emptyList()
     override var apiLevel: Int = -1
+    @Suppress("LeakingThis")
+    override val printer = CodePrinter(this)
+    @Suppress("LeakingThis")
+    override var preFiltered: Boolean = original != null
 
     override fun getPermissionLevel(name: String): String? {
         if (permissions == null) {
@@ -166,7 +274,7 @@
             }
             try {
                 val map = HashMap<String, String>(600)
-                val doc = XmlUtils.parseDocument(manifest?.readText(UTF_8), true)
+                val doc = parseDocument(manifest?.readText(UTF_8) ?: "", true)
                 var current = getFirstSubTagByName(doc.documentElement, TAG_PERMISSION)
                 while (current != null) {
                     val permissionName = current.getAttributeNS(ANDROID_URI, ATTR_NAME)
diff --git a/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt b/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt
new file mode 100644
index 0000000..65ebd48
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/DefaultModifierList.kt
@@ -0,0 +1,315 @@
+/*
+ * 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.model
+
+import com.android.tools.metalava.model.psi.PsiModifierItem
+
+open class DefaultModifierList(
+    override val codebase: Codebase,
+    protected var flags: Int = 0,
+    protected open var annotations: MutableList<AnnotationItem>? = null
+) : MutableModifierList {
+    private lateinit var owner: Item
+
+    private operator fun set(mask: Int, set: Boolean) {
+        flags = if (set) {
+            flags or mask
+        } else {
+            flags and mask.inv()
+        }
+    }
+
+    private fun isSet(mask: Int): Boolean {
+        return flags and mask != 0
+    }
+
+    override fun annotations(): List<AnnotationItem> {
+        return annotations ?: emptyList()
+    }
+
+    override fun owner(): Item {
+        return owner
+    }
+
+    fun setOwner(owner: Item) {
+        this.owner = owner
+    }
+
+    override fun isPublic(): Boolean {
+        return isSet(PUBLIC)
+    }
+
+    override fun isProtected(): Boolean {
+        return isSet(PROTECTED)
+    }
+
+    override fun isPrivate(): Boolean {
+        return isSet(PRIVATE)
+    }
+
+    override fun isStatic(): Boolean {
+        return isSet(STATIC)
+    }
+
+    override fun isAbstract(): Boolean {
+        return isSet(ABSTRACT)
+    }
+
+    override fun isFinal(): Boolean {
+        return isSet(FINAL)
+    }
+
+    override fun isNative(): Boolean {
+        return isSet(NATIVE)
+    }
+
+    override fun isSynchronized(): Boolean {
+        return isSet(SYNCHRONIZED)
+    }
+
+    override fun isStrictFp(): Boolean {
+        return isSet(STRICT_FP)
+    }
+
+    override fun isTransient(): Boolean {
+        return isSet(TRANSIENT)
+    }
+
+    override fun isVolatile(): Boolean {
+        return isSet(VOLATILE)
+    }
+
+    override fun isDefault(): Boolean {
+        return isSet(DEFAULT)
+    }
+
+    fun isDeprecated(): Boolean {
+        return isSet(DEPRECATED)
+    }
+
+    override fun isVarArg(): Boolean {
+        return isSet(VARARG)
+    }
+
+    override fun isSealed(): Boolean {
+        return isSet(SEALED)
+    }
+
+    override fun isInternal(): Boolean {
+        return isSet(INTERNAL)
+    }
+
+    override fun isInfix(): Boolean {
+        return isSet(INFIX)
+    }
+
+    override fun isSuspend(): Boolean {
+        return isSet(SUSPEND)
+    }
+
+    override fun isOperator(): Boolean {
+        return isSet(OPERATOR)
+    }
+
+    override fun isInline(): Boolean {
+        return isSet(INLINE)
+    }
+
+    override fun setPublic(public: Boolean) {
+        set(PUBLIC, public)
+    }
+
+    override fun setProtected(protected: Boolean) {
+        set(PROTECTED, protected)
+    }
+
+    override fun setPrivate(private: Boolean) {
+        set(PRIVATE, private)
+    }
+
+    override fun setStatic(static: Boolean) {
+        set(STATIC, static)
+    }
+
+    override fun setAbstract(abstract: Boolean) {
+        set(ABSTRACT, abstract)
+    }
+
+    override fun setFinal(final: Boolean) {
+        set(FINAL, final)
+    }
+
+    override fun setNative(native: Boolean) {
+        set(NATIVE, native)
+    }
+
+    override fun setSynchronized(synchronized: Boolean) {
+        set(SYNCHRONIZED, synchronized)
+    }
+
+    override fun setStrictFp(strictfp: Boolean) {
+        set(STRICT_FP, strictfp)
+    }
+
+    override fun setTransient(transient: Boolean) {
+        set(TRANSIENT, transient)
+    }
+
+    override fun setVolatile(volatile: Boolean) {
+        set(VOLATILE, volatile)
+    }
+
+    override fun setDefault(default: Boolean) {
+        set(DEFAULT, default)
+    }
+
+    override fun setInternal(internal: Boolean) {
+        set(INTERNAL, internal)
+    }
+
+    override fun setSealed(sealed: Boolean) {
+        set(SEALED, sealed)
+    }
+
+    override fun setInfix(infix: Boolean) {
+        set(INFIX, infix)
+    }
+
+    override fun setOperator(operator: Boolean) {
+        set(OPERATOR, operator)
+    }
+
+    override fun setInline(inline: Boolean) {
+        set(INLINE, inline)
+    }
+
+    override fun setVarArg(vararg: Boolean) {
+        set(VARARG, vararg)
+    }
+
+    fun setDeprecated(deprecated: Boolean) {
+        set(DEPRECATED, deprecated)
+    }
+
+    fun setSuspend(suspend: Boolean) {
+        set(SUSPEND, suspend)
+    }
+
+    override fun addAnnotation(annotation: AnnotationItem) {
+        if (annotations == null) {
+            annotations = mutableListOf()
+        }
+        annotations?.add(annotation)
+    }
+
+    override fun removeAnnotation(annotation: AnnotationItem) {
+        annotations?.remove(annotation)
+    }
+
+    override fun clearAnnotations(annotation: AnnotationItem) {
+        annotations?.clear()
+    }
+
+    override fun isEmpty(): Boolean {
+        return flags and DEPRECATED.inv() == 0 // deprecated isn't a real modifier
+    }
+
+    override fun isPackagePrivate(): Boolean {
+        return flags and (PUBLIC or PROTECTED or PRIVATE or INTERNAL) == 0
+    }
+
+    fun getAccessFlags(): Int {
+        return flags and (PUBLIC or PROTECTED or PRIVATE or INTERNAL)
+    }
+
+    /** Sets the given modifier */
+    fun set(modifier: String) {
+        set(bit(modifier), true)
+    }
+
+    // Rename? It's not a full equality, it's whether an override's modifier set is significant
+    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
+        }
+        return false
+    }
+
+    companion object {
+        const val PUBLIC = 1 shl 0
+        const val PROTECTED = 1 shl 1
+        const val PRIVATE = 1 shl 2
+        const val STATIC = 1 shl 3
+        const val ABSTRACT = 1 shl 4
+        const val FINAL = 1 shl 5
+        const val NATIVE = 1 shl 6
+        const val SYNCHRONIZED = 1 shl 7
+        const val STRICT_FP = 1 shl 8
+        const val TRANSIENT = 1 shl 9
+        const val VOLATILE = 1 shl 10
+        const val DEFAULT = 1 shl 11
+        const val DEPRECATED = 1 shl 12
+        const val VARARG = 1 shl 13
+        const val SEALED = 1 shl 14
+        const val INTERNAL = 1 shl 15
+        const val INFIX = 1 shl 16
+        const val OPERATOR = 1 shl 17
+        const val INLINE = 1 shl 18
+        const val SUSPEND = 1 shl 19
+
+        private fun bit(modifier: String): Int {
+            return when (modifier) {
+                "public" -> PUBLIC
+                "protected" -> PROTECTED
+                "private" -> PRIVATE
+                "static" -> STATIC
+                "abstract" -> ABSTRACT
+                "final" -> FINAL
+                "native" -> NATIVE
+                "synchronized" -> SYNCHRONIZED
+                "strictfp" -> STRICT_FP
+                "transient" -> TRANSIENT
+                "volatile" -> VOLATILE
+                "default" -> DEFAULT
+                "deprecated" -> DEPRECATED
+                "vararg" -> VARARG
+                "sealed" -> SEALED
+                "internal" -> INTERNAL
+                "infix" -> INFIX
+                "operator" -> OPERATOR
+                "inline" -> INLINE
+                "suspend" -> SUSPEND
+                else -> error("Unsupported modifier $modifier")
+            }
+        }
+
+        /**
+         * Modifiers considered significant to include signature files (and similarly
+         * 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
+            SEALED or INTERNAL or INFIX or OPERATOR or SUSPEND
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/ErrorConfiguration.kt b/src/main/java/com/android/tools/metalava/model/ErrorConfiguration.kt
new file mode 100644
index 0000000..16cec23
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/ErrorConfiguration.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.model
+
+import com.android.tools.metalava.Severity
+import com.android.tools.metalava.doclava1.Errors
+
+/** An error configuration is a set of overrides for severities for various [Errors.Error] */
+class ErrorConfiguration {
+    private val overrides = mutableMapOf<Errors.Error, Severity>()
+
+    /** Returns the severity of the given issue */
+    fun getSeverity(error: Errors.Error): Severity {
+        return overrides[error] ?: error.level
+    }
+
+    private fun setSeverity(error: Errors.Error, severity: Severity) {
+        overrides[error] = severity
+    }
+
+    /** Set the severity of the given issue to [Severity.ERROR] */
+    fun error(error: Errors.Error) {
+        setSeverity(error, Severity.ERROR)
+    }
+
+    /** Set the severity of the given issue to [Severity.HIDDEN] */
+    fun hide(error: Errors.Error) {
+        setSeverity(error, Severity.HIDDEN)
+    }
+}
+
+/** Default error configuration: uses all the severities initialized in the [Errors] class */
+val defaultConfiguration = ErrorConfiguration()
+
+/** Current configuration to apply when reporting errors */
+var configuration = defaultConfiguration
diff --git a/src/main/java/com/android/tools/metalava/model/FieldItem.kt b/src/main/java/com/android/tools/metalava/model/FieldItem.kt
index f512833..2861bea 100644
--- a/src/main/java/com/android/tools/metalava/model/FieldItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/FieldItem.kt
@@ -38,6 +38,12 @@
     fun isEnumConstant(): Boolean
 
     /**
+     * If this field is copied from a super class (typically via [duplicate]) this
+     * field points to the original class it was copied from
+     */
+    var inheritedFrom: ClassItem?
+
+    /**
      * Duplicates this field item. Used when we need to insert inherited fields from
      * interfaces etc.
      */
diff --git a/src/main/java/com/android/tools/metalava/model/Item.kt b/src/main/java/com/android/tools/metalava/model/Item.kt
index e698476..d0f9722 100644
--- a/src/main/java/com/android/tools/metalava/model/Item.kt
+++ b/src/main/java/com/android/tools/metalava/model/Item.kt
@@ -44,6 +44,12 @@
      */
     var included: Boolean
 
+    /**
+     * Whether this element was originally hidden with @hide/@Hide. The [hidden] property
+     * tracks whether it is *actually* hidden, since elements can be unhidden via show annotations, etc.
+     */
+    var originallyHidden: Boolean
+
     /** Whether this element has been hidden with @hide/@Hide (or after propagation, in some containing class/pkg) */
     var hidden: Boolean
 
@@ -60,7 +66,7 @@
     var removed: Boolean
 
     /** True if this element has been marked deprecated */
-    val deprecated: Boolean
+    var deprecated: Boolean
 
     /** True if this element is only intended for documentation */
     var docOnly: Boolean
@@ -183,6 +189,126 @@
      * {@link java.util.List List}.
      */
     fun fullyQualifiedDocumentation(): String = documentation
+
+    /** Expands the given documentation comment in the current name context */
+    fun fullyQualifiedDocumentation(documentation: String): String = documentation
+
+    /** Produces a user visible description of this item, including a label such as "class" or "field" */
+    fun describe(capitalize: Boolean = false) = describe(this, capitalize)
+
+    companion object {
+        fun describe(item: Item, capitalize: Boolean = false): String {
+            return when (item) {
+                is PackageItem -> describe(item, capitalize = capitalize)
+                is ClassItem -> describe(item, capitalize = capitalize)
+                is FieldItem -> describe(item, capitalize = capitalize)
+                is MethodItem -> describe(
+                    item,
+                    includeParameterNames = false,
+                    includeParameterTypes = true,
+                    capitalize = capitalize
+                )
+                is ParameterItem -> describe(
+                    item,
+                    includeParameterNames = true,
+                    includeParameterTypes = true,
+                    capitalize = capitalize
+                )
+                else -> item.toString()
+            }
+        }
+
+        fun describe(
+            item: MethodItem,
+            includeParameterNames: Boolean = false,
+            includeParameterTypes: Boolean = false,
+            includeReturnValue: Boolean = false,
+            capitalize: Boolean = false
+        ): String {
+            val builder = StringBuilder()
+            if (item.isConstructor()) {
+                builder.append(if (capitalize) "Constructor" else "constructor")
+            } else {
+                builder.append(if (capitalize) "Method" else "method")
+            }
+            builder.append(' ')
+            if (includeReturnValue && !item.isConstructor()) {
+                builder.append(item.returnType()?.toSimpleType())
+                builder.append(' ')
+            }
+            appendMethodSignature(builder, item, includeParameterNames, includeParameterTypes)
+            return builder.toString()
+        }
+
+        fun describe(
+            item: ParameterItem,
+            includeParameterNames: Boolean = false,
+            includeParameterTypes: Boolean = false,
+            capitalize: Boolean = false
+        ): String {
+            val builder = StringBuilder()
+            builder.append(if (capitalize) "Parameter" else "parameter")
+            builder.append(' ')
+            builder.append(item.name())
+            builder.append(" in ")
+            val method = item.containingMethod()
+            appendMethodSignature(builder, method, includeParameterNames, includeParameterTypes)
+            return builder.toString()
+        }
+
+        private fun appendMethodSignature(
+            builder: StringBuilder,
+            item: MethodItem,
+            includeParameterNames: Boolean,
+            includeParameterTypes: Boolean
+        ) {
+            builder.append(item.containingClass().qualifiedName())
+            if (!item.isConstructor()) {
+                builder.append('.')
+                builder.append(item.name())
+            }
+            if (includeParameterNames || includeParameterTypes) {
+                builder.append('(')
+                var first = true
+                for (parameter in item.parameters()) {
+                    if (first) {
+                        first = false
+                    } else {
+                        builder.append(',')
+                        if (includeParameterNames && includeParameterTypes) {
+                            builder.append(' ')
+                        }
+                    }
+                    if (includeParameterTypes) {
+                        builder.append(parameter.type().toSimpleType())
+                        if (includeParameterNames) {
+                            builder.append(' ')
+                        }
+                    }
+                    if (includeParameterNames) {
+                        builder.append(parameter.publicName() ?: parameter.name())
+                    }
+                }
+                builder.append(')')
+            }
+        }
+
+        private fun describe(item: FieldItem, capitalize: Boolean = false): String {
+            return if (item.isEnumConstant()) {
+                "${if (capitalize) "Enum" else "enum"} constant ${item.containingClass().qualifiedName()}.${item.name()}"
+            } else {
+                "${if (capitalize) "Field" else "field"} ${item.containingClass().qualifiedName()}.${item.name()}"
+            }
+        }
+
+        private fun describe(item: ClassItem, capitalize: Boolean = false): String {
+            return "${if (capitalize) "Class" else "class"} ${item.qualifiedName()}"
+        }
+
+        private fun describe(item: PackageItem, capitalize: Boolean = false): String {
+            return "${if (capitalize) "Package" else "package"} ${item.qualifiedName()}"
+        }
+    }
 }
 
 abstract class DefaultItem(override val sortingRank: Int = nextRank++) : Item {
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 a3836c0..edc1309 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.doclava1.TextCodebase
 import com.android.tools.metalava.model.visitors.ItemVisitor
 import com.android.tools.metalava.model.visitors.TypeVisitor
 import java.util.LinkedHashSet
@@ -138,6 +139,12 @@
     var inheritedMethod: Boolean
 
     /**
+     * If this method is inherited from a super class (typically via [duplicate]) this
+     * field points to the original class it was inherited from
+     */
+    var inheritedFrom: ClassItem?
+
+    /**
      * Duplicates this field item. Used when we need to insert inherited fields from
      * interfaces etc.
      */
@@ -236,7 +243,8 @@
                 val p2n = p2.size
                 for (i in 0 until minOf(p1n, p2n)) {
                     val compareTypes =
-                        p1[i].type().toTypeString().compareTo(p2[i].type().toTypeString(), ignoreCase = true)
+                        p1[i].type().toTypeString()
+                            .compareTo(p2[i].type().toTypeString(), ignoreCase = true)
                     if (compareTypes != 0) {
                         return compareTypes
                     }
@@ -261,22 +269,6 @@
             }
         }
 
-        /** Gets the primary super method from a given method */
-        fun getPrimarySuperMethod(method: MethodItem): MethodItem? {
-            val superMethods = method.superMethods()
-            return when {
-                superMethods.isEmpty() -> null
-                superMethods.size > 1 -> {
-                    // Prefer default methods (or super class method bodies)
-                    superMethods
-                        .filter { it.modifiers.isDefault() || it.containingClass().isClass() }
-                        .forEach { return it }
-                    superMethods[0]
-                }
-                else -> superMethods[0]
-            }
-        }
-
         fun sameSignature(method: MethodItem, superMethod: MethodItem, compareRawTypes: Boolean = false): Boolean {
             // If the return types differ, override it (e.g. parent implements clone(),
             // subclass overrides with more specific return type)
@@ -410,6 +402,19 @@
     fun defaultValue(): String = ""
 
     /**
+     * Check the declared default annotation value and return true if the defaults
+     * are the same. Only defined on two annotation methods; for all other
+     * methods the result is "true".
+     */
+    fun hasSameValue(other: MethodItem): Boolean {
+        if (!containingClass().isAnnotationType() || !other.containingClass().isAnnotationType()) {
+            return true
+        }
+
+        return defaultValue() == other.defaultValue()
+    }
+
+    /**
      * Returns true if this method is a signature match for the given method (e.g. can
      * be overriding). This checks that the name and parameter lists match, but ignores
      * differences in parameter names, return value types and throws list types.
@@ -431,9 +436,24 @@
         for (i in 0 until parameters1.size) {
             val parameter1 = parameters1[i]
             val parameter2 = parameters2[i]
-            val type1 = parameter1.type().toErasedTypeString()
-            val type2 = parameter2.type().toErasedTypeString()
+            val typeString1 = parameter1.type().toString()
+            val typeString2 = parameter2.type().toString()
+            if (typeString1 == typeString2) {
+                continue
+            }
+            val type1 = parameter1.type().toErasedTypeString(this)
+            val type2 = parameter2.type().toErasedTypeString(other)
+
             if (type1 != type2) {
+                // Workaround for signature-based codebase, where we can't always resolve generic
+                // parameters: if we see a mismatch here which looks like a failure to erase say T into
+                // java.lang.Object, don't treat that as a mismatch. (Similar common case: T[] and Object[])
+                if (typeString1[0].isUpperCase() &&
+                    typeString1.length == 1 || !typeString2[1].isLetterOrDigit() &&
+                    parameter1.codebase is TextCodebase
+                ) {
+                    continue
+                }
                 return false
             }
         }
@@ -442,4 +462,4 @@
 
     /** Whether this method is a getter/setter for an underlying Kotlin property (val/var) */
     fun isKotlinProperty(): Boolean = false
-}
\ No newline at end of file
+}
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 a6c1b9f..46074fc 100644
--- a/src/main/java/com/android/tools/metalava/model/ModifierList.kt
+++ b/src/main/java/com/android/tools/metalava/model/ModifierList.kt
@@ -53,11 +53,13 @@
 
     fun isInternal(): Boolean = false
     fun isInfix(): Boolean = false
+    fun isSuspend(): Boolean = false
     fun isOperator(): Boolean = false
     fun isInline(): Boolean = false
     fun isEmpty(): Boolean
 
     fun isPackagePrivate() = !(isPublic() || isProtected() || isPrivate())
+    fun isPublicOrProtected() = isPublic() || isProtected()
 
     // Rename? It's not a full equality, it's whether an override's modifier set is significant
     fun equivalentTo(other: ModifierList): Boolean {
@@ -85,6 +87,11 @@
         return annotations().any { it.isNonNull() || it.isNullable() }
     }
 
+    /** Returns true if this modifier list contains any a Nullable annotation */
+    fun isNullable(): Boolean {
+        return annotations().any { it.isNullable() }
+    }
+
     /**
      * Returns true if this modifier list contains any annotations explicitly passed in
      * via [Options.showAnnotations]
@@ -100,6 +107,20 @@
 
     /**
      * Returns true if this modifier list contains any annotations explicitly passed in
+     * via [Options.showSingleAnnotations]
+     */
+    fun hasShowSingleAnnotation(): Boolean {
+
+        if (options.showSingleAnnotations.isEmpty()) {
+            return false
+        }
+        return annotations().any {
+            options.showSingleAnnotations.contains(it.qualifiedName())
+        }
+    }
+
+    /**
+     * Returns true if this modifier list contains any annotations explicitly passed in
      * via [Options.hideAnnotations]
      */
     fun hasHideAnnotations(): Boolean {
@@ -160,6 +181,7 @@
         }
     }
 
+    /** User visible description of the visibility in this modifier list */
     fun getVisibilityString(): String {
         return when {
             isPublic() -> "public"
@@ -171,22 +193,37 @@
         }
     }
 
+    /**
+     * Like [getVisibilityString], but package private has no modifiers; this typically corresponds to
+     * the source code for the visibility modifiers in the modifier list
+     */
+    fun getVisibilityModifiers(): String {
+        return when {
+            isPublic() -> "public"
+            isProtected() -> "protected"
+            isPackagePrivate() -> ""
+            isInternal() -> "internal"
+            isPrivate() -> "private"
+            else -> error(toString())
+        }
+    }
+
     companion object {
         fun write(
             writer: Writer,
             modifiers: ModifierList,
             item: Item,
+            target: AnnotationTarget,
             // TODO: "deprecated" isn't a modifier; clarify method name
             includeDeprecated: Boolean = false,
             includeAnnotations: Boolean = true,
+            runtimeAnnotationsOnly: Boolean = false,
             skipNullnessAnnotations: Boolean = false,
             omitCommonPackages: Boolean = false,
             removeAbstract: Boolean = false,
             removeFinal: Boolean = false,
             addPublic: Boolean = false,
-            onlyIncludeSignatureAnnotations: Boolean = true,
-            onlyIncludeStubAnnotations: Boolean = false,
-            onlyIncludeClassRetentionAnnotations: Boolean = false
+            separateLines: Boolean = false
         ) {
 
             val list = if (removeAbstract || removeFinal || addPublic) {
@@ -210,18 +247,30 @@
 
             if (includeAnnotations) {
                 writeAnnotations(
-                    list = list,
-                    skipNullnessAnnotations = skipNullnessAnnotations,
-                    omitCommonPackages = omitCommonPackages,
-                    separateLines = false,
-                    writer = writer,
-                    onlyIncludeSignatureAnnotations = onlyIncludeSignatureAnnotations,
-                    onlyIncludeStubAnnotations = onlyIncludeStubAnnotations,
-                    onlyIncludeClassRetentionAnnotations = onlyIncludeClassRetentionAnnotations
+                    item,
+                    target,
+                    runtimeAnnotationsOnly,
+                    includeDeprecated,
+                    writer,
+                    separateLines,
+                    list,
+                    skipNullnessAnnotations,
+                    omitCommonPackages
                 )
+            } else {
+                // We always include @Deprecated annotation in stub files
+                if (item.deprecated && target.isStubsFile()) {
+                    writer.write("@Deprecated")
+                    writer.write(if (separateLines) "\n" else " ")
+                }
             }
 
-            if (compatibility.doubleSpaceForPackagePrivate && item.isPackagePrivate && item is MemberItem) {
+            if (item is PackageItem) {
+                // Packages use a modifier list, but only annotations apply
+                return
+            }
+
+            if (compatibility.extraSpaceForEmptyModifiers && item.isPackagePrivate && item is MemberItem) {
                 writer.write(" ")
             }
 
@@ -264,6 +313,14 @@
                     writer.write("sealed ")
                 }
 
+                if (list.isSuspend()) {
+                    writer.write("suspend ")
+                }
+
+                if (list.isInline()) {
+                    writer.write("inline ")
+                }
+
                 if (list.isInfix()) {
                     writer.write("infix ")
                 }
@@ -285,15 +342,15 @@
                     writer.write("abstract ")
                 }
 
-                if (!compatibility.skipNativeModifier && list.isNative()) {
+                if (list.isNative() && (target.isStubsFile() || !compatibility.skipNativeModifier)) {
                     writer.write("native ")
                 }
 
-                if (item.deprecated && includeDeprecated) {
+                if (item.deprecated && includeDeprecated && !target.isStubsFile() && options.compatOutput) {
                     writer.write("deprecated ")
                 }
 
-                if (list.isSynchronized()) {
+                if (list.isSynchronized() && (options.compatOutput || target.isStubsFile())) {
                     writer.write("synchronized ")
                 }
 
@@ -309,7 +366,7 @@
                     writer.write("volatile ")
                 }
             } else {
-                if (item.deprecated && includeDeprecated) {
+                if (item.deprecated && includeDeprecated && !target.isStubsFile() && options.compatOutput) {
                     writer.write("deprecated ")
                 }
 
@@ -353,6 +410,14 @@
                     writer.write("sealed ")
                 }
 
+                if (list.isSuspend()) {
+                    writer.write("suspend ")
+                }
+
+                if (list.isInline()) {
+                    writer.write("inline ")
+                }
+
                 if (list.isInfix()) {
                     writer.write("infix ")
                 }
@@ -369,11 +434,11 @@
                     writer.write("volatile ")
                 }
 
-                if (list.isSynchronized()) {
+                if (list.isSynchronized() && (options.compatOutput || target.isStubsFile())) {
                     writer.write("synchronized ")
                 }
 
-                if (!compatibility.skipNativeModifier && list.isNative()) {
+                if (list.isNative() && (target.isStubsFile() || !compatibility.skipNativeModifier)) {
                     writer.write("native ")
                 }
 
@@ -384,33 +449,73 @@
         }
 
         fun writeAnnotations(
+            item: Item,
+            target: AnnotationTarget,
+            runtimeAnnotationsOnly: Boolean,
+            includeDeprecated: Boolean,
+            writer: Writer,
+            separateLines: Boolean,
+            list: ModifierList,
+            skipNullnessAnnotations: Boolean,
+            omitCommonPackages: Boolean
+        ) {
+            //  if includeDeprecated we want to do it
+            //  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()) &&
+                (runtimeAnnotationsOnly || includeDeprecated)
+            ) {
+                writer.write("@Deprecated")
+                writer.write(if (separateLines) "\n" else " ")
+            }
+
+            writeAnnotations(
+                list = list,
+                runtimeAnnotationsOnly = runtimeAnnotationsOnly,
+                skipNullnessAnnotations = skipNullnessAnnotations,
+                omitCommonPackages = omitCommonPackages,
+                separateLines = separateLines,
+                writer = writer,
+                target = target
+            )
+        }
+
+        fun writeAnnotations(
             list: ModifierList,
             skipNullnessAnnotations: Boolean = false,
+            runtimeAnnotationsOnly: Boolean = false,
             omitCommonPackages: Boolean = false,
             separateLines: Boolean = false,
             filterDuplicates: Boolean = false,
             writer: Writer,
-            onlyIncludeSignatureAnnotations: Boolean = true,
-            onlyIncludeStubAnnotations: Boolean = true,
-            onlyIncludeClassRetentionAnnotations: Boolean = false
+            target: AnnotationTarget
         ) {
             val annotations = list.annotations()
+
+            // Ensure stable signature file order
+            if (annotations.size > 2) {
+                annotations.sortedBy { it.qualifiedName() }
+            }
+
             if (annotations.isNotEmpty()) {
                 var index = -1
                 for (annotation in annotations) {
                     index++
-                    if (onlyIncludeSignatureAnnotations && !annotation.isSignificantInSignatures()) {
+
+                    if (runtimeAnnotationsOnly && annotation.retention != AnnotationRetention.RUNTIME) {
                         continue
-                    } else if (onlyIncludeStubAnnotations && !annotation.isSignificantInStubs()) {
+                    }
+
+                    if (!annotation.targets().contains(target)) {
                         continue
-                    } else if (onlyIncludeClassRetentionAnnotations && !annotation.hasClassRetention() &&
-                        !options.includeSourceRetentionAnnotations
-                    ) {
-                        continue
-                    } else if ((annotation.isNonNull() || annotation.isNullable())) {
+                    } else if ((annotation.isNullnessAnnotation())) {
                         if (skipNullnessAnnotations) {
                             continue
                         }
+                    } else if (annotation.qualifiedName() == "java.lang.Deprecated") {
+                        // Special cased in stubs and signature files: emitted first
+                        continue
                     }
 
                     // Optionally filter out duplicates
diff --git a/src/main/java/com/android/tools/metalava/model/MutableModifierList.kt b/src/main/java/com/android/tools/metalava/model/MutableModifierList.kt
index feeb615..f5aa3bf 100644
--- a/src/main/java/com/android/tools/metalava/model/MutableModifierList.kt
+++ b/src/main/java/com/android/tools/metalava/model/MutableModifierList.kt
@@ -29,6 +29,12 @@
     fun setTransient(transient: Boolean)
     fun setVolatile(volatile: Boolean)
     fun setDefault(default: Boolean)
+    fun setInternal(internal: Boolean)
+    fun setSealed(sealed: Boolean)
+    fun setInfix(infix: Boolean)
+    fun setOperator(operator: Boolean)
+    fun setInline(inline: Boolean)
+    fun setVarArg(vararg: Boolean)
 
     fun addAnnotation(annotation: AnnotationItem)
     fun removeAnnotation(annotation: AnnotationItem)
diff --git a/src/main/java/com/android/tools/metalava/model/PackageDocs.kt b/src/main/java/com/android/tools/metalava/model/PackageDocs.kt
index 97286b5..0c3c243 100644
--- a/src/main/java/com/android/tools/metalava/model/PackageDocs.kt
+++ b/src/main/java/com/android/tools/metalava/model/PackageDocs.kt
@@ -16,6 +16,11 @@
 
 package com.android.tools.metalava.model
 
-data class PackageDocs(val packageDocs: MutableMap<String, String>, val hiddenPackages: MutableSet<String>) {
-    fun getDocs(pkg: PackageItem): String? = packageDocs[pkg.qualifiedName()]
+data class PackageDocs(
+    val packageDocs: MutableMap<String, String>,
+    val overviewDocs: MutableMap<String, String>,
+    val hiddenPackages: MutableSet<String>
+) {
+    fun getPackageDocumentation(pkg: PackageItem): String? = packageDocs[pkg.qualifiedName()]
+    fun getOverviewDocumentation(pkg: PackageItem): String? = overviewDocs[pkg.qualifiedName()]
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/PackageList.kt b/src/main/java/com/android/tools/metalava/model/PackageList.kt
index 58ebf3c..d71caa0 100644
--- a/src/main/java/com/android/tools/metalava/model/PackageList.kt
+++ b/src/main/java/com/android/tools/metalava/model/PackageList.kt
@@ -19,11 +19,13 @@
 import com.android.tools.metalava.model.visitors.ItemVisitor
 import com.android.tools.metalava.model.visitors.TypeVisitor
 
-class PackageList(val packages: List<PackageItem>) {
+class PackageList(val codebase: Codebase, val packages: List<PackageItem>) {
     fun accept(visitor: ItemVisitor) {
+        visitor.visitCodebase(codebase)
         packages.forEach {
             it.accept(visitor)
         }
+        visitor.afterVisitCodebase(codebase)
     }
 
     fun acceptTypes(visitor: TypeVisitor) {
diff --git a/src/main/java/com/android/tools/metalava/model/PropertyItem.kt b/src/main/java/com/android/tools/metalava/model/PropertyItem.kt
new file mode 100644
index 0000000..f82708c
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/PropertyItem.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.model
+
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.TypeVisitor
+
+interface PropertyItem : MemberItem {
+    /** The type of this property */
+    fun type(): TypeItem
+
+    override fun accept(visitor: ItemVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        visitor.visitItem(this)
+        visitor.visitProperty(this)
+
+        visitor.afterVisitProperty(this)
+        visitor.afterVisitItem(this)
+    }
+
+    override fun acceptTypes(visitor: TypeVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        val type = type()
+        visitor.visitType(type, this)
+        visitor.afterVisitType(type, this)
+    }
+
+    override fun hasNullnessInfo(): Boolean {
+        if (!requiresNullnessInfo()) {
+            return true
+        }
+
+        return modifiers.hasNullnessInfo()
+    }
+
+    override fun requiresNullnessInfo(): Boolean {
+        if (type().primitive) {
+            return false
+        }
+
+        return true
+    }
+
+    companion object {
+        val comparator: java.util.Comparator<PropertyItem> = Comparator { a, b -> a.name().compareTo(b.name()) }
+    }
+}
\ No newline at end of file
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 b1ac351..5261e46 100644
--- a/src/main/java/com/android/tools/metalava/model/TypeItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/TypeItem.kt
@@ -48,11 +48,12 @@
     fun toTypeString(
         outerAnnotations: Boolean = false,
         innerAnnotations: Boolean = outerAnnotations,
-        erased: Boolean = false
+        erased: Boolean = false,
+        context: Item? = null
     ): String
 
     /** Alias for [toTypeString] with erased=true */
-    fun toErasedTypeString(): String
+    fun toErasedTypeString(context: Item? = null): String
 
     /** Returns the internal name of the type, as seen in bytecode */
     fun internalName(): String {
@@ -213,7 +214,7 @@
 
             var cleaned = type
 
-            if (compatibility.spacesAfterCommas && cleaned.indexOf(',') != -1) {
+            if (compatibility.spaceAfterCommaInTypes && cleaned.indexOf(',') != -1) {
                 // The compat files have spaces after commas where we normally don't
                 cleaned = cleaned.replace(",", ", ").replace(",  ", ", ")
             }
@@ -290,4 +291,4 @@
             return dimension + base
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/android/tools/metalava/model/TypeParameterItem.kt b/src/main/java/com/android/tools/metalava/model/TypeParameterItem.kt
index 7238e33..c419deb 100644
--- a/src/main/java/com/android/tools/metalava/model/TypeParameterItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/TypeParameterItem.kt
@@ -18,4 +18,5 @@
 
 interface TypeParameterItem : ClassItem {
     fun bounds(): List<ClassItem>
+    fun isReified(): Boolean
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt b/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt
new file mode 100644
index 0000000..bcd526b
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/TypeParameterListOwner.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.model
+
+interface TypeParameterListOwner {
+    fun typeParameterList(): TypeParameterList
+    /** Given a variable in this owner, resolves to a type parameter item */
+    fun resolveParameter(variable: String): TypeParameterItem?
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/Xml.kt b/src/main/java/com/android/tools/metalava/model/Xml.kt
new file mode 100644
index 0000000..9364edf
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/Xml.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.model
+
+import com.android.utils.XmlUtils.stripBom
+import org.w3c.dom.Document
+import org.xml.sax.ErrorHandler
+import org.xml.sax.InputSource
+import org.xml.sax.SAXParseException
+import java.io.PrintWriter
+import java.io.Reader
+import java.io.StringReader
+import javax.xml.parsers.DocumentBuilder
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.parsers.ParserConfigurationException
+
+// XML parser features
+private const val LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd"
+private const val EXTERNAL_PARAMETER_ENTITIES = "http://xml.org/sax/features/external-parameter-entities"
+private const val EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities"
+
+/**
+ * Parses the given XML string as a DOM document, using the JDK parser. The parser does not
+ * validate, and is optionally namespace aware.
+ *
+ * @param xml the XML content to be parsed (must be well formed)
+ * @param namespaceAware whether the parser is namespace aware
+ * @param errorWriter the writer to write errors to
+ * @return the DOM document
+ */
+fun parseDocument(xml: String, namespaceAware: Boolean, errorWriter: PrintWriter = PrintWriter(System.out)): Document {
+    return parseDocument(StringReader(stripBom(xml)), namespaceAware, errorWriter)
+}
+
+/**
+ * Parses the given [Reader] as a DOM document, using the JDK parser. The parser does not
+ * validate, and is optionally namespace aware.
+ *
+ * @param xml a reader for the XML content to be parsed (must be well formed)
+ * @param namespaceAware whether the parser is namespace aware
+ * @param errorWriter the writer to write errors to
+ * @return the DOM document
+ */
+fun parseDocument(xml: Reader, namespaceAware: Boolean, errorWriter: PrintWriter = PrintWriter(System.out)): Document {
+    val inputStream = InputSource(xml)
+    val builder = createDocumentBuilder(namespaceAware)
+    builder.setErrorHandler(object : ErrorHandler {
+        override fun warning(exception: SAXParseException) {
+            errorWriter.println("${exception.lineNumber}: Warning: ${exception.message}")
+        }
+
+        override fun error(exception: SAXParseException) {
+            errorWriter.println("${exception.lineNumber}: Error: ${exception.message}")
+        }
+
+        override fun fatalError(exception: SAXParseException) {
+            error(exception)
+        }
+    })
+    return builder.parse(inputStream)
+}
+
+/** Creates a preconfigured document builder.  */
+private fun createDocumentBuilder(namespaceAware: Boolean): DocumentBuilder {
+    try {
+        val factory = DocumentBuilderFactory.newInstance()
+        factory.isNamespaceAware = namespaceAware
+        factory.isValidating = false
+        factory.setFeature(EXTERNAL_GENERAL_ENTITIES, false)
+        factory.setFeature(EXTERNAL_PARAMETER_ENTITIES, false)
+        factory.setFeature(LOAD_EXTERNAL_DTD, false)
+        return factory.newDocumentBuilder()
+    } catch (e: ParserConfigurationException) {
+        throw Error(e) // Impossible in the current context.
+    }
+}
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
new file mode 100644
index 0000000..7a96a0c
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt
@@ -0,0 +1,550 @@
+/*
+ * 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.model.psi
+
+import com.android.tools.lint.detector.api.ConstantEvaluator
+import com.android.tools.metalava.Severity
+import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.canonicalizeFloatingPointString
+import com.android.tools.metalava.model.javaEscapeString
+import com.android.tools.metalava.reporter
+import com.android.utils.XmlUtils
+import com.intellij.psi.PsiAnnotation
+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
+import com.intellij.psi.PsiTypeCastExpression
+import com.intellij.psi.PsiVariable
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UBinaryExpression
+import org.jetbrains.uast.UBinaryExpressionWithType
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.ULambdaExpression
+import org.jetbrains.uast.ULiteralExpression
+import org.jetbrains.uast.UReferenceExpression
+import org.jetbrains.uast.UUnaryExpression
+import org.jetbrains.uast.util.isArrayInitializer
+import org.jetbrains.uast.util.isConstructorCall
+import org.jetbrains.uast.util.isTypeCast
+import java.util.function.Predicate
+
+/** Utility methods */
+open class CodePrinter(
+    private val codebase: Codebase,
+    /** Whether we should inline the values of fields, e.g. instead of "Integer.MAX_VALUE" we'd emit "0x7fffffff" */
+    private val inlineFieldValues: Boolean = true,
+    /** Whether we should inline constants when possible, e.g. instead of "2*20+2" we'd emit "42" */
+    private val inlineConstants: Boolean = true,
+    /** Whether we should drop unknown AST nodes instead of inserting the corresponding source text strings */
+    private val skipUnknown: Boolean = false,
+    /** 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(Severity.WARNING, null as PsiElement?, message, Errors.INTERNAL_ERROR)
+    }
+
+    /** Given an annotation member value, returns the corresponding Java source expression */
+    fun toSourceExpression(value: PsiAnnotationMemberValue, owner: Item): String {
+        val sb = StringBuilder()
+        appendSourceExpression(value, sb, owner)
+        return sb.toString()
+    }
+
+    private fun appendSourceExpression(value: PsiAnnotationMemberValue, sb: StringBuilder, owner: Item): Boolean {
+        if (value is PsiReference) {
+            val resolved = value.resolve()
+            if (resolved is PsiField) {
+                sb.append(resolved.containingClass?.qualifiedName).append('.').append(resolved.name)
+                return true
+            }
+        } else if (value is PsiLiteral) {
+            return appendSourceLiteral(value.value, sb, owner)
+        } else if (value is PsiClassObjectAccessExpression) {
+            sb.append(value.operand.type.canonicalText).append(".class")
+            return true
+        } else if (value is PsiArrayInitializerMemberValue) {
+            sb.append('{')
+            var first = true
+            val initialLength = sb.length
+            for (e in value.initializers) {
+                val length = sb.length
+                if (first) {
+                    first = false
+                } else {
+                    sb.append(", ")
+                }
+                val appended = appendSourceExpression(e, sb, owner)
+                if (!appended) {
+                    // trunk off comma if it bailed for some reason (e.g. constant
+                    // filtered out by API etc)
+                    sb.setLength(length)
+                    if (length == initialLength) {
+                        first = true
+                    }
+                }
+            }
+            sb.append('}')
+            return true
+        } else if (value is PsiAnnotation) {
+            sb.append('@').append(value.qualifiedName)
+            return true
+        } else {
+            if (value is PsiTypeCastExpression) {
+                val type = value.castType?.type
+                val operand = value.operand
+                if (type != null && operand is PsiAnnotationMemberValue) {
+                    sb.append('(')
+                    sb.append(type.canonicalText)
+                    sb.append(')')
+                    return appendSourceExpression(operand, sb, owner)
+                }
+            }
+            val constant = ConstantEvaluator.evaluate(null, value)
+            if (constant != null) {
+                return appendSourceLiteral(constant, sb, owner)
+            }
+        }
+        reporter.report(Errors.INTERNAL_ERROR, owner, "Unexpected annotation default value $value")
+        return false
+    }
+
+    fun toSourceString(value: UExpression?): String? {
+        value ?: return null
+        val sb = StringBuilder()
+        return if (appendExpression(sb, value)
+        ) {
+            sb.toString()
+        } else {
+            null
+        }
+    }
+
+    private fun appendExpression(
+        sb: StringBuilder,
+        expression: UExpression
+    ): Boolean {
+        if (expression.isArrayInitializer()) {
+            val call = expression as UCallExpression
+            val initializers = call.valueArguments
+            sb.append('{')
+            var first = true
+            val initialLength = sb.length
+            for (e in initializers) {
+                val length = sb.length
+                if (first) {
+                    first = false
+                } else {
+                    sb.append(", ")
+                }
+                val appended = appendExpression(sb, e)
+                if (!appended) {
+                    // truncate trailing comma if it bailed for some reason (e.g. constant
+                    // filtered out by API etc)
+                    sb.setLength(length)
+                    if (length == initialLength) {
+                        first = true
+                    }
+                }
+            }
+            sb.append('}')
+            return sb.length != 2
+        } else if (expression is UReferenceExpression) {
+            val resolved = expression.resolve()
+            when (resolved) {
+                is PsiField -> {
+                    @Suppress("UnnecessaryVariable")
+                    val field = resolved
+                    if (!inlineFieldValues) {
+                        val value = field.computeConstantValue()
+                        if (appendLiteralValue(sb, value)) {
+                            return true
+                        }
+                    }
+
+                    val declaringClass = field.containingClass
+                    if (declaringClass == null) {
+                        warning("No containing class found for " + field.name)
+                        return false
+                    }
+                    val qualifiedName = declaringClass.qualifiedName
+                    val fieldName = field.name
+
+                    if (qualifiedName != null) {
+                        if (filterReference != null) {
+                            val cls = codebase.findClass(qualifiedName)
+                            val fld = cls?.findField(fieldName, true)
+                            if (fld == null || !filterReference.test(fld)) {
+                                // This field is not visible: remove from typedef
+                                if (fld != null) {
+                                    reporter.report(
+                                        Errors.HIDDEN_TYPEDEF_CONSTANT, fld,
+                                        "Typedef class references hidden field $fld: removed from typedef metadata"
+                                    )
+                                }
+                                return false
+                            }
+                        }
+                        sb.append(qualifiedName)
+                        sb.append('.')
+                        sb.append(fieldName)
+                        return true
+                    }
+                    return if (skipUnknown) {
+                        false
+                    } else {
+                        sb.append(expression.asSourceString())
+                        true
+                    }
+                }
+                is PsiVariable -> {
+                    sb.append(resolved.name)
+                    return true
+                }
+                else -> {
+                    if (skipUnknown) {
+                        warning("Unexpected reference to $expression")
+                        return false
+                    }
+                    sb.append(expression.asSourceString())
+                    return true
+                }
+            }
+        } else if (expression is ULiteralExpression) {
+            val literalValue = expression.value
+            if (appendLiteralValue(sb, literalValue)) {
+                return true
+            }
+        } else if (expression is UAnnotation) {
+            sb.append('@').append(expression.qualifiedName)
+            return true
+        } else if (expression is UBinaryExpressionWithType) {
+            if ((expression).isTypeCast()) {
+                sb.append('(').append(expression.type.canonicalText).append(')')
+                val operand = expression.operand
+                return appendExpression(sb, operand)
+            }
+            return false
+        } else if (expression is UBinaryExpression) {
+            if (inlineConstants) {
+                val constant = expression.evaluate()
+                if (constant != null) {
+                    sb.append(constantToSource(constant))
+                    return true
+                }
+            }
+
+            if (appendExpression(sb, expression.leftOperand)) {
+                sb.append(' ').append(expression.operator.text).append(' ')
+                if (appendExpression(sb, expression.rightOperand)) {
+                    return true
+                }
+            }
+        } else if (expression is UUnaryExpression) {
+            sb.append(expression.operator.text)
+            if (appendExpression(sb, expression.operand)) {
+                return true
+            }
+        } else if (expression is ULambdaExpression) {
+            sb.append("{ ")
+            val valueParameters = expression.valueParameters
+            if (valueParameters.isNotEmpty()) {
+                var first = true
+                for (parameter in valueParameters) {
+                    if (first) {
+                        first = false
+                    } else {
+                        sb.append(", ")
+                    }
+                    sb.append(parameter.name)
+                }
+                sb.append(" -> ")
+            }
+            val body = expression.body
+
+            if (body is UBlockExpression) {
+                var first = true
+                for (e in body.expressions) {
+                    if (first) {
+                        first = false
+                    } else {
+                        sb.append("; ")
+                    }
+                    if (!appendExpression(sb, e)) {
+                        return false
+                    }
+                }
+
+                // Special case: Turn empty lambda {  } into {}
+                if (sb.length > 2) {
+                    sb.append(' ')
+                } else {
+                    sb.setLength(1)
+                }
+                sb.append('}')
+                return true
+            } else {
+                if (appendExpression(sb, body)) {
+                    sb.append(" }")
+                    return true
+                }
+            }
+        } else if (expression is UBlockExpression) {
+            sb.append('{')
+            var first = true
+            for (e in expression.expressions) {
+                if (first) {
+                    first = false
+                } else {
+                    sb.append("; ")
+                }
+                if (!appendExpression(sb, e)) {
+                    return false
+                }
+            }
+            sb.append('}')
+            return true
+        } else if (expression.isConstructorCall()) {
+            val call = expression as UCallExpression
+            val resolved = call.classReference?.resolve()
+            if (resolved is PsiClass) {
+                sb.append(resolved.qualifiedName)
+            } else {
+                sb.append(call.classReference?.resolvedName)
+            }
+            sb.append('(')
+            var first = true
+            for (arg in call.valueArguments) {
+                if (first) {
+                    first = false
+                } else {
+                    sb.append(", ")
+                }
+                if (!appendExpression(sb, arg)) {
+                    return false
+                }
+            }
+            sb.append(')')
+            return true
+        } else {
+            sb.append(expression.asSourceString())
+            return true
+        }
+
+        // For example, binary expressions like 3 + 4
+        val literalValue = ConstantEvaluator.evaluate(null, expression)
+        if (literalValue != null) {
+            if (appendLiteralValue(sb, literalValue)) {
+                return true
+            }
+        }
+
+        warning("Unexpected annotation expression of type ${expression.javaClass} and is $expression")
+
+        return false
+    }
+
+    companion object {
+        private fun appendLiteralValue(sb: StringBuilder, literalValue: Any?): Boolean {
+            if (literalValue == null) {
+                sb.append("null")
+                return true
+            } else if (literalValue is Number || literalValue is Boolean) {
+                sb.append(literalValue.toString())
+                return true
+            } else if (literalValue is String || literalValue is Char) {
+                sb.append('"')
+                XmlUtils.appendXmlAttributeValue(sb, literalValue.toString())
+                sb.append('"')
+                return true
+            }
+            return false
+        }
+
+        fun constantToSource(value: Any?): String {
+            if (value == null) {
+                return "null"
+            }
+
+            when (value) {
+                is Int -> {
+                    return value.toString()
+                }
+                is String -> {
+                    return "\"${javaEscapeString(value)}\""
+                }
+                is Long -> {
+                    return value.toString() + "L"
+                }
+                is Boolean -> {
+                    return value.toString()
+                }
+                is Byte -> {
+                    return value.toString()
+                }
+                is Short -> {
+                    return value.toString()
+                }
+                is Float -> {
+                    return when (value) {
+                        Float.POSITIVE_INFINITY -> "(1.0f/0.0f)"
+                        Float.NEGATIVE_INFINITY -> "(-1.0f/0.0f)"
+                        Float.NaN -> "(0.0f/0.0f)"
+                        else -> {
+                            canonicalizeFloatingPointString(value.toString()) + "f"
+                        }
+                    }
+                }
+                is Double -> {
+                    return when (value) {
+                        Double.POSITIVE_INFINITY -> "(1.0/0.0)"
+                        Double.NEGATIVE_INFINITY -> "(-1.0/0.0)"
+                        Double.NaN -> "(0.0/0.0)"
+                        else -> {
+                            canonicalizeFloatingPointString(value.toString())
+                        }
+                    }
+                }
+                is Char -> {
+                    return String.format("'%s'", javaEscapeString(value.toString()))
+                }
+
+                is kotlin.Pair<*, *> -> {
+                    val first = value.first
+                    if (first is ClassId) {
+                        return first.packageFqName.asString() + "." + first.relativeClassName.asString()
+                    }
+                }
+            }
+
+            return value.toString()
+        }
+
+        fun constantToExpression(constant: Any?): String? {
+            return when (constant) {
+                is Int -> "0x${Integer.toHexString(constant)}"
+                is String -> "\"${javaEscapeString(constant)}\""
+                is Long -> "${constant}L"
+                is Boolean -> constant.toString()
+                is Byte -> Integer.toHexString(constant.toInt())
+                is Short -> Integer.toHexString(constant.toInt())
+                is Float -> {
+                    when (constant) {
+                        Float.POSITIVE_INFINITY -> "Float.POSITIVE_INFINITY"
+                        Float.NEGATIVE_INFINITY -> "Float.NEGATIVE_INFINITY"
+                        Float.NaN -> "Float.NaN"
+                        else -> {
+                            "${canonicalizeFloatingPointString(constant.toString())}F"
+                        }
+                    }
+                }
+                is Double -> {
+                    when (constant) {
+                        Double.POSITIVE_INFINITY -> "Double.POSITIVE_INFINITY"
+                        Double.NEGATIVE_INFINITY -> "Double.NEGATIVE_INFINITY"
+                        Double.NaN -> "Double.NaN"
+                        else -> {
+                            canonicalizeFloatingPointString(constant.toString())
+                        }
+                    }
+                }
+                is Char -> {
+                    "'${javaEscapeString(constant.toString())}'"
+                }
+                else -> {
+                    null
+                }
+            }
+        }
+
+        private fun appendSourceLiteral(v: Any?, sb: StringBuilder, owner: Item): Boolean {
+            if (v == null) {
+                sb.append("null")
+                return true
+            }
+            when (v) {
+                is Int, is Boolean, is Byte, is Short -> {
+                    sb.append(v.toString())
+                    return true
+                }
+                is Long -> {
+                    sb.append(v.toString()).append('L')
+                    return true
+                }
+                is String -> {
+                    sb.append('"').append(javaEscapeString(v)).append('"')
+                    return true
+                }
+                is Float -> {
+                    return when (v) {
+                        Float.POSITIVE_INFINITY -> {
+                            // This convention (displaying fractions) is inherited from doclava
+                            sb.append("(1.0f/0.0f)"); true
+                        }
+                        Float.NEGATIVE_INFINITY -> {
+                            sb.append("(-1.0f/0.0f)"); true
+                        }
+                        Float.NaN -> {
+                            sb.append("(0.0f/0.0f)"); true
+                        }
+                        else -> {
+                            sb.append(canonicalizeFloatingPointString(v.toString()) + "f")
+                            true
+                        }
+                    }
+                }
+                is Double -> {
+                    return when (v) {
+                        Double.POSITIVE_INFINITY -> {
+                            // This convention (displaying fractions) is inherited from doclava
+                            sb.append("(1.0/0.0)"); true
+                        }
+                        Double.NEGATIVE_INFINITY -> {
+                            sb.append("(-1.0/0.0)"); true
+                        }
+                        Double.NaN -> {
+                            sb.append("(0.0/0.0)"); true
+                        }
+                        else -> {
+                            sb.append(canonicalizeFloatingPointString(v.toString()))
+                            true
+                        }
+                    }
+                }
+                is Char -> {
+                    sb.append('\'').append(javaEscapeString(v.toString())).append('\'')
+                    return true
+                }
+                else -> {
+                    reporter.report(Errors.INTERNAL_ERROR, owner, "Unexpected literal value $v")
+                }
+            }
+
+            return false
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt b/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt
index 2a6d702..77c6ea4 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt
@@ -16,20 +16,72 @@
 
 package com.android.tools.metalava.model.psi
 
+import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.reporter
 import com.intellij.psi.JavaDocTokenType
 import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiJavaCodeReferenceElement
+import com.intellij.psi.PsiMember
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiReference
+import com.intellij.psi.PsiTypeParameter
+import com.intellij.psi.PsiWhiteSpace
+import com.intellij.psi.impl.source.SourceTreeToPsiMap
+import com.intellij.psi.impl.source.javadoc.PsiDocMethodOrFieldRef
+import com.intellij.psi.impl.source.tree.CompositePsiElement
+import com.intellij.psi.impl.source.tree.JavaDocElementType
 import com.intellij.psi.javadoc.PsiDocComment
 import com.intellij.psi.javadoc.PsiDocTag
 import com.intellij.psi.javadoc.PsiDocToken
+import com.intellij.psi.javadoc.PsiInlineDocTag
+import org.intellij.lang.annotations.Language
 
 /*
- * Various utilities for merging comments into existing javadoc sections.
+ * Various utilities for handling javadoc, such as
+ * merging comments into existing javadoc sections,
+ * rewriting javadocs into fully qualified references, etc.
  *
  * TODO: Handle KDoc
  */
 
 /**
+ * If true, we'll rewrite all the javadoc documentation in doc stubs
+ * to include fully qualified names
+ */
+const val EXPAND_DOCUMENTATION = true
+
+/**
+ * If the reference is to a class in the same package, include the package prefix?
+ * This should not be necessary, but doclava has problems finding classes without
+ * it. Consider turning this off when we switch to Dokka.
+ */
+const val INCLUDE_SAME_PACKAGE = true
+
+/** If documentation starts with hash, insert the implicit class? */
+const val PREPEND_LOCAL_CLASS = false
+
+/**
+ * Whether we should report unresolved symbols. This is typically
+ * a bug in the documentation. It looks like there are a LOT
+ * of mistakes right now, so I'm worried about turning this on
+ * since doclava didn't seem to abort on this.
+ *
+ * Here are some examples I've spot checked:
+ * (1) "Unresolved SQLExceptionif": In java.sql.CallableStatement the
+ * getBigDecimal method contains this, presumably missing a space
+ * before the if suffix: "@exception SQLExceptionif parameterName does not..."
+ * (2) In android.nfc.tech.IsoDep there is "@throws TagLostException if ..."
+ * but TagLostException is not imported anywhere and is not in the same
+ * package (it's in the parent package).
+ */
+const val REPORT_UNRESOLVED_SYMBOLS = false
+
+/**
  * Merges the given [newText] into the existing documentation block [existingDoc]
  * (which should be a full documentation node, including the surrounding comment
  * start and end tokens.)
@@ -57,12 +109,17 @@
             else -> newText
         }
 
-        // TODO: Handle prefixing "*" on lines, if already done in the document?
-        return if (newText.contains('\n')) {
-            "/** $content */"
-        } else {
-            return insertInto("/**\n */", content, 3)
+        val inherit =
+            when (psiElement) {
+                is PsiMethod -> psiElement.findSuperMethods(true).isNotEmpty()
+                else -> false
+            }
+        val initial = if (inherit) "/**\n* {@inheritDoc}\n */" else "/** */"
+        val new = insertInto(initial, content, initial.indexOf("*/"))
+        if (new.startsWith("/**\n * \n *")) {
+            return "/**\n *" + new.substring(10)
         }
+        return new
     }
 
     val doc = trimDocIndent(existingDoc)
@@ -130,7 +187,8 @@
             if (!append) {
                 4 // "/** ".length
             } else firstTag?.textRange?.startOffset ?: doc.length - 2
-        return insertInto(doc, newText, startOffset)
+        // Insert a <br> before the appended docs, unless it's the beginning of a doc section
+        return insertInto(doc, if (startOffset > 4) "<br>\n$newText" else newText, startOffset)
     }
 }
 
@@ -210,3 +268,771 @@
         prefix + middle + suffix
     }
 }
+
+/** Converts from package.html content to a package-info.java javadoc string. */
+@Language("JAVA")
+fun packageHtmlToJavadoc(@Language("HTML") packageHtml: String?): String {
+    packageHtml ?: return ""
+    if (packageHtml.isBlank()) {
+        return ""
+    }
+
+    val body = getBodyContents(packageHtml).trim()
+    if (body.isBlank()) {
+        return ""
+    }
+    // Combine into comment lines prefixed by asterisk, ,and make sure we don't
+    // have end-comment markers in the HTML that will escape out of the javadoc comment
+    val comment = body.lines().joinToString(separator = "\n") { " * $it" }.replace("*/", "&#42;/")
+    @Suppress("DanglingJavadoc")
+    return "/**\n$comment\n */\n"
+}
+
+/**
+ * Returns the body content from the given HTML document.
+ * Attempts to tokenize the HTML properly such that it doesn't
+ * get confused by comments or text that looks like tags.
+ */
+@Suppress("LocalVariableName")
+private fun getBodyContents(html: String): String {
+    val length = html.length
+    val STATE_TEXT = 1
+    val STATE_SLASH = 2
+    val STATE_ATTRIBUTE_NAME = 3
+    val STATE_IN_TAG = 4
+    val STATE_BEFORE_ATTRIBUTE = 5
+    val STATE_ATTRIBUTE_BEFORE_EQUALS = 6
+    val STATE_ATTRIBUTE_AFTER_EQUALS = 7
+    val STATE_ATTRIBUTE_VALUE_NONE = 8
+    val STATE_ATTRIBUTE_VALUE_SINGLE = 9
+    val STATE_ATTRIBUTE_VALUE_DOUBLE = 10
+    val STATE_CLOSE_TAG = 11
+    val STATE_ENDING_TAG = 12
+
+    var bodyStart = -1
+    var htmlStart = -1
+
+    var state = STATE_TEXT
+    var offset = 0
+    var tagStart = -1
+    var tagEndStart = -1
+    var prev = -1
+    loop@ while (offset < length) {
+        if (offset == prev) {
+            // Purely here to prevent potential bugs in the state machine from looping
+            // infinitely
+            offset++
+            if (offset == length) {
+                break
+            }
+        }
+        prev = offset
+
+        val c = html[offset]
+        when (state) {
+            STATE_TEXT -> {
+                if (c == '<') {
+                    state = STATE_SLASH
+                    offset++
+                    continue@loop
+                }
+
+                // Other text is just ignored
+                offset++
+            }
+
+            STATE_SLASH -> {
+                if (c == '!') {
+                    if (html.startsWith("!--", offset)) {
+                        // Comment
+                        val end = html.indexOf("-->", offset + 3)
+                        if (end == -1) {
+                            offset = length
+                        } else {
+                            offset = end + 3
+                            state = STATE_TEXT
+                        }
+                        continue@loop
+                    } else if (html.startsWith("![CDATA[", offset)) {
+                        val end = html.indexOf("]]>", offset + 8)
+                        if (end == -1) {
+                            offset = length
+                        } else {
+                            state = STATE_TEXT
+                            offset = end + 3
+                        }
+                        continue@loop
+                    } else {
+                        val end = html.indexOf('>', offset + 2)
+                        if (end == -1) {
+                            offset = length
+                            state = STATE_TEXT
+                        } else {
+                            offset = end + 1
+                            state = STATE_TEXT
+                        }
+                        continue@loop
+                    }
+                } else if (c == '/') {
+                    state = STATE_CLOSE_TAG
+                    offset++
+                    tagEndStart = offset
+                    continue@loop
+                } else if (c == '?') {
+                    // XML Prologue
+                    val end = html.indexOf('>', offset + 2)
+                    if (end == -1) {
+                        offset = length
+                        state = STATE_TEXT
+                    } else {
+                        offset = end + 1
+                        state = STATE_TEXT
+                    }
+                    continue@loop
+                }
+                state = STATE_IN_TAG
+                tagStart = offset
+            }
+
+            STATE_CLOSE_TAG -> {
+                if (c == '>') {
+                    state = STATE_TEXT
+                    if (html.startsWith("body", tagEndStart, true)) {
+                        val bodyEnd = tagEndStart - 2 // </
+                        if (bodyStart != -1) {
+                            return html.substring(bodyStart, bodyEnd)
+                        }
+                    }
+                    if (html.startsWith("html", tagEndStart, true)) {
+                        val htmlEnd = tagEndStart - 2
+                        if (htmlEnd != -1) {
+                            return html.substring(htmlStart, htmlEnd)
+                        }
+                    }
+                }
+                offset++
+            }
+
+            STATE_IN_TAG -> {
+                val whitespace = Character.isWhitespace(c)
+                if (whitespace || c == '>') {
+                    if (html.startsWith("body", tagStart, true)) {
+                        bodyStart = html.indexOf('>', offset) + 1
+                    }
+                    if (html.startsWith("html", tagStart, true)) {
+                        htmlStart = html.indexOf('>', offset) + 1
+                    }
+                }
+
+                when {
+                    whitespace -> state = STATE_BEFORE_ATTRIBUTE
+                    c == '>' -> {
+                        state = STATE_TEXT
+                    }
+                    c == '/' -> state = STATE_ENDING_TAG
+                }
+                offset++
+            }
+
+            STATE_ENDING_TAG -> {
+                if (c == '>') {
+                    if (html.startsWith("body", tagEndStart, true)) {
+                        val bodyEnd = tagEndStart - 1
+                        if (bodyStart != -1) {
+                            return html.substring(bodyStart, bodyEnd)
+                        }
+                    }
+                    if (html.startsWith("html", tagEndStart, true)) {
+                        val htmlEnd = tagEndStart - 1
+                        if (htmlEnd != -1) {
+                            return html.substring(htmlStart, htmlEnd)
+                        }
+                    }
+                    offset++
+                    state = STATE_TEXT
+                }
+            }
+
+            STATE_BEFORE_ATTRIBUTE -> {
+                if (c == '>') {
+                    state = STATE_TEXT
+                } else if (c == '/') {
+                    // we expect an '>' next to close the tag
+                } else if (!Character.isWhitespace(c)) {
+                    state = STATE_ATTRIBUTE_NAME
+                }
+                offset++
+            }
+            STATE_ATTRIBUTE_NAME -> {
+                when {
+                    c == '>' -> state = STATE_TEXT
+                    c == '=' -> state = STATE_ATTRIBUTE_AFTER_EQUALS
+                    Character.isWhitespace(c) -> state = STATE_ATTRIBUTE_BEFORE_EQUALS
+                    c == ':' -> {
+                    }
+                }
+                offset++
+            }
+            STATE_ATTRIBUTE_BEFORE_EQUALS -> {
+                if (c == '=') {
+                    state = STATE_ATTRIBUTE_AFTER_EQUALS
+                } else if (c == '>') {
+                    state = STATE_TEXT
+                } else if (!Character.isWhitespace(c)) {
+                    // Attribute value not specified (used for some boolean attributes)
+                    state = STATE_ATTRIBUTE_NAME
+                }
+                offset++
+            }
+
+            STATE_ATTRIBUTE_AFTER_EQUALS -> {
+                if (c == '\'') {
+                    // a='b'
+                    state = STATE_ATTRIBUTE_VALUE_SINGLE
+                } else if (c == '"') {
+                    // a="b"
+                    state = STATE_ATTRIBUTE_VALUE_DOUBLE
+                } else if (!Character.isWhitespace(c)) {
+                    // a=b
+                    state = STATE_ATTRIBUTE_VALUE_NONE
+                }
+                offset++
+            }
+
+            STATE_ATTRIBUTE_VALUE_SINGLE -> {
+                if (c == '\'') {
+                    state = STATE_BEFORE_ATTRIBUTE
+                }
+                offset++
+            }
+            STATE_ATTRIBUTE_VALUE_DOUBLE -> {
+                if (c == '"') {
+                    state = STATE_BEFORE_ATTRIBUTE
+                }
+                offset++
+            }
+            STATE_ATTRIBUTE_VALUE_NONE -> {
+                if (c == '>') {
+                    state = STATE_TEXT
+                } else if (Character.isWhitespace(c)) {
+                    state = STATE_BEFORE_ATTRIBUTE
+                }
+                offset++
+            }
+            else -> assert(false) { state }
+        }
+    }
+
+    return html
+}
+
+fun containsLinkTags(documentation: String): Boolean {
+    var index = 0
+    while (true) {
+        index = documentation.indexOf('@', index)
+        if (index == -1) {
+            return false
+        }
+        if (!documentation.startsWith("@code", index) &&
+            !documentation.startsWith("@literal", index) &&
+            !documentation.startsWith("@param", index) &&
+            !documentation.startsWith("@deprecated", index) &&
+            !documentation.startsWith("@inheritDoc", index) &&
+            !documentation.startsWith("@return", index)
+        ) {
+            return true
+        }
+
+        index++
+    }
+}
+
+// ------------------------------------------------------------------------------------
+// Expanding javadocs into fully qualified documentation
+// ------------------------------------------------------------------------------------
+
+fun toFullyQualifiedDocumentation(owner: PsiItem, documentation: String): String {
+    if (documentation.isBlank() || !containsLinkTags(documentation)) {
+        return documentation
+    }
+
+    val codebase = owner.codebase
+    val comment =
+        try {
+            codebase.getComment(documentation, owner.psi())
+        } catch (throwable: Throwable) {
+            // TODO: Get rid of line comments as documentation
+            // Invalid comment
+            if (documentation.startsWith("//") && documentation.contains("/**")) {
+                return toFullyQualifiedDocumentation(owner, documentation.substring(documentation.indexOf("/**")))
+            }
+            codebase.getComment(documentation, owner.psi())
+        }
+    val sb = StringBuilder(documentation.length)
+    expand(owner, comment, sb)
+
+    return sb.toString()
+}
+
+private fun reportUnresolvedDocReference(owner: Item, unresolved: String) {
+    @Suppress("ConstantConditionIf")
+    if (!REPORT_UNRESOLVED_SYMBOLS) {
+        return
+    }
+
+    if (unresolved.startsWith("{@") && !unresolved.startsWith("{@link")) {
+        return
+    }
+
+    // References are sometimes split across lines and therefore have newlines, leading asterisks
+    // etc in the middle: clean this up before emitting reference into error message
+    val cleaned = unresolved.replace("\n", "").replace("*", "")
+        .replace("  ", " ")
+
+    reporter.report(Errors.UNRESOLVED_LINK, owner, "Unresolved documentation reference: $cleaned")
+}
+
+private fun expand(owner: PsiItem, element: PsiElement, sb: StringBuilder) {
+    when {
+        element is PsiWhiteSpace -> {
+            sb.append(element.text)
+        }
+        element is PsiDocToken -> {
+            assert(element.firstChild == null)
+            val text = element.text
+            // Auto-fix some docs in the framework which starts with R.styleable in @attr
+            if (text.startsWith("R.styleable#") && owner.documentation.contains("@attr")) {
+                sb.append("android.")
+            }
+
+            sb.append(text)
+        }
+        element is PsiDocMethodOrFieldRef -> {
+            val text = element.text
+            var resolved = element.reference?.resolve()
+
+            // Workaround: relative references doesn't work from a class item to its members
+            if (resolved == null && owner is ClassItem) {
+                // For some reason, resolving relative methods and field references at the root
+                // level isn't working right.
+                if (PREPEND_LOCAL_CLASS && text.startsWith("#")) {
+                    var end = text.indexOf('(')
+                    if (end == -1) {
+                        // definitely a field
+                        end = text.length
+                        val fieldName = text.substring(1, end)
+                        val field = owner.findField(fieldName)
+                        if (field != null) {
+                            resolved = field.psi()
+                        }
+                    }
+                    if (resolved == null) {
+                        val methodName = text.substring(1, end)
+                        resolved = (owner.psi() as PsiClass).findMethodsByName(methodName, true).firstOrNull()
+                    }
+                }
+            }
+
+            if (resolved is PsiMember) {
+                val containingClass = resolved.containingClass
+                if (containingClass != null && !samePackage(owner, containingClass)) {
+                    val referenceText = element.reference?.element?.text ?: text
+                    if (!PREPEND_LOCAL_CLASS && referenceText.startsWith("#")) {
+                        sb.append(text)
+                        return
+                    }
+
+                    var className = containingClass.qualifiedName
+
+                    if (element.firstChildNode.elementType === JavaDocElementType.DOC_REFERENCE_HOLDER) {
+                        val firstChildPsi =
+                            SourceTreeToPsiMap.treeElementToPsi(element.firstChildNode.firstChildNode)
+                        if (firstChildPsi is PsiJavaCodeReferenceElement) {
+                            val referenceElement = firstChildPsi as PsiJavaCodeReferenceElement?
+                            val referencedElement = referenceElement!!.resolve()
+                            if (referencedElement is PsiClass) {
+                                className = referencedElement.qualifiedName
+                            }
+                        }
+                    }
+
+                    sb.append(className)
+                    sb.append('#')
+                    sb.append(resolved.name)
+                    val index = text.indexOf('(')
+                    if (index != -1) {
+                        sb.append(text.substring(index))
+                    }
+                } else {
+                    sb.append(text)
+                }
+            } else {
+                if (resolved == null) {
+                    val referenceText = element.reference?.element?.text ?: text
+                    if (text.startsWith("#") && owner is ClassItem) {
+                        // Unfortunately resolving references is broken from class javadocs
+                        // to members using just a relative reference, #.
+                    } else {
+                        reportUnresolvedDocReference(owner, referenceText)
+                    }
+                }
+                sb.append(text)
+            }
+        }
+        element is PsiJavaCodeReferenceElement -> {
+            val resolved = element.resolve()
+            if (resolved is PsiClass) {
+                if (samePackage(owner, resolved) || resolved is PsiTypeParameter) {
+                    sb.append(element.text)
+                } else {
+                    sb.append(resolved.qualifiedName)
+                }
+            } else if (resolved is PsiMember) {
+                val text = element.text
+                sb.append(resolved.containingClass?.qualifiedName)
+                sb.append('#')
+                sb.append(resolved.name)
+                val index = text.indexOf('(')
+                if (index != -1) {
+                    sb.append(text.substring(index))
+                }
+            } else {
+                val text = element.text
+                if (resolved == null) {
+                    reportUnresolvedDocReference(owner, text)
+                }
+                sb.append(text)
+            }
+        }
+        element is PsiInlineDocTag -> {
+            val handled = handleTag(element, owner, sb)
+            if (!handled) {
+                sb.append(element.text)
+            }
+        }
+        element.firstChild != null -> {
+            var curr = element.firstChild
+            while (curr != null) {
+                expand(owner, curr, sb)
+                curr = curr.nextSibling
+            }
+        }
+        else -> {
+            val text = element.text
+            sb.append(text)
+        }
+    }
+}
+
+fun handleTag(
+    element: PsiInlineDocTag,
+    owner: PsiItem,
+    sb: StringBuilder
+): Boolean {
+    val name = element.name
+    if (name == "code" || name == "literal") {
+        // @code: don't attempt to rewrite this
+        sb.append(element.text)
+        return true
+    }
+
+    val reference = extractReference(element)
+    val referenceText = reference?.element?.text ?: element.text
+    if (!PREPEND_LOCAL_CLASS && referenceText.startsWith("#")) {
+        val suffix = element.text
+        if (suffix.contains("(") && suffix.contains(")")) {
+            expandArgumentList(element, suffix, sb)
+        } else {
+            sb.append(suffix)
+        }
+        return true
+    }
+
+    // TODO: If referenceText is already absolute, e.g. android.Manifest.permission#BIND_CARRIER_SERVICES,
+    // try to short circuit this?
+
+    val valueElement = element.valueElement
+    if (valueElement is CompositePsiElement) {
+        if (valueElement.firstChildNode.elementType === JavaDocElementType.DOC_REFERENCE_HOLDER) {
+            val firstChildPsi =
+                SourceTreeToPsiMap.treeElementToPsi(valueElement.firstChildNode.firstChildNode)
+            if (firstChildPsi is PsiJavaCodeReferenceElement) {
+                val referenceElement = firstChildPsi as PsiJavaCodeReferenceElement?
+                val referencedElement = referenceElement!!.resolve()
+                if (referencedElement is PsiClass) {
+                    var className = PsiClassItem.computeFullClassName(referencedElement)
+                    if (className.indexOf('.') != -1 && !referenceText.startsWith(className)) {
+                        val simpleName = referencedElement.name
+                        if (simpleName != null && referenceText.startsWith(simpleName)) {
+                            className = simpleName
+                        }
+                    }
+                    if (referenceText.startsWith(className)) {
+                        sb.append("{@")
+                        sb.append(element.name)
+                        sb.append(' ')
+                        sb.append(referencedElement.qualifiedName)
+                        val suffix = referenceText.substring(className.length)
+                        if (suffix.contains("(") && suffix.contains(")")) {
+                            expandArgumentList(element, suffix, sb)
+                        } else {
+                            sb.append(suffix)
+                        }
+                        sb.append(' ')
+                        sb.append(referenceText)
+                        sb.append("}")
+                        return true
+                    }
+                }
+            }
+        }
+    }
+
+    var resolved = reference?.resolve()
+    if (resolved == null && owner is ClassItem) {
+        // For some reason, resolving relative methods and field references at the root
+        // level isn't working right.
+        if (PREPEND_LOCAL_CLASS && referenceText.startsWith("#")) {
+            var end = referenceText.indexOf('(')
+            if (end == -1) {
+                // definitely a field
+                end = referenceText.length
+                val fieldName = referenceText.substring(1, end)
+                val field = owner.findField(fieldName)
+                if (field != null) {
+                    resolved = field.psi()
+                }
+            }
+            if (resolved == null) {
+                val methodName = referenceText.substring(1, end)
+                resolved = (owner.psi() as PsiClass).findMethodsByName(methodName, true).firstOrNull()
+            }
+        }
+    }
+
+    if (resolved != null) {
+        when (resolved) {
+            is PsiClass -> {
+                val text = element.text
+                if (samePackage(owner, resolved)) {
+                    sb.append(text)
+                    return true
+                }
+                val qualifiedName = resolved.qualifiedName ?: run {
+                    sb.append(text)
+                    return true
+                }
+                if (referenceText == qualifiedName) {
+                    // Already absolute
+                    sb.append(text)
+                    return true
+                }
+                val append = when {
+                    valueElement != null -> {
+                        val start = valueElement.startOffsetInParent
+                        val end = start + valueElement.textLength
+                        text.substring(0, start) + qualifiedName + text.substring(end)
+                    }
+                    name == "see" -> {
+                        val suffix = text.substring(text.indexOf(referenceText) + referenceText.length)
+                        "@see $qualifiedName$suffix"
+                    }
+                    text.startsWith("{") -> "{@$name $qualifiedName $referenceText}"
+                    else -> "@$name $qualifiedName $referenceText"
+                }
+                sb.append(append)
+                return true
+            }
+            is PsiMember -> {
+                val text = element.text
+                val containing = resolved.containingClass ?: run {
+                    sb.append(text)
+                    return true
+                }
+                if (samePackage(owner, containing)) {
+                    sb.append(text)
+                    return true
+                }
+                val qualifiedName = containing.qualifiedName ?: run {
+                    sb.append(text)
+                    return true
+                }
+                if (referenceText.startsWith(qualifiedName)) {
+                    // Already absolute
+                    sb.append(text)
+                    return true
+                }
+
+                // It may also be the case that the reference is already fully qualified
+                // but to some different class. For example, the link may be to
+                // android.os.Bundle#getInt, but the resolved method actually points to
+                // an inherited method into android.os.Bundle from android.os.BaseBundle.
+                // In that case we don't want to rewrite the link.
+                for (index in 0 until referenceText.length) {
+                    val c = referenceText[index]
+                    if (c == '.') {
+                        // Already qualified
+                        sb.append(text)
+                        return true
+                    } else if (!Character.isJavaIdentifierPart(c)) {
+                        break
+                    }
+                }
+
+                if (valueElement != null) {
+                    val start = valueElement.startOffsetInParent
+
+                    var nameEnd = -1
+                    var close = start
+                    var balance = 0
+                    while (close < text.length) {
+                        val c = text[close]
+                        if (c == '(') {
+                            if (nameEnd == -1) {
+                                nameEnd = close
+                            }
+                            balance++
+                        } else if (c == ')') {
+                            balance--
+                            if (balance == 0) {
+                                close++
+                                break
+                            }
+                        } else if (c == '}') {
+                            if (nameEnd == -1) {
+                                nameEnd = close
+                            }
+                            break
+                        } else if (balance == 0 && c == '#') {
+                            if (nameEnd == -1) {
+                                nameEnd = close
+                            }
+                        } else if (balance == 0 && !Character.isJavaIdentifierPart(c)) {
+                            break
+                        }
+                        close++
+                    }
+                    val memberPart = text.substring(nameEnd, close)
+                    val append = "${text.substring(0, start)}$qualifiedName$memberPart $referenceText}"
+                    sb.append(append)
+                    return true
+                }
+            }
+        }
+    } else {
+        reportUnresolvedDocReference(owner, referenceText)
+    }
+
+    return false
+}
+
+private fun expandArgumentList(
+    element: PsiInlineDocTag,
+    suffix: String,
+    sb: StringBuilder
+) {
+    val elementFactory = JavaPsiFacade.getElementFactory(element.project)
+    // Try to rewrite the types to fully qualified names as well
+    val begin = suffix.indexOf('(')
+    sb.append(suffix.substring(0, begin + 1))
+    var index = begin + 1
+    var balance = 0
+    var argBegin = index
+    while (index < suffix.length) {
+        val c = suffix[index++]
+        if (c == '<' || c == '(') {
+            balance++
+        } else if (c == '>') {
+            balance--
+        } else if (c == ')' && balance == 0 || c == ',') {
+            // Strip off javadoc header
+            while (argBegin < index) {
+                val p = suffix[argBegin]
+                if (p != '*' && !p.isWhitespace()) {
+                    break
+                }
+                argBegin++
+            }
+            if (index > argBegin + 1) {
+                val arg = suffix.substring(argBegin, index - 1).trim()
+                val space = arg.indexOf(' ')
+                // Strip off parameter name (shouldn't be there but happens
+                // in some Android sources sine tools didn't use to complain
+                val typeString = if (space == -1) {
+                    arg
+                } else {
+                    if (space < arg.length - 1 && !arg[space + 1].isJavaIdentifierStart()) {
+                        // Example: "String []"
+                        arg
+                    } else {
+                        // Example "String name"
+                        arg.substring(0, space)
+                    }
+                }
+                var insert = arg
+                if (typeString[0].isUpperCase()) {
+                    try {
+                        val type = elementFactory.createTypeFromText(typeString, element)
+                        insert = type.canonicalText
+                    } catch (ignore: com.intellij.util.IncorrectOperationException) {
+                        // Not a valid type - just leave what was in the parameter text
+                    }
+                }
+                sb.append(insert)
+                sb.append(c)
+                if (c == ')') {
+                    break
+                }
+            } else if (c == ')') {
+                sb.append(')')
+                break
+            }
+            argBegin = index
+        } else if (c == ')') {
+            balance--
+        }
+    }
+    while (index < suffix.length) {
+        sb.append(suffix[index++])
+    }
+}
+
+private fun samePackage(owner: PsiItem, cls: PsiClass): Boolean {
+    @Suppress("ConstantConditionIf")
+    if (INCLUDE_SAME_PACKAGE) {
+        // doclava seems to have REAL problems with this
+        return false
+    }
+    val pkg = packageName(owner) ?: return false
+    return cls.qualifiedName == "$pkg.${cls.name}"
+}
+
+private fun packageName(owner: PsiItem): String? {
+    var curr: Item? = owner
+    while (curr != null) {
+        if (curr is PackageItem) {
+            return curr.qualifiedName()
+        }
+        curr = curr.parent()
+    }
+
+    return null
+}
+
+// Copied from UnnecessaryJavaDocLinkInspection and tweaked a bit
+private fun extractReference(tag: PsiDocTag): PsiReference? {
+    val valueElement = tag.valueElement
+    if (valueElement != null) {
+        return valueElement.reference
+    }
+    // hack around the fact that a reference to a class is apparently
+    // not a PsiDocTagValue
+    val dataElements = tag.dataElements
+    if (dataElements.isEmpty()) {
+        return null
+    }
+    val salientElement: PsiElement =
+        dataElements.firstOrNull { it !is PsiWhiteSpace && it !is PsiDocToken } ?: return null
+    val child = salientElement.firstChild
+    return if (child !is PsiReference) null else child
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
index 1aaca8e..7a18e22 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
@@ -26,9 +26,10 @@
 import com.android.tools.metalava.model.AnnotationSingleAttributeValue
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.DefaultAnnotationItem
 import com.android.tools.metalava.model.Item
-import com.android.tools.metalava.model.canonicalizeFloatingPointString
-import com.android.tools.metalava.model.javaEscapeString
+import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToExpression
+import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToSource
 import com.intellij.psi.PsiAnnotation
 import com.intellij.psi.PsiAnnotationMemberValue
 import com.intellij.psi.PsiArrayInitializerMemberValue
@@ -45,7 +46,7 @@
 class PsiAnnotationItem private constructor(
     override val codebase: PsiBasedCodebase,
     val psiAnnotation: PsiAnnotation
-) : AnnotationItem {
+) : DefaultAnnotationItem(codebase) {
     private var attributes: List<AnnotationAttribute>? = null
 
     override fun toString(): String = toSource()
@@ -99,7 +100,7 @@
         //  @android.support.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
         when (value) {
             null -> sb.append("null")
-            is PsiLiteral -> sb.append(literalToString(value.value))
+            is PsiLiteral -> sb.append(constantToSource(value.value))
             is PsiReference -> {
                 val resolved = value.resolve()
                 when (resolved) {
@@ -182,92 +183,7 @@
 
     private fun getConstantSource(value: PsiExpression): String? {
         val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
-        return when (constant) {
-            is Int -> "0x${Integer.toHexString(constant)}"
-            is String -> "\"${javaEscapeString(constant)}\""
-            is Long -> "${constant}L"
-            is Boolean -> constant.toString()
-            is Byte -> Integer.toHexString(constant.toInt())
-            is Short -> Integer.toHexString(constant.toInt())
-            is Float -> {
-                when (constant) {
-                    Float.POSITIVE_INFINITY -> "Float.POSITIVE_INFINITY"
-                    Float.NEGATIVE_INFINITY -> "Float.NEGATIVE_INFINITY"
-                    Float.NaN -> "Float.NaN"
-                    else -> {
-                        "${canonicalizeFloatingPointString(constant.toString())}F"
-                    }
-                }
-            }
-            is Double -> {
-                when (constant) {
-                    Double.POSITIVE_INFINITY -> "Double.POSITIVE_INFINITY"
-                    Double.NEGATIVE_INFINITY -> "Double.NEGATIVE_INFINITY"
-                    Double.NaN -> "Double.NaN"
-                    else -> {
-                        canonicalizeFloatingPointString(constant.toString())
-                    }
-                }
-            }
-            is Char -> {
-                "'${javaEscapeString(constant.toString())}'"
-            }
-            else -> {
-                null
-            }
-        }
-    }
-
-    private fun literalToString(value: Any?): String {
-        if (value == null) {
-            return "null"
-        }
-
-        when (value) {
-            is Int -> {
-                return value.toString()
-            }
-            is String -> {
-                return "\"${javaEscapeString(value)}\""
-            }
-            is Long -> {
-                return value.toString() + "L"
-            }
-            is Boolean -> {
-                return value.toString()
-            }
-            is Byte -> {
-                return Integer.toHexString(value.toInt())
-            }
-            is Short -> {
-                return Integer.toHexString(value.toInt())
-            }
-            is Float -> {
-                return when (value) {
-                    Float.POSITIVE_INFINITY -> "(1.0f/0.0f)"
-                    Float.NEGATIVE_INFINITY -> "(-1.0f/0.0f)"
-                    Float.NaN -> "(0.0f/0.0f)"
-                    else -> {
-                        canonicalizeFloatingPointString(value.toString()) + "f"
-                    }
-                }
-            }
-            is Double -> {
-                return when (value) {
-                    Double.POSITIVE_INFINITY -> "(1.0/0.0)"
-                    Double.NEGATIVE_INFINITY -> "(-1.0/0.0)"
-                    Double.NaN -> "(0.0/0.0)"
-                    else -> {
-                        canonicalizeFloatingPointString(value.toString())
-                    }
-                }
-            }
-            is Char -> {
-                return String.format("'%s'", javaEscapeString(value.toString()))
-            }
-        }
-
-        return value.toString()
+        return constantToExpression(constant)
     }
 
     override fun qualifiedName() = AnnotationItem.mapName(codebase, psiAnnotation.qualifiedName)
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 b8b2504..0ac0887 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
@@ -20,9 +20,7 @@
 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.Codebase
 import com.android.tools.metalava.model.DefaultCodebase
-import com.android.tools.metalava.model.FieldItem
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.PackageDocs
@@ -32,9 +30,8 @@
 import com.android.tools.metalava.options
 import com.android.tools.metalava.reporter
 import com.android.tools.metalava.tick
-import com.google.common.collect.BiMap
-import com.google.common.collect.HashBiMap
 import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.Disposer
 import com.intellij.psi.JavaPsiFacade
 import com.intellij.psi.PsiAnnotation
 import com.intellij.psi.PsiArrayType
@@ -53,26 +50,26 @@
 import com.intellij.psi.javadoc.PsiDocTag
 import com.intellij.psi.search.GlobalSearchScope
 import com.intellij.psi.util.PsiTreeUtil
-import org.intellij.lang.annotations.Language
+import org.jetbrains.kotlin.resolve.BindingContext
 import org.jetbrains.uast.UFile
 import org.jetbrains.uast.UastContext
 import java.io.File
 import java.io.IOException
-import java.util.ArrayDeque
 import java.util.ArrayList
 import java.util.HashMap
-import java.util.function.Predicate
 import java.util.zip.ZipFile
 
 const val PACKAGE_ESTIMATE = 400
 const val CLASS_ESTIMATE = 12000
 const val METHOD_ESTIMATE = 1000
 
-open class PsiBasedCodebase(override var description: String = "Unknown") : DefaultCodebase() {
+open class PsiBasedCodebase(location: File, override var description: String = "Unknown") : DefaultCodebase(location) {
     lateinit var project: Project
 
+    var bindingContext: BindingContext? = null
+
     /** Map from class name to class item */
-    protected val classMap: MutableMap<String, PsiClassItem> = HashMap(CLASS_ESTIMATE)
+    private val classMap: MutableMap<String, PsiClassItem> = HashMap(CLASS_ESTIMATE)
 
     /** Map from psi type to type item */
     private val typeMap: MutableMap<PsiType, TypeItem> = HashMap(400)
@@ -92,6 +89,12 @@
     /** A set of packages to hide */
     private lateinit var hiddenPackages: MutableMap<String, Boolean?>
 
+    /**
+     * A list of the top-level classes declared in the codebase's source (rather than on its
+     * classpath).
+     */
+    private lateinit var topLevelClassesFromSource: MutableList<ClassItem>
+
     private var initializing = false
 
     override fun trustedApi(): Boolean = false
@@ -120,11 +123,11 @@
         packageClasses = HashMap(PACKAGE_ESTIMATE)
         packageClasses[""] = ArrayList()
         this.methodMap = HashMap(METHOD_ESTIMATE)
+        topLevelClassesFromSource = ArrayList(CLASS_ESTIMATE)
 
         for (unit in units) {
             tick() // show progress
 
-            val topLevelClasses = mutableListOf<ClassItem>()
             var classes = (unit as? PsiClassOwner)?.classes?.toList() ?: emptyList()
             if (classes.isEmpty()) {
                 val uastContext = project.getComponent(UastContext::class.java)
@@ -133,6 +136,7 @@
             }
             var packageName: String? = null
             if (classes.isEmpty() && unit is PsiJavaFile) {
+                // package-info.java ?
                 val packageStatement = unit.packageStatement
                 // Look for javadoc on the package statement; this is NOT handed to us on
                 // the PsiPackage!
@@ -144,13 +148,21 @@
                         if (text.contains("@hide")) {
                             hiddenPackages.add(packageName)
                         }
-                        packageDocs[packageName] = text + (packageDocs[packageName] ?: "")
+                        if (packageDocs[packageName] != null) {
+                            reporter.report(
+                                Errors.BOTH_PACKAGE_INFO_AND_HTML,
+                                unit,
+                                "It is illegal to provide both a package-info.java file and a " +
+                                    "package.html file for the same package"
+                            )
+                        }
+                        packageDocs[packageName] = text
                     }
                 }
             } else {
                 for (psiClass in classes) {
                     val classItem = createClass(psiClass)
-                    topLevelClasses.add(classItem)
+                    topLevelClassesFromSource.add(classItem)
 
                     if (packageName == null) {
                         packageName = getPackageName(psiClass)
@@ -169,8 +181,7 @@
             }
 
             val sortedClasses = classes.toMutableList().sortedWith(ClassItem.fullNameComparator)
-            val packageHtml = packageDocs[pkgName]
-            registerPackage(psiPackage, sortedClasses, packageHtml, pkgName)
+            registerPackage(psiPackage, sortedClasses, packageDocs[pkgName], pkgName)
         }
 
         initializing = false
@@ -199,6 +210,11 @@
         addParentPackages(packageMap.values)
     }
 
+    override fun dispose() {
+        Disposer.dispose(project)
+        super.dispose()
+    }
+
     private fun addParentPackages(packages: Collection<PsiPackageItem>) {
         val missingPackages = packages.mapNotNull {
             val name = it.qualifiedName()
@@ -251,10 +267,7 @@
         packageHtml: String?,
         pkgName: String
     ): PsiPackageItem {
-        val packageItem = PsiPackageItem.create(
-            this, psiPackage,
-            packageHtml
-        )
+        val packageItem = PsiPackageItem.create(this, psiPackage, packageHtml)
         packageMap[pkgName] = packageItem
         if (isPackageHidden(pkgName)) {
             packageItem.hidden = true
@@ -264,7 +277,8 @@
         return packageItem
     }
 
-    fun initialize(project: Project, jarFile: File) {
+    fun initialize(project: Project, jarFile: File, preFiltered: Boolean = false) {
+        this.preFiltered = preFiltered
         initializing = true
         hideClassesFromJars = false
 
@@ -284,7 +298,7 @@
         )
         packageToClasses[""] = ArrayList() // ensure we construct one for the default package
 
-        val topLevelClasses = ArrayList<ClassItem>(CLASS_ESTIMATE)
+        topLevelClassesFromSource = ArrayList(CLASS_ESTIMATE)
 
         try {
             ZipFile(jarFile).use { jar ->
@@ -311,7 +325,7 @@
                             val psiClass = facade.findClass(qualifiedName, scope) ?: continue
 
                             val classItem = createClass(psiClass)
-                            topLevelClasses.add(classItem)
+                            topLevelClassesFromSource.add(classItem)
 
                             val packageName = getPackageName(psiClass)
                             var list = packageToClasses[packageName]
@@ -474,7 +488,7 @@
 
     override fun getPackages(): PackageList {
         // TODO: Sorting is probably not necessary here!
-        return PackageList(packageMap.values.toMutableList().sortedWith(PackageItem.comparator))
+        return PackageList(this, packageMap.values.toMutableList().sortedWith(PackageItem.comparator))
     }
 
     override fun getPackageDocs(): PackageDocs? {
@@ -626,6 +640,14 @@
         }
     }
 
+    /**
+     * Returns a list of the top-level classes declared in the codebase's source (rather than on its
+     * classpath).
+     */
+    fun getTopLevelClassesFromSource(): List<ClassItem> {
+        return topLevelClassesFromSource
+    }
+
     fun createReferenceFromText(s: String, parent: PsiElement? = null): PsiJavaCodeReferenceElement =
         getFactory().createReferenceFromText(s, parent)
 
@@ -646,7 +668,7 @@
     private fun getFactory() = JavaPsiFacade.getElementFactory(project)
 
     override fun createAnnotation(
-        @Language("JAVA") source: String,
+        source: String,
         context: Item?,
         mapName: Boolean
     ): PsiAnnotationItem {
@@ -658,340 +680,9 @@
 
     override fun toString(): String = description
 
-    override fun filter(filterEmit: Predicate<Item>, filterReference: Predicate<Item>): Codebase =
-        filter(this, filterEmit, filterReference)
-
-    companion object {
-        // This is on the companion object rather than as an instance method to make sure
-        // we don't accidentally call self-methods on the old codebase when we meant the
-        // new: this forces us to be explicit about which codebase we mean
-        fun filter(
-            oldCodebase: PsiBasedCodebase,
-            filterEmit: Predicate<Item>,
-            filterReference: Predicate<Item>
-        ): Codebase {
-            val newCodebase = LockedPsiBasedCodebase("Filtered ${oldCodebase.description}") as PsiBasedCodebase
-            with(newCodebase) {
-                project = oldCodebase.project
-                hiddenPackages = HashMap(oldCodebase.hiddenPackages)
-                packageMap = HashMap(PACKAGE_ESTIMATE)
-                packageClasses = HashMap(PACKAGE_ESTIMATE)
-                packageClasses[""] = ArrayList()
-                methodMap = HashMap(METHOD_ESTIMATE)
-                initializing = true
-                original = oldCodebase
-                units = oldCodebase.units
-            }
-
-            val oldToNew: BiMap<PsiClassItem, PsiClassItem> = HashBiMap.create(30000)
-
-            val newPackages = mutableListOf<PsiPackageItem>()
-
-            val oldPackages = oldCodebase.packageMap.values
-            for (pkg in oldPackages) {
-                if (pkg.hidden) {
-                    continue
-                }
-                var currentPackage: PsiPackageItem? = null
-
-                for (cls in pkg.topLevelClasses()) {
-                    if (cls.isFromClassPath()) {
-                        continue
-                    }
-                    val classFilter = FilteredClassView(cls as PsiClassItem, filterEmit, filterReference)
-                    if (classFilter.emit()) {
-                        val newPackage = currentPackage ?: run {
-                            val newPackage = PsiPackageItem.create(newCodebase, pkg)
-                            currentPackage = newPackage
-                            newPackages.add(newPackage)
-                            newCodebase.packageMap[newPackage.qualifiedName()] = newPackage
-                            newPackage
-                        }
-
-                        // Bottom-up copy
-                        val newClass = classFilter.create(newCodebase)
-
-                        // Register it and all inner classes in the class map
-                        for (c in newClass.allInnerClasses(includeSelf = true)) {
-                            newCodebase.classMap[c.qualifiedName()] = c as PsiClassItem
-                        }
-
-                        newPackage.addClass(newClass) // (inner classes are not registered in the package)
-
-                        oldToNew[cls] = newClass
-                        newClass.containingPackage = newPackage
-                    }
-                }
-            }
-
-            // Initialize super classes and super methods
-            for (cls in newCodebase.classMap.values) {
-                val originalClass = cls.source!! // should be set here during construction
-                val prevSuperClass = originalClass.filteredSuperClassType(filterReference)
-                val curr = prevSuperClass?.asClass()
-                if (curr != null) {
-                    val superClassName = curr.qualifiedName()
-                    val publicSuperClass: PsiClassItem? = newCodebase.classMap[superClassName]
-                    cls.setSuperClass(
-                        if (publicSuperClass == null) {
-                            if (curr.isFromClassPath() && options.allowReferencingUnknownClasses) {
-                                curr
-                            } else {
-                                reporter.report(
-                                    Errors.HIDDEN_SUPERCLASS, originalClass.psiClass,
-                                    "$cls has a super class " +
-                                        "that is excluded via filters: $superClassName"
-                                )
-                                null
-                            }
-                        } else {
-                            newCodebase.classMap[superClassName] = publicSuperClass
-                            publicSuperClass
-                        },
-                        PsiTypeItem.create(newCodebase, prevSuperClass as PsiTypeItem)
-                    )
-                } else {
-                    // typically java.lang.Object
-                    cls.setSuperClass(null, null)
-                }
-
-                val psiClass = cls.psiClass
-
-                val filtered = originalClass.filteredInterfaceTypes(filterReference)
-                if (filtered.isEmpty()) {
-                    cls.setInterfaces(emptyList())
-                } else {
-                    val interfaceTypeList = mutableListOf<PsiTypeItem>()
-                    for (type in filtered) {
-                        interfaceTypeList.add(PsiTypeItem.create(newCodebase, type as PsiTypeItem))
-                    }
-                    cls.setInterfaces(interfaceTypeList)
-                }
-
-                val oldCls = oldCodebase.findOrCreateClass(cls.psiClass)
-                val oldDefaultConstructor = oldCls.defaultConstructor
-                if (oldDefaultConstructor != null) {
-                    val newConstructor = cls.findConstructor(oldDefaultConstructor) as PsiConstructorItem?
-                    if (newConstructor != null) {
-                        cls.defaultConstructor = newConstructor
-                    } else {
-                        // Constructor picked before that isn't available here: recreate it
-                        val recreated = cls.createDefaultConstructor()
-
-                        recreated.mutableModifiers().setPackagePrivate(true)
-                        cls.defaultConstructor = recreated
-                    }
-                }
-
-                val constructors = cls.constructors().asSequence()
-                val methods = cls.methods().asSequence()
-                val allMethods = methods.plus(constructors)
-
-                // Super constructors
-                for (method in constructors) {
-                    val original = method.source as PsiConstructorItem
-
-                    val originalSuperConstructor = original.superConstructor
-                    val superConstructor =
-                        if (originalSuperConstructor != null && filterReference.test(originalSuperConstructor)) {
-                            originalSuperConstructor
-                        } else {
-                            original.findDelegate(filterReference, true)
-                        }
-
-                    if (superConstructor != null) {
-                        val superConstructorClass =
-                            newCodebase.classMap[superConstructor.containingClass().qualifiedName()]
-                        if (superConstructorClass == null) {
-                            // This class is not in the filtered codebase
-                            if (superConstructor.isFromClassPath() && options.allowReferencingUnknownClasses) {
-                                method.superConstructor = superConstructor
-                                method.setSuperMethods(listOf(superConstructor))
-                            } else {
-                                reporter.report(
-                                    Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " +
-                                        "in a class that is excluded via filters: " +
-                                        "${superConstructor.containingClass().qualifiedName()} "
-                                )
-                            }
-                        } else {
-                            // Find corresponding super method
-                            val newSuperConstructor = superConstructorClass.findMethod(superConstructor)
-                            if (newSuperConstructor == null) {
-                                reporter.report(
-                                    Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " +
-                                        "in a class that is not matched via filters: " +
-                                        "${superConstructor.containingClass().qualifiedName()} "
-                                )
-                            } else {
-                                val constructorItem = newSuperConstructor as PsiConstructorItem
-                                method.superConstructor = constructorItem
-                                method.setSuperMethods(listOf(constructorItem))
-                            }
-                        }
-                    } else {
-                        method.setSuperMethods(emptyList())
-                    }
-                }
-
-                // Super methods
-                for (method in methods) {
-                    val original = method.source!! // should be set here
-                    val list = mutableListOf<MethodItem>()
-                    val superMethods = ArrayDeque<MethodItem>()
-                    superMethods.addAll(original.superMethods())
-                    while (!superMethods.isEmpty()) {
-                        val superMethod = superMethods.removeFirst()
-                        if (filterReference.test(superMethod)) {
-                            // Find corresponding method in the new filtered codebase
-                            val superMethodClass = newCodebase.classMap[superMethod.containingClass().qualifiedName()]
-                            if (superMethodClass == null) {
-                                // This class is not in the filtered codebase
-                                if (superMethod.isFromClassPath() && options.allowReferencingUnknownClasses) {
-                                    list.add(superMethod)
-                                } else {
-                                    reporter.report(
-                                        Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " +
-                                            "in a class that is excluded via filters: " +
-                                            "${superMethod.containingClass().qualifiedName()} "
-                                    )
-                                }
-                            } else {
-                                // Find corresponding super method
-                                val newSuperMethod = superMethodClass.findMethod(superMethod)
-                                if (newSuperMethod == null) {
-                                    reporter.report(
-                                        Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " +
-                                            "in a class that is not matched via filters: " +
-                                            "${superMethod.containingClass().qualifiedName()} "
-                                    )
-                                } else {
-                                    list.add(newSuperMethod)
-                                }
-                            }
-                        } else {
-                            // Process its parents instead
-                            superMethods.addAll(superMethod.superMethods())
-                        }
-                    }
-                    method.setSuperMethods(list)
-                }
-
-                // Methods and constructors: initialize throws lists
-                for (method in allMethods) {
-                    val original = method.source!! // should be set here
-
-                    val throwsTypes: List<PsiClassItem> = if (original.throwsTypes().isNotEmpty()) {
-                        val list = ArrayList<PsiClassItem>()
-
-                        original.filteredThrowsTypes(filterReference).forEach {
-                            val newCls = newCodebase.classMap[it.qualifiedName()]
-                            if (newCls == null) {
-                                if (it.isFromClassPath() && options.allowReferencingUnknownClasses) {
-                                    list.add(it as PsiClassItem)
-                                } else {
-                                    reporter.report(
-                                        Errors.HIDDEN_SUPERCLASS, psiClass, "$newCls has a throws class " +
-                                            "that is excluded via filters: ${it.qualifiedName()}"
-                                    )
-                                }
-                            } else {
-                                list.add(newCls)
-                            }
-                        }
-                        list
-                    } else {
-                        emptyList()
-                    }
-                    method.setThrowsTypes(throwsTypes)
-
-                    method.source = null
-                }
-
-                cls.source = null
-            }
-
-            val pkg: PsiPackageItem? = newCodebase.findPackage("") ?: run {
-                val psiPackage = JavaPsiFacade.getInstance(newCodebase.project).findPackage("")
-                if (psiPackage != null) {
-                    PsiPackageItem.create(newCodebase, psiPackage, null)
-                } else {
-                    null
-                }
-            }
-            pkg?.let {
-                newCodebase.emptyPackage = it
-                newCodebase.packageMap[""] = it
-            }
-
-            newCodebase.addParentPackages(newCodebase.packageMap.values)
-
-            newCodebase.initializing = false
-
-            return newCodebase
-        }
-    }
-
     fun registerClass(cls: PsiClassItem) {
         assert(classMap[cls.qualifiedName()] == null || classMap[cls.qualifiedName()] == cls)
 
         classMap[cls.qualifiedName()] = cls
     }
 }
-
-class FilteredClassView(
-    val cls: PsiClassItem,
-    private val filterEmit: Predicate<Item>,
-    private val filterReference: Predicate<Item>
-) {
-    val innerClasses: Sequence<FilteredClassView>
-    val constructors: Sequence<MethodItem>
-    val methods: Sequence<MethodItem>
-    val fields: Sequence<FieldItem>
-
-    init {
-        constructors = cls.constructors().asSequence().filter { filterEmit.test(it) }
-        methods = cls.methods().asSequence().filter { filterEmit.test(it) }
-        // fields = cls.fields().asSequence().filter { filterEmit.test(it) }
-
-        fields = cls.filteredFields(filterEmit).asSequence()
-        innerClasses = cls.innerClasses()
-            .asSequence()
-            .filterIsInstance(PsiClassItem::class.java)
-            .map { FilteredClassView(it, filterEmit, filterReference) }
-    }
-
-    fun create(codebase: PsiBasedCodebase): PsiClassItem {
-        return PsiClassItem.create(codebase, this)
-    }
-
-    /** Will this class emit anything? */
-    fun emit(): Boolean {
-        val emit = emitClass()
-        if (emit) {
-            return true
-        }
-
-        return innerClasses.any { it.emit() }
-    }
-
-    /** Does the body of this class (everything other than the inner classes) emit anything? */
-    private fun emitClass(): Boolean {
-        val classEmpty = (constructors.none() && methods.none() && fields.none())
-        return if (filterEmit.test(cls)) {
-            true
-        } else if (!classEmpty) {
-            filterReference.test(cls)
-        } else {
-            false
-        }
-    }
-}
-
-class LockedPsiBasedCodebase(description: String = "Unknown") : PsiBasedCodebase(description) {
-    // Not yet locked
-    // override fun findClass(psiClass: PsiClass): PsiClassItem {
-    //    val qualifiedName: String = psiClass.qualifiedName ?: psiClass.name!!
-    //    return classMap[qualifiedName] ?: error("Attempted to register ${psiClass.name} in locked codebase")
-    // }
-}
\ No newline at end of file
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 b1cfb48..2c2b15a 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
@@ -17,14 +17,17 @@
 package com.android.tools.metalava.model.psi
 
 import com.android.tools.metalava.compatibility
+import com.android.tools.metalava.model.AnnotationRetention
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.CompilationUnit
 import com.android.tools.metalava.model.ConstructorItem
 import com.android.tools.metalava.model.FieldItem
 import com.android.tools.metalava.model.MethodItem
 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
@@ -35,6 +38,8 @@
 import com.intellij.psi.PsiTypeParameter
 import com.intellij.psi.impl.source.PsiClassReferenceType
 import com.intellij.psi.util.PsiUtil
+import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.uast.UMethod
 
 open class PsiClassItem(
     override val codebase: PsiBasedCodebase,
@@ -79,9 +84,6 @@
 
     private var containingClass: PsiClassItem? = null
     override fun containingClass(): PsiClassItem? = containingClass
-    fun setContainingClass(containingClass: ClassItem?) {
-        this.containingClass = containingClass as PsiClassItem?
-    }
 
     // TODO: Come up with a better scheme for how to compute this
     override var included: Boolean = true
@@ -95,7 +97,7 @@
         setInterfaces(interfaceTypes as List<PsiTypeItem>)
     }
 
-    fun setInterfaces(interfaceTypes: List<PsiTypeItem>) {
+    private fun setInterfaces(interfaceTypes: List<PsiTypeItem>) {
         this.interfaceTypes = interfaceTypes
     }
 
@@ -141,6 +143,7 @@
     private lateinit var interfaceTypes: List<TypeItem>
     private lateinit var constructors: List<PsiConstructorItem>
     private lateinit var methods: List<PsiMethodItem>
+    private lateinit var properties: List<PsiPropertyItem>
     private lateinit var fields: List<FieldItem>
 
     /**
@@ -154,6 +157,7 @@
     override fun innerClasses(): List<PsiClassItem> = innerClasses
     override fun constructors(): List<PsiConstructorItem> = constructors
     override fun methods(): List<PsiMethodItem> = methods
+    override fun properties(): List<PropertyItem> = properties
     override fun fields(): List<FieldItem> = fields
 
     override fun toType(): TypeItem {
@@ -213,6 +217,16 @@
             inner.finishInitialization()
         }
 
+        // Delay initializing super classes and implemented interfaces for all inner classes: they may refer
+        // to *other* inner classes in this class, which would lead to an attempt to construct
+        // recursively. Instead, we wait until all the inner classes have been constructed, and at
+        // the very end, initialize super classes and interfaces recursively.
+        if (psiClass.containingClass == null) {
+            initializeSuperClasses()
+        }
+    }
+
+    private fun initializeSuperClasses() {
         val extendsListTypes = psiClass.extendsListTypes
         if (!extendsListTypes.isEmpty()) {
             val type = PsiTypeItem.create(codebase, extendsListTypes[0])
@@ -242,6 +256,10 @@
             interfaces.mapTo(result) { create(it) }
             result
         })
+
+        for (inner in innerClasses) {
+            inner.initializeSuperClasses()
+        }
     }
 
     protected fun initialize(
@@ -258,7 +276,7 @@
         this.fields = fields
     }
 
-    override fun mapTypeVariables(target: ClassItem, reverse: Boolean): Map<String, String> {
+    override fun mapTypeVariables(target: ClassItem): Map<String, String> {
         val targetPsi = target.psi() as PsiClass
         val maps = mapTypeVariablesToSuperclass(
             psiClass, targetPsi, considerSuperClasses = true,
@@ -307,8 +325,7 @@
     override fun createMethod(template: MethodItem): MethodItem {
         val method = template as PsiMethodItem
 
-        val replacementMap = mapTypeVariables(template.containingClass(), reverse = true)
-
+        val replacementMap = mapTypeVariables(template.containingClass())
         val newMethod: PsiMethodItem
         if (replacementMap.isEmpty()) {
             newMethod = PsiMethodItem.create(codebase, this, method)
@@ -341,6 +358,19 @@
         (methods as MutableList<PsiMethodItem>).add(method as PsiMethodItem)
     }
 
+    private var retention: AnnotationRetention? = null
+
+    override fun getRetention(): AnnotationRetention {
+        retention?.let { return it }
+
+        if (!isAnnotationType()) {
+            error("getRetention() should only be called on annotation classes")
+        }
+
+        retention = ClassItem.findRetention(this)
+        return retention!!
+    }
+
     override fun hashCode(): Int = qualifiedName.hashCode()
 
     override fun toString(): String = "class ${qualifiedName()}"
@@ -378,6 +408,16 @@
 
             if (classType == ClassType.ENUM) {
                 addEnumMethods(codebase, item, psiClass, methods)
+            } else if (classType == ClassType.ANNOTATION_TYPE && !options.compatOutput &&
+                modifiers.findAnnotation("java.lang.annotation.Retention") == null
+            ) {
+                // By policy, include explicit retention policy annotation if missing
+                modifiers.addAnnotation(
+                    codebase.createAnnotation(
+                        "@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)",
+                        item, false
+                    )
+                )
             }
 
             val constructors: MutableList<PsiConstructorItem> = ArrayList(5)
@@ -427,6 +467,30 @@
             item.methods = methods
             item.fields = fields
 
+            item.properties = emptyList()
+            if (isKotlin(psiClass)) {
+                // Try to initialize the Kotlin properties
+                val properties = mutableListOf<PsiPropertyItem>()
+                for (method in psiMethods) {
+                    if (method is UMethod) {
+                        if (method.modifierList.hasModifierProperty(PsiModifier.STATIC)) {
+                            // Skip extension properties
+                            continue
+                        }
+                        val sourcePsi = method.sourcePsi
+                        if (sourcePsi is KtProperty) {
+                            if (method.name.startsWith("set")) {
+                                continue
+                            }
+                            val name = sourcePsi.name ?: continue
+                            val psiType = method.returnType ?: continue
+                            properties.add(PsiPropertyItem.create(codebase, item, name, psiType, method))
+                        }
+                    }
+                }
+                item.properties = properties
+            }
+
             val psiInnerClasses = psiClass.innerClasses
             item.innerClasses = if (psiInnerClasses.isEmpty()) {
                 emptyList()
@@ -444,51 +508,6 @@
             return item
         }
 
-        fun create(codebase: PsiBasedCodebase, classFilter: FilteredClassView): PsiClassItem {
-            val original = classFilter.cls
-
-            val newClass = PsiClassItem(
-                codebase = codebase,
-                psiClass = original.psiClass,
-                name = original.name,
-                fullName = original.fullName,
-                qualifiedName = original.qualifiedName,
-                classType = original.classType,
-                hasImplicitDefaultConstructor = original.hasImplicitDefaultConstructor,
-                documentation = original.documentation,
-                modifiers = PsiModifierItem.create(codebase, original.modifiers)
-            )
-
-            newClass.modifiers.setOwner(newClass)
-            codebase.registerClass(newClass)
-            newClass.source = original
-
-            newClass.constructors = classFilter.constructors.map {
-                PsiConstructorItem.create(codebase, newClass, it as PsiConstructorItem)
-            }.toMutableList()
-
-            newClass.methods = classFilter.methods.map {
-                PsiMethodItem.create(codebase, newClass, it as PsiMethodItem)
-            }.toMutableList()
-
-            newClass.fields = classFilter.fields.asSequence()
-                // Preserve sorting order for enums
-                .sortedBy { it.sortingRank }.map {
-                    PsiFieldItem.create(codebase, newClass, it as PsiFieldItem)
-                }.toMutableList()
-
-            newClass.innerClasses = classFilter.innerClasses.map {
-                val newInnerClass = codebase.findClass(it.cls.qualifiedName) ?: it.create(codebase)
-                newInnerClass.containingClass = newClass
-                codebase.registerClass(newInnerClass)
-                newInnerClass
-            }.toMutableList()
-
-            newClass.hasPrivateConstructor = classFilter.cls.hasPrivateConstructor
-
-            return newClass
-        }
-
         private fun addEnumMethods(
             codebase: PsiBasedCodebase,
             classItem: PsiClassItem,
@@ -534,7 +553,7 @@
          * Computes the "full" class name; this is not the qualified class name (e.g. with package)
          * but for an inner class it includes all the outer classes
          */
-        private fun computeFullClassName(cls: PsiClass): String {
+        fun computeFullClassName(cls: PsiClass): String {
             if (cls.containingClass == null) {
                 val name = cls.name
                 return name!!
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt
index 843d3a0..018bb84 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt
@@ -20,6 +20,7 @@
 import com.android.tools.metalava.model.CompilationUnit
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.MemberItem
+import com.android.tools.metalava.model.PackageItem
 import com.android.tools.metalava.model.visitors.ItemVisitor
 import com.google.common.collect.ArrayListMultimap
 import com.google.common.collect.Multimap
@@ -27,14 +28,20 @@
 import com.intellij.psi.PsiClassOwner
 import com.intellij.psi.PsiComment
 import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiField
 import com.intellij.psi.PsiFile
 import com.intellij.psi.PsiJavaFile
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiPackage
 import com.intellij.psi.PsiWhiteSpace
 import org.jetbrains.kotlin.kdoc.psi.api.KDoc
 import org.jetbrains.kotlin.psi.KtFile
 import org.jetbrains.kotlin.psi.psiUtil.startOffset
 import java.util.function.Predicate
 
+/** Whether we should limit import statements to symbols found in class docs  */
+private const val ONLY_IMPORT_CLASSES_REFERENCED_IN_DOCS = true
+
 class PsiCompilationUnit(val codebase: PsiBasedCodebase, containingFile: PsiFile) : CompilationUnit(containingFile) {
     override fun getHeaderComments(): String? {
         // https://youtrack.jetbrains.com/issue/KT-22135
@@ -67,7 +74,6 @@
         val imports = mutableListOf<Item>()
 
         if (file is PsiJavaFile) {
-            // TODO: Do we need to deal with static imports?
             val importList = file.importList
             if (importList != null) {
                 for (importStatement in importList.importStatements) {
@@ -77,15 +83,30 @@
                         if (predicate.test(classItem)) {
                             imports.add(classItem)
                         }
+                    } else if (resolved is PsiPackage) {
+                        val pkgItem = codebase.findPackage(resolved.qualifiedName) ?: continue
+                        if (predicate.test(pkgItem) &&
+                            // Also make sure it isn't an empty package (after applying the filter)
+                            // since in that case we'd have an invalid import
+                            pkgItem.topLevelClasses().any { it.emit && predicate.test(it) }
+                        ) {
+                            imports.add(pkgItem)
+                        }
+                    } else if (resolved is PsiMethod) {
+                        codebase.findClass(resolved.containingClass ?: continue) ?: continue
+                        val methodItem = codebase.findMethod(resolved)
+                        if (predicate.test(methodItem)) {
+                            imports.add(methodItem)
+                        }
+                    } else if (resolved is PsiField) {
+                        val classItem = codebase.findClass(resolved.containingClass ?: continue) ?: continue
+                        val fieldItem = classItem.findField(resolved.name, true, false) ?: continue
+                        if (predicate.test(fieldItem)) {
+                            imports.add(fieldItem)
+                        }
                     }
                 }
             }
-
-            for (psiClass in file.classes) {
-                val classItem = codebase.findClass(psiClass) ?: continue
-                if (predicate.test(classItem)) {
-                }
-            }
         } else if (file is KtFile) {
             for (importDirective in file.importDirectives) {
                 val resolved = importDirective.reference?.resolve() ?: continue
@@ -113,39 +134,50 @@
             // Compute set of import statements that are actually referenced
             // from the documentation (we do inexact matching here; we don't
             // need to have an exact set of imports since it's okay to have
-            // some extras)
-            val result = mutableListOf<Item>()
-            for (cls in classes(predicate)) {
-                cls.accept(object : ItemVisitor() {
-                    override fun visitItem(item: Item) {
-                        val doc = item.documentation
-                        if (doc.isNotBlank()) {
-                            var found: MutableList<String>? = null
-                            for (name in map.keys()) {
-                                if (doc.contains(name)) {
-                                    if (found == null) {
-                                        found = mutableListOf()
-                                    }
-                                    found.add(name)
-                                }
-                            }
-                            found?.let {
-                                for (name in found) {
-                                    val all = map.get(name) ?: continue
-                                    for (referenced in all) {
-                                        if (!result.contains(referenced)) {
-                                            result.add(referenced)
+            // some extras). This isn't a big problem since our codestyle
+            // forbids/discourages wildcards, so it shows up in fewer places,
+            // but we need to handle it when it does -- such as in ojluni.
+
+            @Suppress("ConstantConditionIf")
+            return if (ONLY_IMPORT_CLASSES_REFERENCED_IN_DOCS) {
+                val result = mutableListOf<Item>()
+
+                // We keep the wildcard imports since we don't know which ones of those are relevant
+                imports.filter { it is PackageItem }.forEach { result.add(it) }
+
+                for (cls in classes(predicate)) {
+                    cls.accept(object : ItemVisitor() {
+                        override fun visitItem(item: Item) {
+                            val doc = item.documentation
+                            if (doc.isNotBlank()) {
+                                var found: MutableList<String>? = null
+                                for (name in map.keys()) {
+                                    if (doc.contains(name)) {
+                                        if (found == null) {
+                                            found = mutableListOf()
                                         }
+                                        found.add(name)
                                     }
-                                    map.removeAll(name)
+                                }
+                                found?.let {
+                                    for (name in found) {
+                                        val all = map.get(name) ?: continue
+                                        for (referenced in all) {
+                                            if (!result.contains(referenced)) {
+                                                result.add(referenced)
+                                            }
+                                        }
+                                        map.removeAll(name)
+                                    }
                                 }
                             }
                         }
-                    }
-                })
+                    })
+                }
+                result
+            } else {
+                imports
             }
-
-            return result
         }
 
         return emptyList()
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
index cc14c10..5b51251 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
@@ -17,7 +17,6 @@
 package com.android.tools.metalava.model.psi
 
 import com.android.tools.metalava.model.ConstructorItem
-import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.MethodItem
 import com.intellij.psi.JavaPsiFacade
 import com.intellij.psi.PsiClass
@@ -27,7 +26,6 @@
 import com.intellij.psi.PsiMethod
 import com.intellij.psi.PsiMethodCallExpression
 import com.intellij.psi.PsiWhiteSpace
-import java.util.function.Predicate
 
 class PsiConstructorItem(
     codebase: PsiBasedCodebase,
@@ -93,43 +91,14 @@
         return _superMethods!!
     }
 
-    fun findDelegate(predicate: Predicate<Item>, allowInexactMatch: Boolean = true): PsiConstructorItem? {
-        if (isImplicitConstructor()) {
-            // Delegate to parent implicit constructors
-            (containingClass().superClass() as? PsiClassItem)?.constructors()?.forEach {
-                if (it.implicitConstructor) {
-                    return if (predicate.test(it)) {
-                        it
-                    } else {
-                        it.findDelegate(predicate, allowInexactMatch)
-                    }
-                }
-            }
+    override fun psi(): PsiElement? {
+        // If no PSI element, is this a synthetic/implicit constructor? If so
+        // grab the parent class' PSI element instead for file/location purposes
+        if (implicitConstructor && element.containingFile?.virtualFile == null) {
+            return containingClass().psi()
         }
 
-        val superPsiMethod = PsiConstructorItem.findSuperOrThis(psiMethod)
-        if (superPsiMethod != null) {
-            val superMethod = codebase.findMethod(superPsiMethod) as PsiConstructorItem
-            if (!predicate.test(superMethod)) {
-                return superMethod.findDelegate(predicate, allowInexactMatch)
-            }
-            return superMethod
-        }
-
-        // Try to pick an alternative - for example adding package private bridging
-        // methods if the super class is in the same package
-        val constructors = (containingClass().superClass() as? PsiClassItem)?.constructors()
-        constructors?.forEach { constructor ->
-            if (predicate.test(constructor)) {
-                return constructor
-            }
-            val superMethod = constructor.findDelegate(predicate, allowInexactMatch)
-            if (superMethod != null) {
-                return superMethod
-            }
-        }
-
-        return null
+        return element
     }
 
     companion object {
@@ -187,65 +156,5 @@
             modifiers.setOwner(item)
             return item
         }
-
-        fun create(
-            codebase: PsiBasedCodebase,
-            containingClass: PsiClassItem,
-            original: PsiConstructorItem
-        ): PsiConstructorItem {
-            val constructor = PsiConstructorItem(
-                codebase = codebase,
-                psiMethod = original.psiMethod,
-                containingClass = containingClass,
-                name = original.name(),
-                documentation = original.documentation,
-                modifiers = PsiModifierItem.create(codebase, original.modifiers),
-                parameters = PsiParameterItem.create(codebase, original.parameters()),
-                returnType = codebase.getType(containingClass.psiClass),
-                implicitConstructor = original.implicitConstructor
-            )
-
-            constructor.modifiers.setOwner(constructor)
-            constructor.source = original
-
-            return constructor
-        }
-
-        internal fun findSuperOrThis(psiMethod: PsiMethod): PsiMethod? {
-            val superMethods = psiMethod.findSuperMethods()
-            if (superMethods.isNotEmpty()) {
-                return superMethods[0]
-            }
-
-// WARNING: I've deleted private constructors from class model; may not be right for here!
-
-            // TODO: Port to UAST
-            var curr: PsiElement? = psiMethod.body?.firstBodyElement
-            while (curr != null && curr is PsiWhiteSpace) {
-                curr = curr.nextSibling
-            }
-            if (curr is PsiExpressionStatement && curr.expression is PsiMethodCallExpression) {
-                val call = curr.expression as PsiMethodCallExpression
-                if (call.firstChild?.lastChild is PsiKeyword) {
-                    val keyword = call.firstChild?.lastChild
-                    // TODO: Check Kotlin!
-                    if (keyword?.text == "super" || keyword?.text == "this") {
-                        val resolved = call.resolveMethod()
-                        if (resolved is PsiMethod) {
-                            return resolved
-                        }
-                    }
-                }
-            }
-
-            // TODO: Try to find a super call *anywhere* in the method
-
-            // See if we have an implicit constructor in the parent that we can call
-//            psiMethod.containingClass?.constructors?.forEach {
-//                // PsiUtil.hasDefaultConstructor(psiClass) if (it.impl)
-//            }
-
-            return null
-        }
     }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt
index f04647a..dc027ec 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt
@@ -79,8 +79,9 @@
 
     override fun duplicate(targetContainingClass: ClassItem): PsiFieldItem {
         val duplicated = create(codebase, targetContainingClass as PsiClassItem, psiField)
+        duplicated.inheritedFrom = containingClass
 
-        // Preserve flags that may have been inherited (propagated) fro surrounding packages
+        // Preserve flags that may have been inherited (propagated) from surrounding packages
         if (targetContainingClass.hidden) {
             duplicated.hidden = true
         }
@@ -94,6 +95,8 @@
         return duplicated
     }
 
+    override var inheritedFrom: ClassItem? = null
+
     override fun equals(other: Any?): Boolean {
         if (this === other) {
             return true
@@ -131,21 +134,5 @@
             field.modifiers.setOwner(field)
             return field
         }
-
-        fun create(codebase: PsiBasedCodebase, containingClass: PsiClassItem, original: PsiFieldItem): PsiFieldItem {
-            val field = PsiFieldItem(
-                codebase = codebase,
-                psiField = original.psiField,
-                containingClass = containingClass,
-                name = original.name,
-                documentation = original.documentation,
-                modifiers = PsiModifierItem.create(codebase, original.modifiers),
-                fieldType = PsiTypeItem.create(codebase, original.fieldType),
-                isEnumConstant = original.isEnumConstant,
-                initialValue = original.initialValue
-            )
-            field.modifiers.setOwner(field)
-            return field
-        }
     }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt
index 591bcd7..a8251a5 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt
@@ -17,20 +17,13 @@
 package com.android.tools.metalava.model.psi
 
 import com.android.tools.metalava.model.DefaultItem
-import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.MutableModifierList
-import com.android.tools.metalava.model.PackageItem
 import com.android.tools.metalava.model.ParameterItem
-import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiCompiledElement
 import com.intellij.psi.PsiDocCommentOwner
 import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiMember
 import com.intellij.psi.PsiModifierListOwner
-import com.intellij.psi.PsiReference
-import com.intellij.psi.PsiWhiteSpace
-import com.intellij.psi.javadoc.PsiDocTag
-import com.intellij.psi.javadoc.PsiInlineDocTag
+import org.jetbrains.kotlin.idea.KotlinLanguage
 import org.jetbrains.kotlin.kdoc.psi.api.KDoc
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.sourcePsiElement
@@ -42,15 +35,25 @@
     override var documentation: String
 ) : DefaultItem() {
 
-    override val deprecated: Boolean get() = modifiers.isDeprecated()
+    @Suppress("LeakingThis")
+    override var deprecated: Boolean = modifiers.isDeprecated()
 
     @Suppress("LeakingThis") // Documentation can change, but we don't want to pick up subsequent @docOnly mutations
     override var docOnly = documentation.contains("@doconly")
     @Suppress("LeakingThis")
     override var removed = documentation.contains("@removed")
+
     @Suppress("LeakingThis")
-    override var hidden = (documentation.contains("@hide") || documentation.contains("@pending") ||
-        modifiers.hasHideAnnotations()) && !modifiers.hasShowAnnotation()
+    override var originallyHidden =
+        documentation.contains('@') &&
+            (documentation.contains("@hide") ||
+                documentation.contains("@pending") ||
+                // KDoc:
+                documentation.contains("@suppress")) ||
+            modifiers.hasHideAnnotations()
+
+    @Suppress("LeakingThis")
+    override var hidden = originallyHidden && !modifiers.hasShowAnnotation()
 
     override fun psi(): PsiElement? = element
 
@@ -121,174 +124,12 @@
         documentation = mergeDocumentation(documentation, element, comment.trim(), tagSection, append)
     }
 
-    private fun packageName(): String? {
-        var curr: Item? = this
-        while (curr != null) {
-            if (curr is PackageItem) {
-                return curr.qualifiedName()
-            }
-            curr = curr.parent()
-        }
-
-        return null
-    }
-
     override fun fullyQualifiedDocumentation(): String {
-        if (documentation.isBlank()) {
-            return documentation
-        }
-
-        if (!(documentation.contains("@link") || // includes @linkplain
-                documentation.contains("@see") ||
-                documentation.contains("@throws"))
-        ) {
-            // No relevant tags that need to be expanded/rewritten
-            return documentation
-        }
-
-        val comment =
-            try {
-                codebase.getComment(documentation, psi())
-            } catch (throwable: Throwable) {
-                // TODO: Get rid of line comments as documentation
-                // Invalid comment
-                if (documentation.startsWith("//") && documentation.contains("/**")) {
-                    documentation = documentation.substring(documentation.indexOf("/**"))
-                }
-                codebase.getComment(documentation, psi())
-            }
-        val sb = StringBuilder(documentation.length)
-        var curr = comment.firstChild
-        while (curr != null) {
-            if (curr is PsiDocTag) {
-                sb.append(getExpanded(curr))
-            } else {
-                sb.append(curr.text)
-            }
-            curr = curr.nextSibling
-        }
-
-        return sb.toString()
+        return fullyQualifiedDocumentation(documentation)
     }
 
-    private fun getExpanded(tag: PsiDocTag): String {
-        val text = tag.text
-        var valueElement = tag.valueElement
-        val reference = extractReference(tag)
-        var resolved = reference?.resolve()
-        var referenceText = reference?.element?.text
-        if (resolved == null && tag.name == "throws") {
-            // Workaround: @throws does not provide a valid reference to the class
-            val dataElements = tag.dataElements
-            if (dataElements.isNotEmpty()) {
-                if (dataElements[0] is PsiInlineDocTag) {
-                    val innerReference = extractReference(dataElements[0] as PsiInlineDocTag)
-                    resolved = innerReference?.resolve()
-                    if (innerReference != null && resolved == null) {
-                        referenceText = innerReference.canonicalText
-                        resolved = codebase.createReferenceFromText(referenceText, psi()).resolve()
-                    } else {
-                        referenceText = innerReference?.element?.text
-                    }
-                }
-                if (resolved == null || referenceText == null) {
-                    val exceptionName = dataElements[0].text
-                    val exceptionReference = codebase.createReferenceFromText(exceptionName, psi())
-                    resolved = exceptionReference.resolve()
-                    referenceText = exceptionName
-                } else {
-                    // Create a placeholder value since the inline tag
-                    // wipes it out
-                    val t = dataElements[0].text
-                    val index = text.indexOf(t) + t.length
-                    val suffix = text.substring(index)
-                    val dummyTag = codebase.createDocTagFromText("@${tag.name} $suffix")
-                    valueElement = dummyTag.valueElement
-                }
-            } else {
-                return text
-            }
-        }
-
-        if (resolved != null && referenceText != null) {
-            if (referenceText.startsWith("#")) {
-                // Already a local/relative reference
-                return text
-            }
-
-            when (resolved) {
-            // TODO: If not absolute, preserve syntax
-                is PsiClass -> {
-                    if (samePackage(resolved)) {
-                        return text
-                    }
-                    val qualifiedName = resolved.qualifiedName ?: return text
-                    if (referenceText == qualifiedName) {
-                        // Already absolute
-                        return text
-                    }
-                    return when {
-                        valueElement != null -> {
-                            val start = valueElement.startOffsetInParent
-                            val end = start + valueElement.textLength
-                            text.substring(0, start) + qualifiedName + text.substring(end)
-                        }
-                        tag.name == "see" -> {
-                            val suffix = text.substring(text.indexOf(referenceText) + referenceText.length)
-                            "@see $qualifiedName$suffix"
-                        }
-                        text.startsWith("{") -> "{@${tag.name} $qualifiedName $referenceText}"
-                        else -> "@${tag.name} $qualifiedName $referenceText"
-                    }
-                }
-                is PsiMember -> {
-                    val containing = resolved.containingClass ?: return text
-                    if (samePackage(containing)) {
-                        return text
-                    }
-                    val qualifiedName = containing.qualifiedName ?: return text
-                    if (referenceText.startsWith(qualifiedName)) {
-                        // Already absolute
-                        return text
-                    }
-
-                    val name = containing.name ?: return text
-                    if (valueElement != null) {
-                        val start = valueElement.startOffsetInParent
-                        val close = text.lastIndexOf('}')
-                        if (close == -1) {
-                            return text // invalid javadoc
-                        }
-                        val memberPart = text.substring(text.indexOf(name, start) + name.length, close)
-                        return "${text.substring(0, start)}$qualifiedName$memberPart $referenceText}"
-                    }
-                }
-            }
-        }
-
-        return text
-    }
-
-    private fun samePackage(cls: PsiClass): Boolean {
-        val pkg = packageName() ?: return false
-        return cls.qualifiedName == "$pkg.${cls.name}"
-    }
-
-    // Copied from UnnecessaryJavaDocLinkInspection
-    private fun extractReference(tag: PsiDocTag): PsiReference? {
-        val valueElement = tag.valueElement
-        if (valueElement != null) {
-            return valueElement.reference
-        }
-        // hack around the fact that a reference to a class is apparently
-        // not a PsiDocTagValue
-        val dataElements = tag.dataElements
-        if (dataElements.isEmpty()) {
-            return null
-        }
-        val salientElement: PsiElement = dataElements.firstOrNull { it !is PsiWhiteSpace } ?: return null
-        val child = salientElement.firstChild
-        return if (child !is PsiReference) null else child
+    override fun fullyQualifiedDocumentation(documentation: String): String {
+        return toFullyQualifiedDocumentation(this, documentation)
     }
 
     /** Finish initialization of the item */
@@ -322,7 +163,7 @@
                 }
             }
 
-            if (element is PsiDocCommentOwner) {
+            if (element is PsiDocCommentOwner && element.docComment !is PsiCompiledElement) {
                 return element.docComment?.text ?: ""
             }
 
@@ -338,7 +179,7 @@
         }
 
         fun isKotlin(element: PsiElement): Boolean {
-            return element.language.id == "kotlin"
+            return element.language === KotlinLanguage.INSTANCE
         }
     }
 }
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
index 2f557be..f856c91 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
@@ -16,14 +16,15 @@
 
 package com.android.tools.metalava.model.psi
 
-import com.android.tools.metalava.ExtractAnnotations
 import com.android.tools.metalava.compatibility
+import com.android.tools.metalava.model.AnnotationTarget
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.ModifierList
 import com.android.tools.metalava.model.ParameterItem
 import com.android.tools.metalava.model.TypeItem
 import com.android.tools.metalava.model.TypeParameterList
+import com.intellij.openapi.components.ServiceManager
 import com.intellij.psi.PsiAnnotationMethod
 import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiMethod
@@ -34,9 +35,11 @@
 import org.jetbrains.kotlin.psi.KtProperty
 import org.jetbrains.uast.UClass
 import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
 import org.jetbrains.uast.UMethod
 import org.jetbrains.uast.UThrowExpression
 import org.jetbrains.uast.UTryExpression
+import org.jetbrains.uast.UastContext
 import org.jetbrains.uast.getParentOfType
 import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
 import org.jetbrains.uast.visitor.AbstractUastVisitor
@@ -75,6 +78,7 @@
     internal var source: PsiMethodItem? = null
 
     override var inheritedMethod: Boolean = false
+    override var inheritedFrom: ClassItem? = null
 
     override fun name(): String = name
     override fun containingClass(): PsiClassItem = containingClass
@@ -114,10 +118,6 @@
         return superMethods!!
     }
 
-    fun setSuperMethods(superMethods: List<MethodItem>) {
-        this.superMethods = superMethods
-    }
-
     override fun typeParameterList(): TypeParameterList {
         if (psiMethod.hasTypeParameters()) {
             return PsiTypeParameterList(
@@ -217,7 +217,24 @@
         if (psiMethod is PsiAnnotationMethod) {
             val value = psiMethod.defaultValue
             if (value != null) {
-                return ExtractAnnotations.toSourceExpression(value, this)
+                if (PsiItem.isKotlin(value)) {
+                    val uastContext = ServiceManager.getService(value.project, UastContext::class.java)
+                        ?: error("UastContext not found")
+                    val defaultExpression: UExpression = uastContext.convertElement(
+                        value, null,
+                        UExpression::class.java
+                    ) as? UExpression ?: return ""
+                    val constant = defaultExpression.evaluate()
+                    return if (constant != null) {
+                        CodePrinter.constantToSource(constant)
+                    } else {
+                        // Expression: Compute from UAST rather than just using the source text
+                        // such that we can ensure references are fully qualified etc.
+                        codebase.printer.toSourceString(defaultExpression) ?: ""
+                    }
+                } else {
+                    return codebase.printer.toSourceExpression(value, this)
+                }
             }
         }
 
@@ -227,7 +244,9 @@
     override fun duplicate(targetContainingClass: ClassItem): PsiMethodItem {
         val duplicated = create(codebase, targetContainingClass as PsiClassItem, psiMethod)
 
-        // Preserve flags that may have been inherited (propagated) fro surrounding packages
+        duplicated.inheritedFrom = containingClass
+
+        // Preserve flags that may have been inherited (propagated) from surrounding packages
         if (targetContainingClass.hidden) {
             duplicated.hidden = true
         }
@@ -237,7 +256,9 @@
         if (targetContainingClass.docOnly) {
             duplicated.docOnly = true
         }
-
+        if (targetContainingClass.deprecated && compatibility.propagateDeprecatedMembers) {
+            duplicated.deprecated = true
+        }
         duplicated.throwsTypes = throwsTypes
         return duplicated
     }
@@ -261,11 +282,11 @@
 
         val modifierString = StringWriter()
         ModifierList.write(
-            modifierString, method.modifiers, method, removeAbstract = false,
-            removeFinal = false, addPublic = true,
-            onlyIncludeSignatureAnnotations = false,
-            onlyIncludeStubAnnotations = true,
-            onlyIncludeClassRetentionAnnotations = true
+            modifierString, method.modifiers, method,
+            target = AnnotationTarget.SDK_STUBS_FILE,
+            removeAbstract = false,
+            removeFinal = false,
+            addPublic = true
         )
         sb.append(modifierString.toString())
 
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
index 324625e..015b470 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
@@ -18,251 +18,26 @@
 
 import com.android.tools.metalava.model.AnnotationItem
 import com.android.tools.metalava.model.Codebase
-import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.DefaultModifierList
 import com.android.tools.metalava.model.ModifierList
 import com.android.tools.metalava.model.MutableModifierList
 import com.intellij.psi.PsiDocCommentOwner
+import com.intellij.psi.PsiMethod
 import com.intellij.psi.PsiModifier
-import com.intellij.psi.PsiModifierList
 import com.intellij.psi.PsiModifierListOwner
 import org.jetbrains.kotlin.asJava.elements.KtLightModifierList
 import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtNamedFunction
+import org.jetbrains.uast.UMethod
 
 class PsiModifierItem(
-    override val codebase: Codebase,
-    var flags: Int = 0,
-    private var annotations: MutableList<AnnotationItem>? = null
-) : ModifierList, MutableModifierList {
-    private lateinit var owner: Item
-
-    private operator fun set(mask: Int, set: Boolean) {
-        flags = if (set) {
-            flags or mask
-        } else {
-            flags and mask.inv()
-        }
-    }
-
-    private fun isSet(mask: Int): Boolean {
-        return flags and mask != 0
-    }
-
-    override fun annotations(): List<AnnotationItem> {
-        return annotations ?: emptyList()
-    }
-
-    override fun owner(): Item {
-        return owner
-    }
-
-    fun setOwner(owner: Item) {
-        this.owner = owner
-    }
-
-    override fun isPublic(): Boolean {
-        return isSet(PUBLIC)
-    }
-
-    override fun isProtected(): Boolean {
-        return isSet(PROTECTED)
-    }
-
-    override fun isPrivate(): Boolean {
-        return isSet(PRIVATE)
-    }
-
-    override fun isStatic(): Boolean {
-        return isSet(STATIC)
-    }
-
-    override fun isAbstract(): Boolean {
-        return isSet(ABSTRACT)
-    }
-
-    override fun isFinal(): Boolean {
-        return isSet(FINAL)
-    }
-
-    override fun isNative(): Boolean {
-        return isSet(NATIVE)
-    }
-
-    override fun isSynchronized(): Boolean {
-        return isSet(SYNCHRONIZED)
-    }
-
-    override fun isStrictFp(): Boolean {
-        return isSet(STRICT_FP)
-    }
-
-    override fun isTransient(): Boolean {
-        return isSet(TRANSIENT)
-    }
-
-    override fun isVolatile(): Boolean {
-        return isSet(VOLATILE)
-    }
-
-    override fun isDefault(): Boolean {
-        return isSet(DEFAULT)
-    }
-
-    fun isDeprecated(): Boolean {
-        return isSet(DEPRECATED)
-    }
-
-    override fun isVarArg(): Boolean {
-        return isSet(VARARG)
-    }
-
-    override fun isSealed(): Boolean {
-        return isSet(SEALED)
-    }
-
-    override fun isInternal(): Boolean {
-        return isSet(INTERNAL)
-    }
-
-    override fun isInfix(): Boolean {
-        return isSet(INFIX)
-    }
-
-    override fun isOperator(): Boolean {
-        return isSet(OPERATOR)
-    }
-
-    override fun isInline(): Boolean {
-        return isSet(INLINE)
-    }
-
-    override fun setPublic(public: Boolean) {
-        set(PUBLIC, public)
-    }
-
-    override fun setProtected(protected: Boolean) {
-        set(PROTECTED, protected)
-    }
-
-    override fun setPrivate(private: Boolean) {
-        set(PRIVATE, private)
-    }
-
-    override fun setStatic(static: Boolean) {
-        set(STATIC, static)
-    }
-
-    override fun setAbstract(abstract: Boolean) {
-        set(ABSTRACT, abstract)
-    }
-
-    override fun setFinal(final: Boolean) {
-        set(FINAL, final)
-    }
-
-    override fun setNative(native: Boolean) {
-        set(NATIVE, native)
-    }
-
-    override fun setSynchronized(synchronized: Boolean) {
-        set(SYNCHRONIZED, synchronized)
-    }
-
-    override fun setStrictFp(strictfp: Boolean) {
-        set(STRICT_FP, strictfp)
-    }
-
-    override fun setTransient(transient: Boolean) {
-        set(TRANSIENT, transient)
-    }
-
-    override fun setVolatile(volatile: Boolean) {
-        set(VOLATILE, volatile)
-    }
-
-    override fun setDefault(default: Boolean) {
-        set(DEFAULT, default)
-    }
-
-    fun setDeprecated(deprecated: Boolean) {
-        set(DEPRECATED, deprecated)
-    }
-
-    override fun addAnnotation(annotation: AnnotationItem) {
-        if (annotations == null) {
-            annotations = mutableListOf()
-        }
-        annotations?.add(annotation)
-    }
-
-    override fun removeAnnotation(annotation: AnnotationItem) {
-        annotations?.remove(annotation)
-    }
-
-    override fun clearAnnotations(annotation: AnnotationItem) {
-        annotations?.clear()
-    }
-
-    override fun isEmpty(): Boolean {
-        return flags and DEPRECATED.inv() == 0 // deprecated isn't a real modifier
-    }
-
-    override fun isPackagePrivate(): Boolean {
-        return flags and (PUBLIC or PROTECTED or PRIVATE or INTERNAL) == 0
-    }
-
-    fun getAccessFlags(): Int {
-        return flags and (PUBLIC or PROTECTED or PRIVATE or INTERNAL)
-    }
-
-    // Rename? It's not a full equality, it's whether an override's modifier set is significant
-    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
-        }
-        return false
-    }
-
+    codebase: Codebase,
+    flags: Int = 0,
+    annotations: MutableList<AnnotationItem>? = null
+) : DefaultModifierList(codebase, flags, annotations), ModifierList, MutableModifierList {
     companion object {
-        const val PUBLIC = 1 shl 0
-        const val PROTECTED = 1 shl 1
-        const val PRIVATE = 1 shl 2
-        const val STATIC = 1 shl 3
-        const val ABSTRACT = 1 shl 4
-        const val FINAL = 1 shl 5
-        const val NATIVE = 1 shl 6
-        const val SYNCHRONIZED = 1 shl 7
-        const val STRICT_FP = 1 shl 8
-        const val TRANSIENT = 1 shl 9
-        const val VOLATILE = 1 shl 10
-        const val DEFAULT = 1 shl 11
-        const val DEPRECATED = 1 shl 12
-        const val VARARG = 1 shl 13
-        const val SEALED = 1 shl 14
-        const val INTERNAL = 1 shl 15
-        const val INFIX = 1 shl 16
-        const val OPERATOR = 1 shl 17
-        const val INLINE = 1 shl 18
-
-        /**
-         * Modifiers considered significant to include signature files (and similarly
-         * 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
-            SEALED or INTERNAL or INFIX or OPERATOR
-
         fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner, documentation: String?): PsiModifierItem {
-            val modifiers = create(
-                codebase,
-                element.modifierList
-            )
+            val modifiers = create(codebase, element)
 
             if (documentation?.contains("@deprecated") == true ||
                 // Check for @Deprecated annotation
@@ -274,8 +49,8 @@
             return modifiers
         }
 
-        fun create(codebase: PsiBasedCodebase, modifierList: PsiModifierList?): PsiModifierItem {
-            modifierList ?: return PsiModifierItem(codebase)
+        private fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner): PsiModifierItem {
+            val modifierList = element.modifierList ?: return PsiModifierItem(codebase)
 
             var flags = 0
             if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) {
@@ -339,6 +114,26 @@
                     }
                     if (ktModifierList.hasModifier(KtTokens.INLINE_KEYWORD)) {
                         flags = flags or INLINE
+
+                        // Workaround for b/117565118:
+                        if ((flags or PRIVATE) != 0 && element is PsiMethod) {
+                            val t =
+                                ((element as? UMethod)?.sourcePsi as? KtNamedFunction)?.typeParameterList?.text ?: ""
+                            if (t.contains("reified") &&
+                                !ktModifierList.hasModifier(KtTokens.PRIVATE_KEYWORD) &&
+                                !ktModifierList.hasModifier(KtTokens.INTERNAL_KEYWORD)
+                            ) {
+                                // Switch back from private to public
+                                flags = (flags and PRIVATE.inv()) or PUBLIC
+                            }
+                        }
+                    }
+                    if (ktModifierList.hasModifier(KtTokens.SUSPEND_KEYWORD)) {
+                        flags = flags or SUSPEND
+
+                        // Workaround for b/117565118:
+                        // Switch back from private to public
+                        flags = (flags and PRIVATE.inv()) or PUBLIC
                     }
                 }
             }
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiPackageItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiPackageItem.kt
index a4fa5e4..6060a07 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiPackageItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiPackageItem.kt
@@ -18,7 +18,6 @@
 
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.PackageItem
-import com.android.tools.metalava.options
 import com.intellij.psi.PsiPackage
 
 class PsiPackageItem(
@@ -41,8 +40,6 @@
 
     lateinit var containingPackageField: PsiPackageItem
 
-    override var hidden: Boolean = super.hidden || options.hidePackages.contains(qualifiedName)
-
     override fun containingPackage(): PackageItem? {
         return if (qualifiedName.isEmpty()) null else {
             if (!::containingPackageField.isInitialized) {
@@ -107,7 +104,7 @@
 
     override fun hashCode(): Int = qualifiedName.hashCode()
 
-    override fun toString(): String = "Package $qualifiedName"
+    override fun toString(): String = "package $qualifiedName"
 
     override fun finishInitialization() {
         super.finishInitialization()
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
index a15e083..71c5093 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
@@ -19,10 +19,15 @@
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.ParameterItem
 import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToSource
+import com.intellij.openapi.components.ServiceManager
 import com.intellij.psi.PsiParameter
+import org.jetbrains.kotlin.psi.KtConstantExpression
 import org.jetbrains.kotlin.psi.KtNamedFunction
 import org.jetbrains.kotlin.psi.KtParameter
 import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UastContext
 import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
 
 class PsiParameterItem(
@@ -62,7 +67,7 @@
 
     override fun hasDefaultValue(): Boolean {
         return if (isKotlin(psiParameter)) {
-            getKtParameter()?.hasDefaultValue() ?: false
+            getKtParameter()?.hasDefaultValue() ?: false && defaultValue() != INVALID_VALUE
         } else {
             // Java: Look for @ParameterName annotation
             modifiers.annotations().any { it.isDefaultValue() }
@@ -107,14 +112,41 @@
         return null
     }
 
+    private var defaultValue: String? = null
+
     override fun defaultValue(): String? {
+        if (defaultValue == null) {
+            defaultValue = computeDefaultValue()
+        }
+        return defaultValue
+    }
+
+    private fun computeDefaultValue(): String? {
         if (isKotlin(psiParameter)) {
             val ktParameter = getKtParameter() ?: return null
             if (ktParameter.hasDefaultValue()) {
-                return ktParameter.defaultValue?.text
+                val defaultValue = ktParameter.defaultValue ?: return null
+                if (defaultValue is KtConstantExpression) {
+                    return defaultValue.text
+                }
+
+                val uastContext = ServiceManager.getService(psiParameter.project, UastContext::class.java)
+                    ?: error("UastContext not found")
+                val defaultExpression: UExpression = uastContext.convertElement(
+                    defaultValue, null,
+                    UExpression::class.java
+                ) as? UExpression ?: return INVALID_VALUE
+                val constant = defaultExpression.evaluate()
+                return if (constant != null && constant !is kotlin.Pair<*, *>) {
+                    constantToSource(constant)
+                } else {
+                    // Expression: Compute from UAST rather than just using the source text
+                    // such that we can ensure references are fully qualified etc.
+                    codebase.printer.toSourceString(defaultExpression)
+                }
             }
 
-            return null
+            return INVALID_VALUE
         } else {
             // Java: Look for @ParameterName annotation
             val annotation = modifiers.annotations().firstOrNull { it.isDefaultValue() }
@@ -192,5 +224,11 @@
         ): List<PsiParameterItem> {
             return original.map { create(codebase, it as PsiParameterItem) }
         }
+
+        /**
+         * Private marker return value from [#computeDefaultValue] signifying that the parameter
+         * has a default value but we were unable to compute a suitable static string representation for it
+         */
+        private const val INVALID_VALUE = "__invalid_value__"
     }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiPropertyItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiPropertyItem.kt
new file mode 100644
index 0000000..7c416ad
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiPropertyItem.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.model.psi
+
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.PropertyItem
+import com.android.tools.metalava.model.TypeItem
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiType
+import org.jetbrains.uast.UClass
+
+class PsiPropertyItem(
+    override val codebase: PsiBasedCodebase,
+    private val psiMethod: PsiMethod,
+    private val containingClass: PsiClassItem,
+    private val name: String,
+    modifiers: PsiModifierItem,
+    documentation: String,
+    private val fieldType: PsiTypeItem
+) :
+    PsiItem(
+        codebase = codebase,
+        modifiers = modifiers,
+        documentation = documentation,
+        element = psiMethod
+    ), PropertyItem {
+
+    override fun type(): TypeItem = fieldType
+    override fun name(): String = name
+    override fun containingClass(): ClassItem = containingClass
+
+    override fun isCloned(): Boolean {
+        val psiClass = run {
+            val p = containingClass().psi() as? PsiClass ?: return false
+            if (p is UClass) {
+                p.sourcePsi as? PsiClass ?: return false
+            } else {
+                p
+            }
+        }
+        return psiMethod.containingClass != psiClass
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) {
+            return true
+        }
+        return other is FieldItem && name == other.name() && containingClass == other.containingClass()
+    }
+
+    override fun hashCode(): Int {
+        return name.hashCode()
+    }
+
+    override fun toString(): String = "field ${containingClass.fullName()}.${name()}"
+
+    companion object {
+        fun create(
+            codebase: PsiBasedCodebase,
+            containingClass: PsiClassItem,
+            name: String,
+            psiType: PsiType,
+            psiMethod: PsiMethod
+        ): PsiPropertyItem {
+            val commentText = javadoc(psiMethod)
+            val modifiers = modifiers(codebase, psiMethod, commentText)
+            val typeItem = codebase.getType(psiType)
+            val property = PsiPropertyItem(
+                codebase = codebase,
+                psiMethod = psiMethod,
+                containingClass = containingClass,
+                name = name,
+                documentation = commentText,
+                modifiers = modifiers,
+                fieldType = typeItem
+            )
+            property.modifiers.setOwner(property)
+            return property
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt
index be0d36c..821025f 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt
@@ -53,6 +53,7 @@
 import com.intellij.psi.PsiWildcardType
 import com.intellij.psi.util.PsiTypesUtil
 import com.intellij.psi.util.TypeConversionUtil
+import org.jetbrains.kotlin.asJava.elements.KtLightTypeParameter
 
 /** Represents a type backed by PSI */
 class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, private val psiType: PsiType) : TypeItem {
@@ -69,7 +70,8 @@
     override fun toTypeString(
         outerAnnotations: Boolean,
         innerAnnotations: Boolean,
-        erased: Boolean
+        erased: Boolean,
+        context: Item?
     ): String {
         assert(innerAnnotations || !outerAnnotations) // Can't supply outer=true,inner=false
 
@@ -123,8 +125,8 @@
         }
     }
 
-    override fun toErasedTypeString(): String {
-        return toTypeString(outerAnnotations = false, innerAnnotations = false, erased = true)
+    override fun toErasedTypeString(context: Item?): String {
+        return toTypeString(outerAnnotations = false, innerAnnotations = false, erased = true, context = context)
     }
 
     override fun arrayDimensions(): Int {
@@ -341,12 +343,18 @@
             }
 
             if (SUPPORT_TYPE_USE_ANNOTATIONS && (innerAnnotations || outerAnnotations)) {
-                val typeString = mapAnnotations(codebase, getCanonicalText(type, true))
-                if (!outerAnnotations && typeString.contains("@")) {
-                    // Temporary hack: should use PSI type visitor instead
-                    return TextTypeItem.eraseAnnotations(typeString, false, true)
+                try {
+                    val canonical = getCanonicalText(type, true)
+                    val typeString = mapAnnotations(codebase, canonical)
+                    if (!outerAnnotations && typeString.contains("@")) {
+                        // Temporary hack: should use PSI type visitor instead
+                        return TextTypeItem.eraseAnnotations(typeString, false, true)
+                    }
+
+                    return typeString
+                } catch (ignore: Throwable) {
+                    return type.canonicalText
                 }
-                return typeString
             } else {
                 return type.canonicalText
             }
@@ -375,7 +383,7 @@
                 }
                 val annotation = string.substring(start + 1, index)
 
-                val mapped = AnnotationItem.mapName(codebase, annotation, ApiPredicate(codebase))
+                val mapped = AnnotationItem.mapName(codebase, annotation, ApiPredicate())
                 if (mapped != null) {
                     if (mapped != annotation) {
                         s = string.substring(0, start + 1) + mapped + s.substring(index)
@@ -405,7 +413,11 @@
         }
 
         private fun getCanonicalText(type: PsiType, annotated: Boolean): String {
-            val typeString = type.getCanonicalText(annotated && SUPPORT_TYPE_USE_ANNOTATIONS)
+            val typeString = try {
+                type.getCanonicalText(annotated && SUPPORT_TYPE_USE_ANNOTATIONS)
+            } catch (e: Throwable) {
+                return type.getCanonicalText(false)
+            }
             if (!annotated || !SUPPORT_TYPE_USE_ANNOTATIONS) {
                 return typeString
             }
@@ -488,6 +500,9 @@
                             sb.append(">")
                             return
                         } else if (element is PsiTypeParameter) {
+                            if (element is KtLightTypeParameter && element.kotlinOrigin.text.startsWith("reified")) {
+                                sb.append("reified ")
+                            }
                             sb.append(element.name)
                             // TODO: How do I get super -- e.g. "Comparable<? super T>"
                             val extendsList = element.extendsList
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeParameterItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeParameterItem.kt
index 5c9fc67..91d27ee 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeParameterItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeParameterItem.kt
@@ -20,6 +20,7 @@
 import com.android.tools.metalava.model.TypeParameterItem
 import com.android.tools.metalava.model.psi.ClassType.TYPE_PARAMETER
 import com.intellij.psi.PsiTypeParameter
+import org.jetbrains.kotlin.asJava.elements.KtLightTypeParameter
 
 class PsiTypeParameterItem(
     codebase: PsiBasedCodebase,
@@ -40,6 +41,10 @@
 ), TypeParameterItem {
     override fun bounds(): List<ClassItem> = bounds
 
+    override fun isReified(): Boolean {
+        return element is KtLightTypeParameter && element.kotlinOrigin.text.startsWith("reified")
+    }
+
     private lateinit var bounds: List<ClassItem>
 
     override fun finishInitialization() {
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt
index 4911c56..ed3f0ff 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt
@@ -20,12 +20,13 @@
 import com.android.tools.metalava.model.AnnotationItem
 import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.DefaultAnnotationAttribute
+import com.android.tools.metalava.model.DefaultAnnotationItem
 
 class TextBackedAnnotationItem(
-    override val codebase: Codebase,
+    codebase: Codebase,
     source: String,
     mapName: Boolean = true
-) : AnnotationItem {
+) : DefaultAnnotationItem(codebase) {
     private val qualifiedName: String?
     private val full: String
     private val attributes: List<AnnotationAttribute>
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 7fbfe59..86c72e5 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
@@ -18,30 +18,28 @@
 
 import com.android.tools.metalava.doclava1.SourcePositionInfo
 import com.android.tools.metalava.doclava1.TextCodebase
+import com.android.tools.metalava.model.AnnotationRetention
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.ConstructorItem
+import com.android.tools.metalava.model.DefaultModifierList
 import com.android.tools.metalava.model.FieldItem
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.MethodItem
 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.TypeParameterItem
 import com.android.tools.metalava.model.TypeParameterList
+import com.android.tools.metalava.model.TypeParameterListOwner
 import java.util.function.Predicate
 
 open class TextClassItem(
     override val codebase: TextCodebase,
     position: SourcePositionInfo = SourcePositionInfo.UNKNOWN,
-    isPublic: Boolean = false,
-    isProtected: Boolean = false,
-    isPrivate: Boolean = false,
-    isInternal: Boolean = false,
-    isStatic: Boolean = false,
+    modifiers: TextModifiers,
     private var isInterface: Boolean = false,
-    isAbstract: Boolean = false,
     private var isEnum: Boolean = false,
     private var isAnnotation: Boolean = false,
-    isFinal: Boolean = false,
-    isSealed: Boolean = false,
     val qualifiedName: String = "",
     private val qualifiedTypeName: String = qualifiedName,
     var name: String = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1),
@@ -49,17 +47,12 @@
 ) : TextItem(
     codebase = codebase,
     position = position,
-    modifiers = TextModifiers(
-        codebase = codebase,
-        annotationStrings = annotations,
-        public = isPublic, protected = isProtected, private = isPrivate, internal = isInternal,
-        static = isStatic, abstract = isAbstract, final = isFinal, sealed = isSealed
-    )
-), ClassItem {
+    modifiers = modifiers
+), ClassItem, TypeParameterListOwner {
 
     init {
         @Suppress("LeakingThis")
-        (modifiers as TextModifiers).owner = this
+        modifiers.setOwner(this)
     }
 
     override val isTypeParameter: Boolean = false
@@ -115,27 +108,50 @@
         this.isEnum = isEnum
     }
 
-    override fun containingPackage(): PackageItem = containingPackage ?: error(this)
+    override fun containingPackage(): PackageItem =
+        containingClass?.containingPackage() ?: containingPackage ?: error(this)
 
-    override fun toType(): TypeItem = codebase.obtainTypeFromString(
-        if (typeParameterList().toString().isNotEmpty())
-// TODO: No, handle List<String>[], though this is highly unlikely in a class
-            qualifiedName() + "<" + typeParameterList() + ">"
-        else qualifiedName()
-    )
+    override fun toType(): TypeItem {
+        val typeParameterListString = typeParameterList().toString()
+        return codebase.obtainTypeFromString(
+            if (typeParameterListString.isNotEmpty()) {
+                // TODO: No, handle List<String>[], though this is highly unlikely in a class
+                qualifiedName() + typeParameterListString
+            } else qualifiedName()
+        )
+    }
 
     override fun hasTypeVariables(): Boolean {
         return typeInfo?.hasTypeArguments() ?: false
     }
 
+    private var typeParameterList: TypeParameterList? = null
+
     override fun typeParameterList(): TypeParameterList {
-        // TODO: No, handle List<String>[]  (though it's not likely for type parameters)
-        val s = typeInfo.toString()
-        val index = s.indexOf('<')
-        if (index != -1) {
-            return TextTypeParameterList.create(codebase, s.substring(index))
+        if (typeParameterList == null) {
+            val s = typeInfo.toString()
+            // TODO: No, handle List<String>[]  (though it's not likely for type parameters)
+            val index = s.indexOf('<')
+            typeParameterList = if (index != -1) {
+                TextTypeParameterList.create(codebase, this, s.substring(index))
+            } else {
+                TypeParameterList.NONE
+            }
         }
-        return TypeParameterList.NONE
+
+        return typeParameterList!!
+    }
+
+    override fun resolveParameter(variable: String): TypeParameterItem? {
+        if (hasTypeVariables()) {
+            for (t in typeParameterList().typeParameters()) {
+                if (t.simpleName() == variable) {
+                    return t
+                }
+            }
+        }
+
+        return null
     }
 
     private var superClass: ClassItem? = null
@@ -169,19 +185,17 @@
     private val constructors = mutableListOf<ConstructorItem>()
     private val methods = mutableListOf<MethodItem>()
     private val fields = mutableListOf<FieldItem>()
+    private val properties = mutableListOf<PropertyItem>()
 
     override fun constructors(): List<ConstructorItem> = constructors
     override fun methods(): List<MethodItem> = methods
     override fun fields(): List<FieldItem> = fields
+    override fun properties(): List<PropertyItem> = properties
 
     fun addInterface(itf: TypeItem) {
         interfaceTypes.add(itf)
     }
 
-    fun addInterface(itf: TextClassItem) {
-        interfaceTypes.add(itf.toType())
-    }
-
     fun addConstructor(constructor: TextConstructorItem) {
         constructors += constructor
     }
@@ -194,6 +208,10 @@
         fields += field
     }
 
+    fun addProperty(property: TextPropertyItem) {
+        properties += property
+    }
+
     fun addEnumConstant(field: TextFieldItem) {
         field.setEnumConstant(true)
         fields += field
@@ -211,6 +229,19 @@
         return superClassType
     }
 
+    private var retention: AnnotationRetention? = null
+
+    override fun getRetention(): AnnotationRetention {
+        retention?.let { return it }
+
+        if (!isAnnotationType()) {
+            error("getRetention() should only be called on annotation classes")
+        }
+
+        retention = ClassItem.findRetention(this)
+        return retention!!
+    }
+
     private var fullName: String = name
     override fun simpleName(): String = name.substring(name.lastIndexOf('.') + 1)
     override fun fullName(): String = fullName
@@ -219,36 +250,47 @@
 
     companion object {
         fun createClassStub(codebase: TextCodebase, name: String): TextClassItem =
-            TextClassItem(codebase = codebase, qualifiedName = name, isPublic = true).also {
-                addStubPackage(
-                    name,
-                    codebase,
-                    it
-                )
-            }
-
-        private fun addStubPackage(
-            name: String,
-            codebase: TextCodebase,
-            textClassItem: TextClassItem
-        ) {
-            val endIndex = name.lastIndexOf('.')
-            val pkgPath = name.substring(0, endIndex)
-            val pkg = codebase.findPackage(pkgPath) as? TextPackageItem ?: TextPackageItem(
-                codebase,
-                pkgPath,
-                SourcePositionInfo.UNKNOWN
-            )
-            textClassItem.setContainingPackage(pkg)
-        }
+            createStub(codebase, name, isInterface = false)
 
         fun createInterfaceStub(codebase: TextCodebase, name: String): TextClassItem =
-            TextClassItem(isInterface = true, codebase = codebase, qualifiedName = name, isPublic = true).also {
-                addStubPackage(
-                    name,
-                    codebase,
-                    it
-                )
+            createStub(codebase, name, isInterface = true)
+
+        private fun createStub(codebase: TextCodebase, name: String, isInterface: Boolean): TextClassItem {
+            val index = if (name.endsWith(">")) name.indexOf('<') else -1
+            val qualifiedName = if (index == -1) name else name.substring(0, index)
+            val fullName = getFullName(qualifiedName)
+            val cls = TextClassItem(
+                codebase = codebase,
+                name = fullName,
+                qualifiedName = qualifiedName,
+                isInterface = isInterface,
+                modifiers = TextModifiers(codebase, DefaultModifierList.PUBLIC)
+            )
+            cls.emit = false // it's a stub
+
+            if (index != -1) {
+                cls.typeParameterList = TextTypeParameterList.create(codebase, cls, name.substring(index))
             }
+
+            return cls
+        }
+
+        private fun getFullName(qualifiedName: String): String {
+            var end = -1
+            val length = qualifiedName.length
+            var prev = qualifiedName[length - 1]
+            for (i in length - 2 downTo 0) {
+                val c = qualifiedName[i]
+                if (c == '.' && prev.isUpperCase()) {
+                    end = i + 1
+                }
+                prev = c
+            }
+            if (end != -1) {
+                return qualifiedName.substring(end)
+            }
+
+            return qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1)
+        }
     }
 }
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
index 2b1e1d7..ce2eefc 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
@@ -24,24 +24,10 @@
     codebase: TextCodebase,
     name: String,
     containingClass: TextClassItem,
-    isPublic: Boolean,
-    isProtected: Boolean,
-    isPrivate: Boolean,
-    isInternal: Boolean,
-    isFinal: Boolean,
-    isStatic: Boolean,
-    isAbstract: Boolean,
-    isSynchronized: Boolean,
-    isNative: Boolean,
-    isDefault: Boolean,
+    modifiers: TextModifiers,
     returnType: TextTypeItem?,
-    position: SourcePositionInfo,
-    annotations: List<String>?
-) : TextMethodItem(
-    codebase, name, containingClass, isPublic, isProtected, isPrivate, isInternal,
-    isFinal, isStatic, isAbstract, isSynchronized, isNative, isDefault, false, false, false, false,
-    returnType, position, annotations
-),
+    position: SourcePositionInfo
+) : TextMethodItem(codebase, name, containingClass, modifiers, returnType, position),
     ConstructorItem {
 
     override var superConstructor: ConstructorItem? = null
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt
index fba5169..5eb2ed7 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt
@@ -31,36 +31,9 @@
     private val constantValue: Any?,
     position: SourcePositionInfo
 ) : TextMemberItem(codebase, name, containingClass, position, modifiers), FieldItem {
-    constructor(
-        codebase: TextCodebase,
-        name: String,
-        containingClass: TextClassItem,
-        isPublic: Boolean,
-        isProtected: Boolean,
-        isPrivate: Boolean,
-        isInternal: Boolean,
-        isFinal: Boolean,
-        isStatic: Boolean,
-        isTransient: Boolean,
-        isVolatile: Boolean,
-        type: TextTypeItem,
-        constantValue: Any?,
-        position: SourcePositionInfo,
-        annotations: List<String>?
-    ) :
-        this(
-            codebase, name, containingClass,
-            TextModifiers(
-                codebase = codebase,
-                annotationStrings = annotations,
-                public = isPublic, protected = isProtected, private = isPrivate, internal = isInternal,
-                static = isStatic, final = isFinal, transient = isTransient, volatile = isVolatile
-            ),
-            type, constantValue, position
-        )
 
     init {
-        modifiers.owner = this
+        modifiers.setOwner(this)
     }
 
     override fun equals(other: Any?): Boolean {
@@ -80,16 +53,16 @@
 
     override fun initialValue(requireConstant: Boolean): Any? = constantValue
 
-    override fun toString(): String = "Field ${containingClass().fullName()}.${name()}"
+    override fun toString(): String = "field ${containingClass().fullName()}.${name()}"
 
     override fun duplicate(targetContainingClass: ClassItem): TextFieldItem {
-        val m = modifiers as TextModifiers
         val duplicated = TextFieldItem(
             codebase, name(), targetContainingClass as TextClassItem,
-            m.duplicate(), type, constantValue, position
+            modifiers.duplicate(), type, constantValue, position
         )
+        duplicated.inheritedFrom = containingClass()
 
-        // Preserve flags that may have been inherited (propagated) fro surrounding packages
+        // Preserve flags that may have been inherited (propagated) from surrounding packages
         if (targetContainingClass.hidden) {
             duplicated.hidden = true
         }
@@ -103,6 +76,8 @@
         return duplicated
     }
 
+    override var inheritedFrom: ClassItem? = null
+
     private var isEnumConstant = false
     override fun isEnumConstant(): Boolean = isEnumConstant
     fun setEnumConstant(isEnumConstant: Boolean) {
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextItem.kt
index 72968bd..1d870d8 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextItem.kt
@@ -19,7 +19,6 @@
 import com.android.tools.metalava.doclava1.SourcePositionInfo
 import com.android.tools.metalava.doclava1.TextCodebase
 import com.android.tools.metalava.model.DefaultItem
-import com.android.tools.metalava.model.ModifierList
 import com.android.tools.metalava.model.MutableModifierList
 
 abstract class TextItem(
@@ -27,25 +26,20 @@
     val position: SourcePositionInfo,
     override var docOnly: Boolean = false,
     override var documentation: String = "",
-    override var modifiers: ModifierList
+    override var modifiers: TextModifiers
 ) : DefaultItem() {
 
+    override var originallyHidden = false
     override var hidden = false
     override var removed = false
 
     override fun findTagDocumentation(tag: String): String? = null
     override fun appendDocumentation(comment: String, tagSection: String?, append: Boolean) = codebase.unsupported()
-    override fun mutableModifiers(): MutableModifierList = modifiers as MutableModifierList
+    override fun mutableModifiers(): MutableModifierList = modifiers
     override fun isJava(): Boolean = codebase.unsupported() // source language not recorded in signature files
     override fun isKotlin(): Boolean = codebase.unsupported() // source language not recorded in signature files
 
-    private var mutableDeprecated = false
-    override val deprecated
-        get() = mutableDeprecated
+    override var deprecated = false
 
     override fun isCloned(): Boolean = false
-
-    fun setDeprecated(deprecated: Boolean) {
-        mutableDeprecated = deprecated
-    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt
index 1ddf760..a2d7f53 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt
@@ -20,14 +20,13 @@
 import com.android.tools.metalava.doclava1.TextCodebase
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.MemberItem
-import com.android.tools.metalava.model.ModifierList
 
 abstract class TextMemberItem(
     codebase: TextCodebase,
     private val name: String,
     private val containingClass: TextClassItem,
     position: SourcePositionInfo,
-    override var modifiers: ModifierList
+    override var modifiers: TextModifiers
 ) : TextItem(codebase, position = position, modifiers = modifiers), MemberItem {
 
     override fun name(): String = name
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 ec76fff..e2a6699 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
@@ -16,6 +16,7 @@
 
 package com.android.tools.metalava.model.text
 
+import com.android.tools.metalava.compatibility
 import com.android.tools.metalava.doclava1.SourcePositionInfo
 import com.android.tools.metalava.doclava1.TextCodebase
 import com.android.tools.metalava.model.ClassItem
@@ -23,46 +24,25 @@
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.ParameterItem
 import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.model.TypeParameterItem
 import com.android.tools.metalava.model.TypeParameterList
+import com.android.tools.metalava.model.TypeParameterListOwner
 import java.util.function.Predicate
 
 open class TextMethodItem(
     codebase: TextCodebase,
     name: String,
     containingClass: TextClassItem,
-    isPublic: Boolean,
-    isProtected: Boolean,
-    isPrivate: Boolean,
-    isInternal: Boolean,
-    isFinal: Boolean,
-    isStatic: Boolean,
-    isAbstract: Boolean,
-    isSynchronized: Boolean,
-    isNative: Boolean,
-    isDefault: Boolean,
-    isStrictFp: Boolean,
-    isInfix: Boolean,
-    isOperator: Boolean,
-    isInline: Boolean,
+    modifiers: TextModifiers,
     private val returnType: TextTypeItem?,
-    position: SourcePositionInfo,
-    annotations: List<String>?
+    position: SourcePositionInfo
 ) : TextMemberItem(
-    // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match
-    // the Java5-emitted base API description.
     codebase, name, containingClass, position,
-    modifiers = TextModifiers(
-        codebase = codebase,
-        annotationStrings = annotations, public = isPublic, protected = isProtected, internal = isInternal,
-        private = isPrivate, static = isStatic, final = isFinal, abstract = isAbstract,
-        synchronized = isSynchronized, native = isNative, default = isDefault, strictfp = isStrictFp,
-        infix = isInfix, operator = isOperator, inline = isInline
-    )
-), MethodItem {
-
+    modifiers = modifiers
+), MethodItem, TypeParameterListOwner {
     init {
         @Suppress("LeakingThis")
-        (modifiers as TextModifiers).owner = this
+        modifiers.setOwner(this)
     }
 
     override fun equals(other: Any?): Boolean {
@@ -138,17 +118,51 @@
         this.typeParameterList = typeParameterList
     }
 
-    fun setTypeParameterList(typeParameterList: String?) {
-        this.typeParameterList = if (typeParameterList != null) {
-            TextTypeParameterList.create(codebase, typeParameterList)
-        } else {
-            TypeParameterList.NONE
-        }
-    }
-
     override fun typeParameterList(): TypeParameterList = typeParameterList
 
-    override fun duplicate(targetContainingClass: ClassItem): MethodItem = codebase.unsupported()
+    override fun resolveParameter(variable: String): TypeParameterItem? {
+        for (t in typeParameterList.typeParameters()) {
+            if (t.simpleName() == variable) {
+                return t
+            }
+        }
+
+        return (containingClass() as TextClassItem).resolveParameter(variable)
+    }
+
+    override fun duplicate(targetContainingClass: ClassItem): MethodItem {
+        val duplicated = TextMethodItem(
+            codebase, name(), targetContainingClass as TextClassItem,
+            modifiers.duplicate(), returnType, position
+        )
+        duplicated.inheritedFrom = containingClass()
+
+        // Preserve flags that may have been inherited (propagated) from surrounding packages
+        if (targetContainingClass.hidden) {
+            duplicated.hidden = true
+        }
+        if (targetContainingClass.removed) {
+            duplicated.removed = true
+        }
+        if (targetContainingClass.docOnly) {
+            duplicated.docOnly = true
+        }
+        if (targetContainingClass.deprecated && compatibility.propagateDeprecatedMembers) {
+            duplicated.deprecated = true
+        }
+
+        duplicated.varargs = varargs
+        duplicated.deprecated = deprecated
+        duplicated.annotationDefault = annotationDefault
+        duplicated.throwsTypes.addAll(throwsTypes)
+        duplicated.throwsClasses = throwsClasses
+        duplicated.typeParameterList = typeParameterList
+        // Consider cloning these: they have back references to the parent method (though it's
+        // unlikely anyone will care about the difference in parent methods)
+        duplicated.parameters.addAll(parameters)
+
+        return duplicated
+    }
 
     private val throwsTypes = mutableListOf<String>()
     private val parameters = mutableListOf<TextParameterItem>()
@@ -185,9 +199,10 @@
     override fun isExtensionMethod(): Boolean = codebase.unsupported()
 
     override var inheritedMethod: Boolean = false
+    override var inheritedFrom: ClassItem? = null
 
     override fun toString(): String =
-        "${if (isConstructor()) "Constructor" else "Method"} ${containingClass().qualifiedName()}.${name()}(${parameters().joinToString {
+        "${if (isConstructor()) "constructor" else "method"} ${containingClass().qualifiedName()}.${name()}(${parameters().joinToString {
             it.type().toSimpleType()
         }})"
 
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt b/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt
index 5038b3b..690594d 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt
@@ -16,74 +16,53 @@
 
 package com.android.tools.metalava.model.text
 
+import com.android.tools.metalava.JAVA_LANG_DEPRECATED
 import com.android.tools.metalava.model.AnnotationAttribute
 import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.AnnotationTarget
 import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.DefaultAnnotationAttribute
-import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.DefaultAnnotationItem
+import com.android.tools.metalava.model.DefaultModifierList
 import com.android.tools.metalava.model.ModifierList
-import com.android.tools.metalava.model.MutableModifierList
 import java.io.StringWriter
 
 class TextModifiers(
     override val codebase: Codebase,
-    private val annotationStrings: List<String>? = null,
-    private var public: Boolean = false,
-    private var protected: Boolean = false,
-    private var private: Boolean = false,
-    private var internal: Boolean = false,
-    private var static: Boolean = false,
-    private var abstract: Boolean = false,
-    private var final: Boolean = false,
-    private var native: Boolean = false,
-    private var synchronized: Boolean = false,
-    private var strictfp: Boolean = false,
-    private var transient: Boolean = false,
-    private var volatile: Boolean = false,
-    private var default: Boolean = false,
-    private var infix: Boolean = false,
-    private var operator: Boolean = false,
-    private var inline: Boolean = false,
-    private var sealed: Boolean = false,
-    private var vararg: Boolean = false
-) : MutableModifierList {
-    private var annotations: MutableList<AnnotationItem> = mutableListOf()
+    flags: Int = 0,
+    annotations: MutableList<AnnotationItem>? = null
+) : DefaultModifierList(codebase, flags, annotations) {
 
     fun duplicate(): TextModifiers {
-        val new = TextModifiers(
-            codebase,
-            null,
-            public,
-            protected,
-            private,
-            internal,
-            static,
-            abstract,
-            final,
-            native,
-            synchronized,
-            strictfp,
-            transient,
-            volatile,
-            default,
-            infix,
-            operator,
-            inline,
-            sealed,
-            vararg
-        )
-        new.annotations.addAll(annotations) // these are immutable; sharing copies is fine
-        return new
+        val annotations = this.annotations
+        val newAnnotations =
+            if (annotations == null || annotations.isEmpty()) {
+                null
+            } else {
+                annotations.toMutableList()
+            }
+        return TextModifiers(codebase, flags, newAnnotations)
     }
 
-    init {
-        annotationStrings?.forEach { source ->
+    fun addAnnotations(annotationSources: List<String>?) {
+        annotationSources ?: return
+        if (annotationSources.isEmpty()) {
+            return
+        }
+
+        val annotations = ArrayList<AnnotationItem>(annotationSources.size)
+        annotationSources.forEach { source ->
             val index = source.indexOf('(')
             val qualifiedName = AnnotationItem.mapName(
                 codebase,
                 if (index == -1) source.substring(1) else source.substring(1, index)
             )
 
+            // @Deprecated is also treated as a "modifier"
+            if (qualifiedName == JAVA_LANG_DEPRECATED) {
+                setDeprecated(true)
+            }
+
             val attributes =
                 if (index == -1) {
                     emptyList()
@@ -91,116 +70,20 @@
                     DefaultAnnotationAttribute.createList(source.substring(index + 1, source.lastIndexOf(')')))
                 }
             val codebase = codebase
-            val item = object : AnnotationItem {
-                override val codebase = codebase
+            val item = object : DefaultAnnotationItem(codebase) {
                 override fun attributes(): List<AnnotationAttribute> = attributes
                 override fun qualifiedName(): String? = qualifiedName
                 override fun toSource(): String = source
             }
             annotations.add(item)
         }
-    }
-
-    override fun isPublic(): Boolean = public
-    override fun isProtected(): Boolean = protected
-    override fun isPrivate(): Boolean = private
-    override fun isInternal(): Boolean = internal
-    override fun isStatic(): Boolean = static
-    override fun isAbstract(): Boolean = abstract
-    override fun isFinal(): Boolean = final
-    override fun isNative(): Boolean = native
-    override fun isSynchronized(): Boolean = synchronized
-    override fun isStrictFp(): Boolean = strictfp
-    override fun isTransient(): Boolean = transient
-    override fun isVolatile(): Boolean = volatile
-    override fun isDefault(): Boolean = default
-    override fun isSealed(): Boolean = sealed
-    override fun isInfix(): Boolean = infix
-    override fun isInline(): Boolean = inline
-    override fun isOperator(): Boolean = operator
-    override fun isVarArg(): Boolean = vararg
-
-    override fun setPublic(public: Boolean) {
-        this.public = public
-    }
-
-    override fun setProtected(protected: Boolean) {
-        this.protected = protected
-    }
-
-    override fun setPrivate(private: Boolean) {
-        this.private = private
-    }
-
-    override fun setStatic(static: Boolean) {
-        this.static = static
-    }
-
-    override fun setAbstract(abstract: Boolean) {
-        this.abstract = abstract
-    }
-
-    override fun setFinal(final: Boolean) {
-        this.final = final
-    }
-
-    override fun setNative(native: Boolean) {
-        this.native = native
-    }
-
-    override fun setSynchronized(synchronized: Boolean) {
-        this.synchronized = synchronized
-    }
-
-    override fun setStrictFp(strictfp: Boolean) {
-        this.strictfp = strictfp
-    }
-
-    override fun setTransient(transient: Boolean) {
-        this.transient = transient
-    }
-
-    override fun setVolatile(volatile: Boolean) {
-        this.volatile = volatile
-    }
-
-    override fun setDefault(default: Boolean) {
-        this.default = default
-    }
-
-    var owner: Item? = null
-
-    override fun owner(): Item = owner!! // Must be set after construction
-    override fun isEmpty(): Boolean {
-        return !(public || protected || private || static || abstract || final || native || synchronized ||
-            strictfp || transient || volatile || default)
-    }
-
-    override fun annotations(): List<AnnotationItem> {
-        return annotations
-    }
-
-    override fun addAnnotation(annotation: AnnotationItem) {
-        val qualifiedName = annotation.qualifiedName()
-        if (annotations.any { it.qualifiedName() == qualifiedName }) {
-            return
-        }
-        // TODO: Worry about repeatable annotations?
-        annotations.add(annotation)
-    }
-
-    override fun removeAnnotation(annotation: AnnotationItem) {
-        annotations.remove(annotation)
-    }
-
-    override fun clearAnnotations(annotation: AnnotationItem) {
-        annotations.clear()
+        this.annotations = annotations
     }
 
     override fun toString(): String {
-        val item = owner ?: return super.toString()
+        val item = owner()
         val writer = StringWriter()
-        ModifierList.write(writer, this, item)
+        ModifierList.write(writer, this, item, target = AnnotationTarget.SDK_STUBS_FILE)
         return writer.toString()
     }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
index 642d437..0c2d7d2 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
@@ -24,10 +24,11 @@
 class TextPackageItem(
     codebase: TextCodebase,
     private val name: String,
+    modifiers: TextModifiers,
     position: SourcePositionInfo
-) : TextItem(codebase, position, modifiers = TextModifiers(codebase = codebase, public = true)), PackageItem {
+) : TextItem(codebase, position, modifiers = modifiers), PackageItem {
     init {
-        (modifiers as TextModifiers).owner = this
+        modifiers.setOwner(this)
     }
 
     private val classes = ArrayList<TextClassItem>(100)
@@ -38,6 +39,16 @@
         classes.add(classInfo)
     }
 
+    internal fun pruneClassList() {
+        val iterator = classes.listIterator()
+        while (iterator.hasNext()) {
+            val cls = iterator.next()
+            if (cls.isInnerClass()) {
+                iterator.remove()
+            }
+        }
+    }
+
     internal fun classList(): List<ClassItem> = classes
 
     override fun topLevelClasses(): Sequence<ClassItem> = classes.asSequence()
@@ -55,5 +66,5 @@
         return name.hashCode()
     }
 
-    override fun toString(): String = name
+    override fun toString(): String = "package $name"
 }
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt
index 1b07c6b..8e9099f 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt
@@ -30,20 +30,15 @@
     private var publicName: String?,
     private var defaultValue: String? = NO_DEFAULT_VALUE,
     override val parameterIndex: Int,
-    var typeName: String,
     private var type: TextTypeItem,
-    vararg: Boolean,
-    position: SourcePositionInfo,
-    annotations: List<String>?
+    modifiers: TextModifiers,
+    position: SourcePositionInfo
 )
 // TODO: We need to pass in parameter modifiers here (synchronized etc)
-    : TextItem(
-    codebase, position,
-    modifiers = TextModifiers(codebase = codebase, annotationStrings = annotations, vararg = vararg)
-), ParameterItem {
+    : TextItem(codebase, position, modifiers = modifiers), ParameterItem {
 
     init {
-        (modifiers as TextModifiers).owner = this
+        modifiers.setOwner(this)
     }
 
     override fun isVarArgs(): Boolean {
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextPropertyItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextPropertyItem.kt
new file mode 100644
index 0000000..f6fb8c9
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextPropertyItem.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.model.text
+
+import com.android.tools.metalava.doclava1.SourcePositionInfo
+import com.android.tools.metalava.doclava1.TextCodebase
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.PropertyItem
+import com.android.tools.metalava.model.TypeItem
+
+class TextPropertyItem(
+    codebase: TextCodebase,
+    name: String,
+    containingClass: TextClassItem,
+    modifiers: TextModifiers,
+    private val type: TextTypeItem,
+    position: SourcePositionInfo
+) : TextMemberItem(codebase, name, containingClass, position, modifiers), PropertyItem {
+
+    init {
+        modifiers.setOwner(this)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is FieldItem) return false
+
+        if (name() != other.name()) {
+            return false
+        }
+
+        return containingClass() == other.containingClass()
+    }
+
+    override fun hashCode(): Int = name().hashCode()
+
+    override fun type(): TypeItem = type
+
+    override fun toString(): String = "Field ${containingClass().fullName()}.${name()}"
+}
\ No newline at end of file
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 306be6f..08ef2ee 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
@@ -16,6 +16,8 @@
 
 package com.android.tools.metalava.model.text
 
+import com.android.tools.metalava.JAVA_LANG_OBJECT
+import com.android.tools.metalava.JAVA_LANG_PREFIX
 import com.android.tools.metalava.doclava1.TextCodebase
 import com.android.tools.metalava.model.ClassItem
 import com.android.tools.metalava.model.Item
@@ -24,6 +26,9 @@
 import com.android.tools.metalava.model.TypeItem
 import com.android.tools.metalava.model.TypeParameterItem
 import com.android.tools.metalava.model.TypeParameterList
+import com.android.tools.metalava.model.TypeParameterListOwner
+
+const val ASSUME_TYPE_VARS_EXTEND_OBJECT = false
 
 class TextTypeItem(
     val codebase: TextCodebase,
@@ -31,16 +36,17 @@
 ) : TypeItem {
     override fun toString(): String = type
 
-    override fun toErasedTypeString(): String {
-        return toTypeString(false, false, true)
+    override fun toErasedTypeString(context: Item?): String {
+        return toTypeString(false, false, true, context)
     }
 
     override fun toTypeString(
         outerAnnotations: Boolean,
         innerAnnotations: Boolean,
-        erased: Boolean
+        erased: Boolean,
+        context: Item?
     ): String {
-        return toTypeString(type, outerAnnotations, innerAnnotations, erased)
+        return toTypeString(type, outerAnnotations, innerAnnotations, erased, context)
     }
 
     override fun asClass(): ClassItem? {
@@ -57,7 +63,7 @@
                 erased
             }
         }
-        return codebase.findClass(cls) ?: TextClassItem.createClassStub(codebase, cls)
+        return codebase.getOrCreateClass(cls)
     }
 
     fun qualifiedTypeName(): String = type
@@ -67,7 +73,29 @@
 
         return when (other) {
             is TextTypeItem -> toString() == other.toString()
-            is TypeItem -> toTypeString().replace(" ", "") == other.toTypeString().replace(" ", "")
+            is TypeItem -> {
+                val thisString = toTypeString()
+                val otherString = other.toTypeString()
+                if (thisString == otherString) {
+                    return true
+                }
+                if (thisString[0] == otherString[0]) {
+                    val thisCondensed = thisString.replace(" ", "")
+                    val otherCondensed = otherString.replace(" ", "")
+                    if (thisCondensed == otherCondensed) {
+                        return true
+                    }
+                }
+                if (thisString.startsWith(JAVA_LANG_PREFIX) && thisString.endsWith(otherString) &&
+                    thisString.length == otherString.length + JAVA_LANG_PREFIX.length
+                ) {
+                    // When reading signature files, it's sometimes ambiguous whether a name
+                    // references a java.lang. implicit class or a type parameter.
+                    return true
+                }
+
+                return false
+            }
             else -> false
         }
     }
@@ -116,14 +144,20 @@
 
     override fun asTypeParameter(context: MemberItem?): TypeParameterItem? {
         return if (isLikelyTypeParameter(toTypeString())) {
-            val typeParameter = TextTypeParameterItem.create(codebase, toTypeString())
+            val typeParameter =
+                TextTypeParameterItem.create(codebase, context as? TypeParameterListOwner, toTypeString())
 
             if (context != null && typeParameter.bounds().isEmpty()) {
                 val bounds = findTypeVariableBounds(context, typeParameter.simpleName())
                 if (bounds.isNotEmpty()) {
                     val filtered = bounds.filter { !it.isJavaLangObject() }
                     if (filtered.isNotEmpty()) {
-                        return TextTypeParameterItem.create(codebase, toTypeString(), bounds)
+                        return TextTypeParameterItem.create(
+                            codebase,
+                            context as? TypeParameterListOwner,
+                            toTypeString(),
+                            bounds
+                        )
                     }
                 }
             }
@@ -172,14 +206,16 @@
             type: String,
             outerAnnotations: Boolean,
             innerAnnotations: Boolean,
-            erased: Boolean
+            erased: Boolean,
+            context: Item? = null
         ): String {
             return if (erased) {
                 val raw = eraseTypeArguments(type)
+                val concrete = substituteTypeParameters(raw, context)
                 if (outerAnnotations && innerAnnotations) {
-                    raw
+                    concrete
                 } else {
-                    eraseAnnotations(raw, outerAnnotations, innerAnnotations)
+                    eraseAnnotations(concrete, outerAnnotations, innerAnnotations)
                 }
             } else {
                 if (outerAnnotations && innerAnnotations) {
@@ -190,7 +226,32 @@
             }
         }
 
-        private fun eraseTypeArguments(s: String): String {
+        private fun substituteTypeParameters(s: String, context: Item?): String {
+            if (context is TypeParameterListOwner) {
+                var end = s.indexOf('[')
+                if (end == -1) {
+                    end = s.length
+                }
+                if (s[0].isUpperCase() && s.lastIndexOf('.', end) == -1) {
+                    val v = s.substring(0, end)
+                    val parameter = context.resolveParameter(v)
+                    if (parameter != null) {
+                        val bounds = parameter.bounds()
+                        if (bounds.isNotEmpty()) {
+                            return bounds.first().qualifiedName() + s.substring(end)
+                        }
+                        @Suppress("ConstantConditionIf")
+                        if (ASSUME_TYPE_VARS_EXTEND_OBJECT) {
+                            return JAVA_LANG_OBJECT + s.substring(end)
+                        }
+                    }
+                }
+            }
+
+            return s
+        }
+
+        fun eraseTypeArguments(s: String): String {
             val index = s.indexOf('<')
             if (index != -1) {
                 var balance = 0
@@ -249,7 +310,7 @@
             while (true) {
                 val index = s.indexOf('@')
                 if (index == -1 || index >= max) {
-                    return s
+                    break
                 }
 
                 // Find end
@@ -260,6 +321,28 @@
                 val removed = oldLength - newLength
                 max -= removed
             }
+
+            // Sometimes we have a second type after the max, such as
+            // @androidx.annotation.NonNull java.lang.reflect.@androidx.annotation.NonNull TypeVariable<...>
+            for (i in 0 until s.length) {
+                val c = s[i]
+                if (Character.isJavaIdentifierPart(c) || c == '.') {
+                    continue
+                } else if (c == '@') {
+                    // Found embedded annotation within the type
+                    val end = findAnnotationEnd(s, i + 1)
+                    if (end == -1 || end == length) {
+                        break
+                    }
+
+                    s = s.substring(0, i).trim() + s.substring(end).trim()
+                    break
+                } else {
+                    break
+                }
+            }
+
+            return s
         }
 
         private fun findAnnotationEnd(type: String, start: Int): Int {
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterItem.kt
index c08a670..4d3ac8f 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterItem.kt
@@ -18,32 +18,54 @@
 
 import com.android.tools.metalava.doclava1.TextCodebase
 import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.DefaultModifierList
 import com.android.tools.metalava.model.TypeParameterItem
+import com.android.tools.metalava.model.TypeParameterListOwner
 
 class TextTypeParameterItem(
     codebase: TextCodebase,
+    private val owner: TypeParameterListOwner?,
     private val typeParameterString: String,
     name: String,
     private var bounds: List<ClassItem>? = null
 ) :
-    TextClassItem(codebase = codebase, isPublic = true, name = name, qualifiedName = name),
+    TextClassItem(
+        codebase = codebase,
+        modifiers = TextModifiers(codebase, DefaultModifierList.PUBLIC),
+        name = name,
+        qualifiedName = name
+    ),
     TypeParameterItem {
 
     override fun bounds(): List<ClassItem> {
         if (bounds == null) {
-            val boundsString = bounds(typeParameterString)
+            val boundsString = bounds(typeParameterString, owner)
             bounds = if (boundsString.isEmpty()) {
                 emptyList()
             } else {
-                boundsString.mapNotNull { codebase.findClass(it) }.filter { !it.isJavaLangObject() }
+                boundsString.mapNotNull {
+                    val clz = codebase.findClass(it)
+                    if (clz == null && it.contains(".")) {
+                        codebase.getOrCreateClass(it)
+                    } else {
+                        clz
+                    }
+                }.filter {
+                    !it.isJavaLangObject()
+                }
             }
         }
         return bounds!!
     }
 
+    override fun isReified(): Boolean {
+        return typeParameterString.startsWith("reified")
+    }
+
     companion object {
         fun create(
             codebase: TextCodebase,
+            owner: TypeParameterListOwner?,
             typeParameterString: String,
             bounds: List<ClassItem>? = null
         ): TextTypeParameterItem {
@@ -59,16 +81,26 @@
             val name = typeParameterString.substring(0, nameEnd)
             return TextTypeParameterItem(
                 codebase = codebase,
+                owner = owner,
                 typeParameterString = typeParameterString,
                 name = name,
                 bounds = bounds
             )
         }
 
-        fun bounds(typeString: String?): List<String> {
+        fun bounds(typeString: String?, owner: TypeParameterListOwner? = null): List<String> {
             val s = typeString ?: return emptyList()
             val index = s.indexOf("extends ")
             if (index == -1) {
+                // See if this is a type variable that has bounds in the parent
+                val parameters = (owner as? TextMemberItem)?.containingClass()?.typeParameterList()?.typeParameters()
+                    ?: return emptyList()
+                for (p in parameters) {
+                    if (p.simpleName() == s) {
+                        return p.bounds().filter { !it.isJavaLangObject() }.map { it.qualifiedName() }
+                    }
+                }
+
                 return emptyList()
             }
             val list = mutableListOf<String>()
@@ -101,7 +133,19 @@
         private fun add(list: MutableList<String>, s: String, from: Int, to: Int) {
             for (i in from until to) {
                 if (!Character.isWhitespace(s[i])) {
-                    list.add(s.substring(i, to))
+                    var end = to
+                    while (end > i && s[end - 1].isWhitespace()) {
+                        end--
+                    }
+                    var begin = i
+                    while (begin < end && s[begin].isWhitespace()) {
+                        begin++
+                    }
+                    if (begin == end) {
+                        return
+                    }
+                    val element = s.substring(begin, end)
+                    list.add(element)
                     return
                 }
             }
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterList.kt b/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterList.kt
index 00572f0..b21f1e2 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterList.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterList.kt
@@ -19,8 +19,13 @@
 import com.android.tools.metalava.doclava1.TextCodebase
 import com.android.tools.metalava.model.TypeParameterItem
 import com.android.tools.metalava.model.TypeParameterList
+import com.android.tools.metalava.model.TypeParameterListOwner
 
-class TextTypeParameterList(val codebase: TextCodebase, private val typeListString: String) : TypeParameterList {
+class TextTypeParameterList(
+    val codebase: TextCodebase,
+    var owner: TypeParameterListOwner?,
+    private val typeListString: String
+) : TypeParameterList {
     private var typeParameters: List<TypeParameterItem>? = null
     private var typeParameterNames: List<String>? = null
 
@@ -43,15 +48,15 @@
         if (typeParameters == null) {
             val strings = typeParameterStrings(typeListString)
             val list = ArrayList<TypeParameterItem>(strings.size)
-            strings.mapTo(list) { TextTypeParameterItem.create(codebase, it) }
+            strings.mapTo(list) { TextTypeParameterItem.create(codebase, owner, it) }
             typeParameters = list
         }
         return typeParameters!!
     }
 
     companion object {
-        fun create(codebase: TextCodebase, typeListString: String): TypeParameterList {
-            return TextTypeParameterList(codebase, typeListString)
+        fun create(codebase: TextCodebase, owner: TypeParameterListOwner?, typeListString: String): TypeParameterList {
+            return TextTypeParameterList(codebase, owner, typeListString)
         }
 
         fun typeParameterStrings(typeString: String?): List<String> {
diff --git a/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt
index ce054c4..b7e34e9 100644
--- a/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt
+++ b/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt
@@ -18,7 +18,6 @@
 
 import com.android.tools.metalava.doclava1.ApiPredicate
 import com.android.tools.metalava.model.ClassItem
-import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.FieldItem
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.MethodItem
@@ -51,6 +50,7 @@
 
     /** The filter to use to determine if we should emit an item */
     val filterEmit: Predicate<Item>,
+
     /** The filter to use to determine if we should emit a reference to an item */
     val filterReference: Predicate<Item>,
 
@@ -60,10 +60,18 @@
      * Typically these are not included in signature files, but when generating
      * stubs we need to include them.
      */
-    val includeEmptyOuterClasses: Boolean = false
+    val includeEmptyOuterClasses: Boolean = false,
+
+    /**
+     * Whether this visitor should visit elements that have not been
+     * annotated with one of the annotations passed in using the
+     * --show-annotation flag. This is normally true, but signature files
+     * sometimes sets this to false to make the signature file only contain
+     * the "diff" of the annotated API relative to the base API.
+     */
+    val showUnannotated: Boolean = true
 ) : ItemVisitor(visitConstructorsAsMethods, nestInnerClasses) {
     constructor(
-        codebase: Codebase,
         /**
          * Whether constructors should be visited as part of a [#visitMethod] call
          * instead of just a [#visitConstructor] call. Helps simplify visitors that
@@ -92,8 +100,8 @@
         visitConstructorsAsMethods, nestInnerClasses,
         true, methodComparator,
         fieldComparator,
-        ApiPredicate(codebase, ignoreShown = ignoreShown, matchRemoved = remove),
-        ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = remove)
+        ApiPredicate(ignoreShown = ignoreShown, matchRemoved = remove),
+        ApiPredicate(ignoreShown = true, ignoreRemoved = remove)
     )
 
     // The API visitor lazily visits packages only when there's a match within at least one class;
diff --git a/src/main/java/com/android/tools/metalava/model/visitors/ItemVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/ItemVisitor.kt
index b04170b..530bb6c 100644
--- a/src/main/java/com/android/tools/metalava/model/visitors/ItemVisitor.kt
+++ b/src/main/java/com/android/tools/metalava/model/visitors/ItemVisitor.kt
@@ -17,6 +17,7 @@
 package com.android.tools.metalava.model.visitors
 
 import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
 import com.android.tools.metalava.model.CompilationUnit
 import com.android.tools.metalava.model.ConstructorItem
 import com.android.tools.metalava.model.FieldItem
@@ -24,6 +25,7 @@
 import com.android.tools.metalava.model.MethodItem
 import com.android.tools.metalava.model.PackageItem
 import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.PropertyItem
 
 open class ItemVisitor(
     /**
@@ -49,8 +51,9 @@
     /** Visits the item. This is always called before other more specialized visit methods, such as [visitClass]. */
     open fun visitItem(item: Item) {}
 
-    open fun visitCompilationUnit(unit: CompilationUnit) {}
+    open fun visitCodebase(codebase: Codebase) {}
     open fun visitPackage(pkg: PackageItem) {}
+    open fun visitCompilationUnit(unit: CompilationUnit) {}
     open fun visitClass(cls: ClassItem) {}
     open fun visitConstructor(constructor: ConstructorItem) {
         if (visitConstructorsAsMethods) {
@@ -61,8 +64,10 @@
     open fun visitField(field: FieldItem) {}
     open fun visitMethod(method: MethodItem) {}
     open fun visitParameter(parameter: ParameterItem) {}
+    open fun visitProperty(property: PropertyItem) {}
 
     open fun afterVisitItem(item: Item) {}
+    open fun afterVisitCodebase(codebase: Codebase) {}
     open fun afterVisitPackage(pkg: PackageItem) {}
     open fun afterVisitCompilationUnit(unit: CompilationUnit) {}
     open fun afterVisitClass(cls: ClassItem) {}
@@ -75,4 +80,5 @@
     open fun afterVisitField(field: FieldItem) {}
     open fun afterVisitMethod(method: MethodItem) {}
     open fun afterVisitParameter(parameter: ParameterItem) {}
+    open fun afterVisitProperty(property: PropertyItem) {}
 }
diff --git a/src/main/java/com/android/tools/metalava/model/visitors/PredicateVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/PredicateVisitor.kt
deleted file mode 100644
index 45ae3fa..0000000
--- a/src/main/java/com/android/tools/metalava/model/visitors/PredicateVisitor.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 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.model.visitors
-
-import com.android.tools.metalava.model.Item
-
-open class PredicateVisitor(
-    private val predicate: (item: Item) -> Boolean,
-
-    /**
-     * Whether constructors should be visited as part of a [#visitMethod] call
-     * instead of just a [#visitConstructor] call. Helps simplify visitors that
-     * don't care to distinguish between the two cases. Defaults to true.
-     */
-    visitConstructorsAsMethods: Boolean = true,
-    /**
-     * Whether inner classes should be visited "inside" a class; when this property
-     * is true, inner classes are visited before the [#afterVisitClass] method is
-     * called; when false, it's done afterwards. Defaults to false.
-     */
-    nestInnerClasses: Boolean = false
-) : ItemVisitor(visitConstructorsAsMethods, nestInnerClasses) {
-    override fun skip(item: Item): Boolean {
-        return !predicate(item)
-    }
-}
\ No newline at end of file
diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties
index ab0e374..d0a19d3 100644
--- a/src/main/resources/version.properties
+++ b/src/main/resources/version.properties
@@ -1,4 +1,5 @@
+# suppress inspection "UnusedProperty" for whole file
 # 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.0.0
+metalavaVersion=1.1.9
diff --git a/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt b/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt
index 93f2c8c..080e347 100644
--- a/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt
+++ b/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt
@@ -181,7 +181,7 @@
                 src/android/pkg/NullMentions.java:19: warning: Return value of 'method4' documentation mentions 'null' without declaring @NonNull or @Nullable [Nullable:123]
                 src/android/pkg/NullMentions.java:8: warning: Field 'field2' documentation mentions 'null' without declaring @NonNull or @Nullable [Nullable:123]
                 """,
-            extraArguments = arrayOf("--warning", "Nullable"), // Hidden by default
+            extraArguments = arrayOf(ARG_WARNING, "Nullable"), // Hidden by default
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -225,7 +225,7 @@
             warnings = """
                 src/android/pkg/NullMentions.java:15: warning: Field 'field1' documentation mentions constants without declaring an @IntDef [IntDef:124]
                 """,
-            extraArguments = arrayOf("--warning", "IntDef"), // Hidden by default
+            extraArguments = arrayOf(ARG_WARNING, "IntDef"), // Hidden by default
             sourceFiles = *arrayOf(
                 java(
                     """
diff --git a/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt b/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
index 4a1cf39..ddafb8e 100644
--- a/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
+++ b/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
@@ -24,12 +24,13 @@
     @Test
     fun `Test emitting annotation statistics`() {
         check(
-            extraArguments = arrayOf("--annotation-coverage-stats"),
+            extraArguments = arrayOf(ARG_ANNOTATION_COVERAGE_STATS),
             expectedOutput = """
                 Nullness Annotation Coverage Statistics:
-                0 out of 0 methods were annotated (100%)
-                0 out of 1 fields were annotated (0%)
-                0 out of 0 parameters were annotated (100%)
+                0 out of 1 eligible fields (out of 2 total fields) were annotated (0%)
+                0 out of 0 eligible methods (out of 2 total methods) were fully annotated (100%)
+                    0 out of 0 eligible method returns were annotated (100%)
+                    0 out of 0 eligible parameters were annotated (100%)
                 """,
             sourceFiles = *arrayOf(
                 java(
@@ -57,12 +58,13 @@
     @Test
     fun `Static final initialized fields are not nullable`() {
         check(
-            extraArguments = arrayOf("--annotation-coverage-stats"),
+            extraArguments = arrayOf(ARG_ANNOTATION_COVERAGE_STATS),
             expectedOutput = """
                 Nullness Annotation Coverage Statistics:
-                4 out of 5 methods were annotated (80%)
-                0 out of 0 fields were annotated (100%)
-                4 out of 5 parameters were annotated (80%)
+                0 out of 0 eligible fields (out of 0 total fields) were annotated (100%)
+                4 out of 5 eligible methods (out of 6 total methods) were fully annotated (80%)
+                    4 out of 5 eligible method returns were annotated (80%)
+                    4 out of 5 eligible parameters were annotated (80%)
                 """,
             compatibilityMode = false,
             signatureSource = """
diff --git a/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt
index daf8c20..6e2d1ed 100644
--- a/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt
+++ b/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt
@@ -29,7 +29,8 @@
 
     @Test
     fun `Write diff`() {
-        val codebase = ApiFile.parseApi("old.txt", """
+        val codebase = ApiFile.parseApi(
+            "old.txt", """
                 package test.pkg {
                   public interface Appendable {
                     method public test.pkg.Appendable append(java.lang.CharSequence?);
@@ -40,16 +41,19 @@
                     method public test.pkg.Appendable append(java.lang.CharSequence);
                   }
                 }
-        """.trimIndent(), true, true)
+        """.trimIndent(), true, true
+        )
 
-        val codebase2 = ApiFile.parseApi("new.txt", """
+        val codebase2 = ApiFile.parseApi(
+            "new.txt", """
         package test.pkg {
           public interface Appendable {
             method @androidx.annotation.NonNull public test.pkg.Appendable append(@androidx.annotation.Nullable java.lang.CharSequence);
             method public test.pkg.Appendable append2(java.lang.CharSequence);
           }
         }
-        """.trimIndent(), false, false)
+        """.trimIndent(), false, false
+        )
 
         val apiFile = temporaryFolder.newFile("diff.txt")
         compatibility = Compatibility(true)
@@ -57,12 +61,14 @@
         AnnotationsDiffer(codebase, codebase2).writeDiffSignature(apiFile)
         assertTrue(apiFile.exists())
         val actual = apiFile.readText(Charsets.UTF_8)
-        assertEquals("""
+        assertEquals(
+            """
             package test.pkg {
               public abstract interface Appendable {
                 method public abstract test.pkg.Appendable append2(java.lang.CharSequence);
               }
             }
-        """.trimIndent(), actual.trim())
+        """.trimIndent(), actual.trim()
+        )
     }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
index 003e7ba..2df9554 100644
--- a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
+++ b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.tools.metalava
 
-import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
 import org.junit.Test
 
 class AnnotationsMergerTest : DriverTest() {
@@ -24,6 +23,7 @@
     // TODO: Test what happens when we have conflicting data
     //   - NULLABLE_SOURCE on one non null on the other
     //   - annotation specified with different parameters (e.g @Size(4) vs @Size(6))
+    // Test with jar file
 
     @Test
     fun `Signature files contain annotations`() {
@@ -56,9 +56,9 @@
             ),
             // Skip the annotations themselves from the output
             extraArguments = arrayOf(
-                "--hide-package", "android.annotation",
-                "--hide-package", "androidx.annotation",
-                "--hide-package", "android.support.annotation"
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "androidx.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
             ),
             api = """
                 package test.pkg {
@@ -134,58 +134,6 @@
     }
 
     @Test
-    fun `Merge jaif files`() {
-        check(
-            sourceFiles = *arrayOf(
-                java(
-                    """
-                    package test.pkg;
-
-                    public interface Appendable {
-                        Appendable append(CharSequence csq) throws IOException;
-                        String reverse(String s);
-                    }
-                    """
-                )
-            ),
-            compatibilityMode = false,
-            outputKotlinStyleNulls = false,
-            omitCommonPackages = false,
-            mergeJaifAnnotations = """
-                //
-                // Copyright (C) 2017 The Android Open Source Project
-                //
-                package test.pkg:
-                class Appendable:
-                    method append(Ljava/lang/CharSequence;)Ltest/pkg/Appendable;:
-                        parameter #0:
-                          type: @libcore.util.Nullable
-                        // Is expected to return self
-                        return: @libcore.util.NonNull
-                """,
-            api = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
-                """
-                    package test.pkg {
-                      public interface Appendable {
-                        method @androidx.annotation.NonNull public test.pkg.Appendable append(@androidx.annotation.Nullable java.lang.CharSequence);
-                        method public java.lang.String reverse(java.lang.String);
-                      }
-                    }
-                   """
-            } else {
-                """
-                    package test.pkg {
-                      public interface Appendable {
-                        method @androidx.annotation.NonNull public test.pkg.Appendable append(java.lang.CharSequence);
-                        method public java.lang.String reverse(java.lang.String);
-                      }
-                    }
-                   """
-            }
-        )
-    }
-
-    @Test
     fun `Merge signature files`() {
         check(
             sourceFiles = *arrayOf(
@@ -223,4 +171,135 @@
                 """
         )
     }
+
+    @Test
+    fun `Merge qualifier annotations from Java stub files`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                package test.pkg;
+
+                public interface Appendable {
+                    Appendable append(CharSequence csq) throws IOException;
+                }
+                """
+                )
+            ),
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            omitCommonPackages = false,
+            mergeJavaStubAnnotations = """
+                package test.pkg;
+
+                import libcore.util.NonNull;
+                import libcore.util.Nullable;
+
+                public interface Appendable {
+                    @NonNull Appendable append(@Nullable java.lang.CharSequence csq);
+                }
+                """,
+            api = """
+                package test.pkg {
+                  public interface Appendable {
+                    method @androidx.annotation.NonNull public test.pkg.Appendable append(@androidx.annotation.Nullable java.lang.CharSequence);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Merge inclusion annotations from Java stub files`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                package test.pkg;
+
+                public interface Example {
+                    void aNotAnnotated();
+                    void bHidden();
+                    void cShown();
+                }
+
+                public interface HiddenExample {
+                    void method();
+                }
+                """
+                )
+            ),
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            omitCommonPackages = false,
+            hideAnnotations = arrayOf("test.annotation.Hide"),
+            showAnnotations = arrayOf("test.annotation.Show"),
+            showUnannotated = true,
+            mergeInclusionAnnotations = """
+                package test.pkg;
+
+                public interface Example {
+                    void aNotAnnotated();
+                    @test.annotation.Hide void bHidden();
+                    @test.annotation.Hide @test.annotation.Show void cShown();
+                }
+
+                @test.annotation.Hide
+                public interface HiddenExample {
+                    void method();
+                }
+                """,
+            api = """
+                package test.pkg {
+                  public interface Example {
+                    method public void aNotAnnotated();
+                    method public void cShown();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Merge inclusion annotations from Java stub files using --show-single-annotation`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                package test.pkg;
+
+                public interface Example {
+                    void aNotAnnotated();
+                    void bShown();
+                }
+                """
+                )
+            ),
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            omitCommonPackages = false,
+            extraArguments = arrayOf(
+                ARG_HIDE_ANNOTATION, "test.annotation.Hide",
+                ARG_SHOW_SINGLE_ANNOTATION, "test.annotation.Show"
+            ),
+            showUnannotated = true,
+            mergeInclusionAnnotations = """
+                package test.pkg;
+
+                @test.annotation.Hide
+                @test.annotation.Show
+                public interface Example {
+                    void aNotAnnotated();
+                    @test.annotation.Show void bShown();
+                }
+                """,
+            api = """
+                package test.pkg {
+                  public interface Example {
+                    method public void bShown();
+                  }
+                }
+                """
+        )
+    }
 }
diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
index 387c24e..be7ddbe 100644
--- a/src/test/java/com/android/tools/metalava/ApiFileTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
@@ -106,7 +106,7 @@
                       }
                     }
                  """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             checkDoclava1 = false /* doesn't support parameter names */
         )
     }
@@ -137,11 +137,11 @@
                 package test.pkg {
                   public class Foo {
                     ctor public Foo();
-                    method public void foo(String! = "null", String! = "\"Hello World\"", int = "42");
+                    method public void foo(String! = null, String! = "Hello World", int = 42);
                   }
                 }
                  """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             checkDoclava1 = false /* doesn't support default Values */
         )
     }
@@ -155,22 +155,139 @@
                 kotlin(
                     """
                     package test.pkg
+                    import some.other.pkg.Constants.Misc.SIZE
+                    import android.graphics.Bitmap
+                    import android.view.View
 
                     class Foo {
-                        fun error(int: Int = 42, int2: Int? = null, byte: Int = 42, vararg args: String) { }
+                        fun method1(int: Int = 42,
+                            int2: Int? = null,
+                            byte: Int = 2 * 21,
+                            str: String = "hello " + "world",
+                            vararg args: String) { }
+
+                        fun method2(int: Int, int2: Int = (2*int) * SIZE) { }
+
+                        fun method3(str: String, int: Int, int2: Int = double(int) + str.length) { }
+
+                        fun emptyLambda(sizeOf: () -> Unit = {  }) {}
+
+                        fun View.drawToBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap? = null
+
+                        companion object {
+                            fun double(int: Int) = 2 * int
+                            fun print(foo: Foo = Foo()) { println(foo) }
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package some.other.pkg;
+                    public class Constants {
+                        public static class Misc {
+                            public static final int SIZE = 5;
+                        }
                     }
                     """
                 )
             ),
             api = """
+                // Signature format: $SIGNATURE_FORMAT
                 package test.pkg {
                   public final class Foo {
                     ctor public Foo();
-                    method public void error(int p = "42", Integer? int2 = "null", int p1 = "42", java.lang.String... args);
+                    method public android.graphics.Bitmap? drawToBitmap(android.view.View, android.graphics.Bitmap.Config config = android.graphics.Bitmap.Config.ARGB_8888);
+                    method public void emptyLambda(kotlin.jvm.functions.Function0<kotlin.Unit> sizeOf = {});
+                    method public void method1(int p = 42, Integer? int2 = null, int p1 = 42, String str = "hello world", java.lang.String... args);
+                    method public void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE);
+                    method public void method3(String str, int p, int int2 = double(int) + str.length);
+                    field public static final test.pkg.Foo.Companion! Companion;
+                  }
+                  public static final class Foo.Companion {
+                    method public int double(int p);
+                    method public void print(test.pkg.Foo foo = test.pkg.Foo());
                   }
                 }
                 """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation", ARG_HIDE_PACKAGE, "some.other.pkg"),
+            includeSignatureVersion = true,
+            checkDoclava1 = false /* doesn't support default Values */
+        )
+    }
+
+    @Test
+    fun `Default Values in Kotlin for expressions`() {
+        // Testing trickier default values; regression test for problem
+        // observed in androidx.core.util with LruCache
+        check(
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    package androidx.core.util
+
+                    import android.util.LruCache
+
+                    inline fun <K : Any, V : Any> lruCache(
+                        maxSize: Int,
+                        crossinline sizeOf: (key: K, value: V) -> Int = { _, _ -> 1 },
+                        @Suppress("USELESS_CAST") // https://youtrack.jetbrains.com/issue/KT-21946
+                        crossinline create: (key: K) -> V? = { null as V? },
+                        crossinline onEntryRemoved: (evicted: Boolean, key: K, oldValue: V, newValue: V?) -> Unit =
+                            { _, _, _, _ -> }
+                    ): LruCache<K, V> {
+                        return object : LruCache<K, V>(maxSize) {
+                            override fun sizeOf(key: K, value: V) = sizeOf(key, value)
+                            override fun create(key: K) = create(key)
+                            override fun entryRemoved(evicted: Boolean, key: K, oldValue: V, newValue: V?) {
+                                onEntryRemoved(evicted, key, oldValue, newValue)
+                            }
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package androidx.collection;
+
+                    import androidx.annotation.NonNull;
+                    import androidx.annotation.Nullable;
+
+                    import java.util.LinkedHashMap;
+                    import java.util.Locale;
+                    import java.util.Map;
+
+                    public class LruCache<K, V> {
+                        @Nullable
+                        protected V create(@NonNull K key) {
+                            return null;
+                        }
+
+                        protected int sizeOf(@NonNull K key, @NonNull V value) {
+                            return 1;
+                        }
+
+                        protected void entryRemoved(boolean evicted, @NonNull K key, @NonNull V oldValue,
+                                @Nullable V newValue) {
+                        }
+                    }
+                    """
+                ),
+                androidxNullableSource,
+                androidxNonNullSource
+            ),
+            api = """
+                // Signature format: $SIGNATURE_FORMAT
+                package androidx.core.util {
+                  public final class TestKt {
+                    ctor public TestKt();
+                    method public static inline <K, V> android.util.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (V)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ ->  });
+                  }
+                }
+                """,
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation", ARG_HIDE_PACKAGE, "androidx.collection"),
+            includeSignatureVersion = true,
             checkDoclava1 = false /* doesn't support default Values */
         )
     }
@@ -202,6 +319,11 @@
                         }
                     }
 
+                    //@get:RequiresApi(26)
+                    inline val @receiver:String Long.isSrgb get() = true
+                    inline val /*@receiver:ColorInt*/ Int.red get() = 0
+                    inline operator fun String.component1() = ""
+
                     open class Parent {
                         open fun method(): String? = null
                         open fun method2(value: Boolean, value: Boolean?): String? = null
@@ -218,12 +340,19 @@
                     method public java.lang.String getProperty2();
                     method public void otherMethod(boolean ok, int times);
                     method public void setProperty2(java.lang.String p);
+                    property public final java.lang.String property2;
                     field public static final test.pkg.Kotlin.Companion Companion;
                     field public static final int MY_CONST = 42; // 0x2a
                     field public int someField2;
                   }
                   public static final class Kotlin.Companion {
                   }
+                  public final class KotlinKt {
+                    ctor public KotlinKt();
+                    method public static inline operator java.lang.String component1(java.lang.String);
+                    method public static inline int getRed(int);
+                    method public static inline boolean isSrgb(long);
+                  }
                   public class Parent {
                     ctor public Parent();
                     method public java.lang.String method();
@@ -238,6 +367,7 @@
                     method internal boolean getMyHiddenVar${"$"}lintWithKotlin();
                     method internal void myHiddenMethod${"$"}lintWithKotlin();
                     method internal void setMyHiddenVar${"$"}lintWithKotlin(boolean p);
+                    property internal final boolean myHiddenVar;
                     field internal boolean myHiddenVar;
                     field private final java.lang.String property1;
                     field private java.lang.String property2;
@@ -289,7 +419,65 @@
                   }
                   public final class _java_Kt {
                     ctor public _java_Kt();
-                    method public static java.lang.String systemService2(test.pkg.Context);
+                    method public static inline <reified T> T systemService1(test.pkg.Context);
+                    method public static inline java.lang.String systemService2(test.pkg.Context);
+                  }
+                }
+                """,
+            checkDoclava1 = false /* doesn't support Kotlin... */
+        )
+    }
+
+    @Test
+    fun `Kotlin Reified Methods 2`() {
+        check(
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    @file:Suppress("NOTHING_TO_INLINE", "RedundantVisibilityModifier", "unused")
+
+                    package test.pkg
+
+                    inline fun <T> a(t: T) { }
+                    inline fun <reified T> b(t: T) { }
+                    private inline fun <reified T> c(t: T) { } // hide
+                    internal inline fun <reified T> d(t: T) { } // hide
+                    public inline fun <reified T> e(t: T) { }
+                    inline fun <reified T> T.f(t: T) { }
+                    """
+                )
+            ),
+            api = """
+                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);
+                  }
+                }
+                """,
+            checkDoclava1 = false /* doesn't support Kotlin... */
+        )
+    }
+
+    @Test
+    fun `Suspend functions`() {
+        check(
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    package test.pkg
+                    suspend inline fun hello() { }
+                    """
+                )
+            ),
+            api = """
+                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);
                   }
                 }
                 """,
@@ -413,11 +601,98 @@
                   }
                   public final class TestKt {
                     ctor public TestKt();
-                    method public static operator <F, S> F! component1(androidx.util.PlatformJavaPair<F,S>);
+                    method public static inline operator <F, S> F! component1(androidx.util.PlatformJavaPair<F,S>);
                   }
                 }
                 """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
+            checkDoclava1 = false /* doesn't support Kotlin... */
+        )
+    }
+
+    @Test
+    fun `Known nullness`() {
+        // Don't emit platform types for some unannotated elements that we know the
+        // nullness for: annotation type members, equals-parameters, initialized constants, etc.
+        check(
+            compatibilityMode = false,
+            outputKotlinStyleNulls = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    // Platform nullability Pair in Java
+                    package test;
+
+                    import androidx.annotation.NonNull;
+
+                    public class MyClass {
+                        public static final String MY_CONSTANT1 = "constant"; // Not nullable
+                        public final String MY_CONSTANT2 = "constant"; // Not nullable
+                        public String MY_CONSTANT3 = "constant"; // Unknown
+
+                        /** @deprecated */
+                        @Deprecated
+                        @Override
+                        public boolean equals(
+                            Object parameter  // nullable
+                        ) {
+                            return super.equals(parameter);
+                        }
+
+                        /** @deprecated */
+                        @Deprecated
+                        @Override // Not nullable
+                        public String toString() {
+                            return super.toString();
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    import static java.lang.annotation.ElementType.*;
+                    import java.lang.annotation.*;
+                    public @interface MyAnnotation {
+                        String[] value(); // Not nullable
+                    }
+                    """
+                ).indented(),
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public enum Foo {
+                        A, B;
+                    }
+                    """
+                ),
+                androidxNonNullSource,
+                androidxNullableSource
+            ),
+            api = """
+                package test {
+                  public class MyClass {
+                    ctor public MyClass();
+                    method @Deprecated public boolean equals(Object?);
+                    method @Deprecated public String toString();
+                    field public static final String MY_CONSTANT1 = "constant";
+                    field public final String MY_CONSTANT2 = "constant";
+                    field public String! MY_CONSTANT3;
+                  }
+                }
+                package test.pkg {
+                  public enum Foo {
+                    enum_constant public static final test.pkg.Foo A;
+                    enum_constant public static final test.pkg.Foo B;
+                  }
+                  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface MyAnnotation {
+                    method public abstract String[] value();
+                  }
+                }
+                """,
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             checkDoclava1 = false /* doesn't support Kotlin... */
         )
     }
@@ -451,7 +726,7 @@
                         }
 
                         @JvmOverloads
-                        fun String.blahblahblah(firstArg: String = "hello", secondArg: Int = "42", thirdArg: String = "world") {
+                        fun String.blahblahblah(firstArg: String = "hello", secondArg: Int = 42, thirdArg: String = "world") {
                         }
                     """
                 )
@@ -460,16 +735,16 @@
                 package androidx.content {
                   public final class TestKt {
                     ctor public TestKt();
-                    method public static void blahblahblah(String, String firstArg = "\"hello\"", int secondArg = "\"42\"", String thirdArg = "\"world\"");
-                    method public static void blahblahblah(String, String firstArg = "\"hello\"", int secondArg = "\"42\"");
-                    method public static void blahblahblah(String, String firstArg = "\"hello\"");
+                    method public static void blahblahblah(String, String firstArg = "hello", int secondArg = 42, String thirdArg = "world");
+                    method public static void blahblahblah(String, String firstArg = "hello", int secondArg = 42);
+                    method public static void blahblahblah(String, String firstArg = "hello");
                     method public static void blahblahblah(String);
-                    method public static void edit(android.content.SharedPreferences, boolean commit = "false", kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action);
-                    method public static void edit(android.content.SharedPreferences, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action);
+                    method public static inline void edit(android.content.SharedPreferences, boolean commit = false, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action);
+                    method public static inline void edit(android.content.SharedPreferences, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action);
                   }
                 }
                 """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             checkDoclava1 = false /* doesn't support default Values */
         )
     }
@@ -561,7 +836,7 @@
                       }
                     }
                 """,
-            extraArguments = arrayOf("--hide", "KotlinKeyword")
+            extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
         )
     }
 
@@ -676,12 +951,6 @@
 
     @Test
     fun `Enum class, non-compat mode`() {
-        @Suppress("ConstantConditionIf")
-        if (SKIP_NON_COMPAT) {
-            println("Skipping test for non-compatibility mode which isn't fully done yet")
-            return
-        }
-
         // Interface: makes sure the right modifiers etc are shown (and that "package private" methods
         // in the interface are taken to be public etc)
         check(
@@ -700,8 +969,8 @@
             api = """
                 package test.pkg {
                   public enum Foo {
-                    enum_constant public static final test.pkg.Foo! A;
-                    enum_constant public static final test.pkg.Foo! B;
+                    enum_constant public static final test.pkg.Foo A;
+                    enum_constant public static final test.pkg.Foo B;
                   }
                 }
                 """
@@ -823,13 +1092,63 @@
     }
 
     @Test
-    fun `Annotation class extraction, non-compat mode`() {
-        @Suppress("ConstantConditionIf")
-        if (SKIP_NON_COMPAT) {
-            println("Skipping test for non-compatibility mode which isn't fully done yet")
-            return
-        }
+    fun `Skip inherited package private methods from private parents`() {
+        // In non-compat mode, include public methods from hidden parents too.
+        // Real life example: StringBuilder.setLength
+        // This is just like the above test, but with compat mode disabled.
+        check(
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class MyStringBuilder<A,B> extends AbstractMyStringBuilder<A,B> {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    class AbstractMyStringBuilder<C,D> extends PublicSuper<C,D> {
+                        public void setLength(int length) {
+                        }
+                        @Override boolean isContiguous() {
+                            return true;
+                        }
+                        @Override boolean concrete() {
+                            return false;
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public class PublicSuper<E,F> {
+                        abstract boolean isContiguous();
+                        boolean concrete() {
+                            return false;
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class MyStringBuilder<A, B> extends test.pkg.PublicSuper<A,B> {
+                    ctor public MyStringBuilder();
+                    method public void setLength(int);
+                  }
+                  public class PublicSuper<E, F> {
+                    ctor public PublicSuper();
+                  }
+                }
+                """
+        )
+    }
 
+    @Test
+    fun `Annotation class extraction, non-compat mode`() {
         // Interface: makes sure the right modifiers etc are shown (and that "package private" methods
         // in the interface are taken to be public etc)
         check(
@@ -859,13 +1178,13 @@
             compatibilityMode = false,
             api = """
                 package android.annotation {
-                  public @interface SuppressLint {
-                    method public abstract String[]! value();
+                  @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.LOCAL_VARIABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface SuppressLint {
+                    method public abstract String[] value();
                   }
                 }
                 package test.pkg {
-                  public @interface Foo {
-                    method public abstract String! value();
+                  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Foo {
+                    method public abstract String value();
                   }
                 }
                 """
@@ -971,42 +1290,6 @@
     }
 
     @Test
-    fun `Warn about findViewById`() {
-        // Include as many modifiers as possible to see which ones are included
-        // in the signature files, and the expected sorting order.
-        // Note that the signature files treat "deprecated" as a fake modifier.
-        // Note also how the "protected" modifier on the interface method gets
-        // promoted to public.
-        check(
-            checkDoclava1 = true,
-            compatibilityMode = false,
-            outputKotlinStyleNulls = false,
-            sourceFiles = *arrayOf(
-                java(
-                    """
-                    package test.pkg;
-                    import android.annotation.Nullable;
-
-                    @SuppressWarnings("ALL")
-                    public abstract class Foo {
-                        @Nullable public String findViewById(int id) { return ""; }
-                    }
-                    """
-                ),
-                nullableSource
-            ),
-            api = """
-                package test.pkg {
-                  public abstract class Foo {
-                    ctor public Foo();
-                    method public String findViewById(int);
-                  }
-                }
-                """
-        )
-    }
-
-    @Test
     fun `Check all modifiers`() {
         // Include as many modifiers as possible to see which ones are included
         // in the signature files, and the expected sorting order.
@@ -1068,14 +1351,48 @@
     }
 
     @Test
-    fun `Check all modifiers, non-compat mode`() {
-        @Suppress("ConstantConditionIf")
-        if (SKIP_NON_COMPAT) {
-            @Suppress("ConstantConditionIf")
-            println("Skipping test for non-compatibility mode which isn't fully done yet")
-            return
-        }
+    fun `Warn about findViewById`() {
+        // Include as many modifiers as possible to see which ones are included
+        // in the signature files, and the expected sorting order.
+        // Note that the signature files treat "deprecated" as a fake modifier.
+        // Note also how the "protected" modifier on the interface method gets
+        // promoted to public.
+        check(
+            checkDoclava1 = false,
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.Nullable;
 
+                    @SuppressWarnings("ALL")
+                    public abstract class Foo {
+                        @Nullable public String findViewById(int id) { return ""; }
+                    }
+                    """
+                ),
+                nullableSource
+            ),
+
+            warnings = """
+                src/test/pkg/Foo.java:6: warning: method test.pkg.Foo.findViewById(int) should not be annotated @Nullable; it should be left unspecified to make it a platform type [ExpectedPlatformType:149]
+                """,
+            extraArguments = arrayOf(ARG_WARNING, "ExpectedPlatformType"),
+            api = """
+                package test.pkg {
+                  public abstract class Foo {
+                    ctor public Foo();
+                    method public String findViewById(int);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Check all modifiers, non-compat mode`() {
         // Like testModifiers but turns off compat mode, such that we have
         // a modifier order more in line with standard code conventions
         check(
@@ -1105,18 +1422,18 @@
                 package test.pkg {
                   public abstract class Foo {
                     ctor public Foo();
-                    method deprecated public static final synchronized strictfp void method1();
-                    method deprecated public static final synchronized native void method2();
+                    method @Deprecated public static final void method1();
+                    method @Deprecated public static final void method2();
                   }
-                  deprecated protected static final class Foo.Inner1 {
-                    ctor protected Foo.Inner1();
+                  @Deprecated protected static final class Foo.Inner1 {
+                    ctor @Deprecated protected Foo.Inner1();
                   }
-                  deprecated protected abstract static class Foo.Inner2 {
-                    ctor protected Foo.Inner2();
+                  @Deprecated protected abstract static class Foo.Inner2 {
+                    ctor @Deprecated protected Foo.Inner2();
                   }
-                  deprecated protected static interface Foo.Inner3 {
-                    method public default void method3();
-                    method public static void method4(int);
+                  @Deprecated protected static interface Foo.Inner3 {
+                    method @Deprecated public default void method3();
+                    method @Deprecated public static void method4(int);
                   }
                 }
                 """
@@ -1657,12 +1974,6 @@
 
     @Test
     fun `Implementing package private class, non-compat mode`() {
-        @Suppress("ConstantConditionIf")
-        if (SKIP_NON_COMPAT) {
-            println("Skipping test for non-compatibility mode which isn't fully done yet")
-            return
-        }
-
         // Like the previous test, but in non compat mode we correctly
         // include all the non-hidden public interfaces into the signature
 
@@ -1703,7 +2014,7 @@
                     ctor public MyClass();
                     method public void method();
                     method public void other();
-                    field public static final String! CONSTANT = "MyConstant";
+                    field public static final String CONSTANT = "MyConstant";
                   }
                   public interface OtherInterface {
                     method public void other();
@@ -1809,14 +2120,6 @@
         // implementing the TemporalAccessor#getLong method
         check(
             sourceFiles = *arrayOf(
-//                java(
-//                    """
-//                    package test.pkg;
-//                    public interface SomeInterface {
-//                        long getLong();
-//                    }
-//                    """
-//                ),
                 java(
                     """
                     package test.pkg;
@@ -1830,26 +2133,13 @@
                 java(
                     """
                     package test.pkg;
-                    public class Foo implements /*SomeInterface,*/ SomeInterface2 {
+                    public class Foo implements SomeInterface2 {
                         @Override
                         public long getLong() { return 0L; }
                     }
                     """
                 )
             ),
-//            api = """
-//                package test.pkg {
-//                  public class Foo implements test.pkg.SomeInterface test.pkg.SomeInterface2 {
-//                    ctor public Foo();
-//                  }
-//                  public abstract interface SomeInterface {
-//                    method public abstract long getLong();
-//                  }
-//                  public abstract interface SomeInterface2 {
-//                    method public default long getLong();
-//                  }
-//                }
-//                """
             api = """
             package test.pkg {
               public class Foo implements test.pkg.SomeInterface2 {
@@ -1864,6 +2154,54 @@
     }
 
     @Test
+    fun `Implementing interface method 2`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public interface SomeInterface {
+                        long getLong();
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface SomeInterface2 {
+                        @Override default long getLong() {
+                            return 42;
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public class Foo implements SomeInterface, SomeInterface2 {
+                        @Override
+                        public long getLong() { return 0L; }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class Foo implements test.pkg.SomeInterface test.pkg.SomeInterface2 {
+                    ctor public Foo();
+                  }
+                  public abstract interface SomeInterface {
+                    method public abstract long getLong();
+                  }
+                  public abstract interface SomeInterface2 {
+                    method public default long getLong();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
     fun `Check basic @remove scenarios`() {
         // Test basic @remove handling for methods and fields
         check(
@@ -2024,7 +2362,7 @@
     @Test
     fun `Test include overridden @Deprecated even if annotated with @hide`() {
         check(
-            checkDoclava1 = true,
+            checkDoclava1 = false, // line numbers differ; they include comments; we point straight to modifier list
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -2039,6 +2377,12 @@
                         public String toString() {
                             return "Child";
                         }
+
+                        /**
+                         * @hide
+                         */
+                        public void hiddenApi() {
+                        }
                     }
                     """
                 ),
@@ -2071,6 +2415,18 @@
                 Ltest/pkg/Parent;
                 Ltest/pkg/Parent;-><init>()V
                 Ltest/pkg/Parent;->toString()Ljava/lang/String;
+            """,
+            dexApiMapping = """
+                Ltest/pkg/Child;-><init>()V
+                src/test/pkg/Child.java:2
+                Ltest/pkg/Child;->hiddenApi()V
+                src/test/pkg/Child.java:16
+                Ltest/pkg/Child;->toString()Ljava/lang/String;
+                src/test/pkg/Child.java:8
+                Ltest/pkg/Parent;-><init>()V
+                src/test/pkg/Parent.java:2
+                Ltest/pkg/Parent;->toString()Ljava/lang/String;
+                src/test/pkg/Parent.java:3
             """
         )
     }
@@ -2096,7 +2452,7 @@
             api = """
                 package test.pkg {
                   public final class -Foo {
-                    method public static void printHelloWorld(java.lang.String);
+                    method public static inline void printHelloWorld(java.lang.String);
                   }
                 }
                 """
@@ -2166,7 +2522,7 @@
         check(
             checkDoclava1 = true,
             extraArguments = arrayOf(
-                "--hide-package", "com.squareup.okhttp"
+                ARG_HIDE_PACKAGE, "com.squareup.okhttp"
             ),
             sourceFiles = *arrayOf(
                 java(
@@ -2495,58 +2851,41 @@
     }
 
     @Test
-    fun `Including private interfaces from types`() {
+    fun `Test KDoc suppress`() {
+        // Basic class; also checks that default constructor is made explicit
         check(
-            checkDoclava1 = true,
             sourceFiles = *arrayOf(
-                java("""package test.pkg1; interface Interface1 { }"""),
-                java("""package test.pkg1; abstract class Class1 { }"""),
-                java("""package test.pkg1; abstract class Class2 { }"""),
-                java("""package test.pkg1; abstract class Class3 { }"""),
-                java("""package test.pkg1; abstract class Class4 { }"""),
-                java("""package test.pkg1; abstract class Class5 { }"""),
-                java("""package test.pkg1; abstract class Class6 { }"""),
-                java("""package test.pkg1; abstract class Class7 { }"""),
-                java("""package test.pkg1; abstract class Class8 { }"""),
-                java("""package test.pkg1; abstract class Class9 { }"""),
                 java(
                     """
-                    package test.pkg1;
-
-                    import java.util.List;
-                    import java.util.Map;
-                    public abstract class Usage implements List<Class1> {
-                       <T extends java.lang.Comparable<? super T>> void sort(java.util.List<T> list) {}
-                       public Class3 myClass1 = null;
-                       public List<? extends Class4> myClass2 = null;
-                       public Map<String, ? extends Class5> myClass3 = null;
-                       public <T extends Class6> void mySort(List<Class7> list, T element) {}
-                       public void ellipsisType(Class8... myargs);
-                       public void arrayType(Class9[] myargs);
+                    package test.pkg;
+                    public class Foo {
+                        private Foo() { }
+                        /** @suppress */
+                        public void hidden() {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    /**
+                    * Some comment.
+                    * @suppress
+                    */
+                    public class Hidden {
+                        private Hidden() { }
+                        public void hidden() {
+                        }
+                        public class Inner {
+                        }
                     }
                     """
                 )
             ),
-
-            // TODO: Test annotations! (values, annotation classes, etc.)
-            warnings = """
-                    src/test/pkg1/Usage.java:12: warning: Parameter myargs references hidden type test.pkg1.Class9[]. [HiddenTypeParameter:121]
-                    src/test/pkg1/Usage.java:11: warning: Parameter myargs references hidden type test.pkg1.Class8.... [HiddenTypeParameter:121]
-                    src/test/pkg1/Usage.java:10: warning: Parameter list references hidden type class test.pkg1.Class7. [HiddenTypeParameter:121]
-                    src/test/pkg1/Usage.java:7: warning: Field Usage.myClass1 references hidden type test.pkg1.Class3. [HiddenTypeParameter:121]
-                    src/test/pkg1/Usage.java:8: warning: Field Usage.myClass2 references hidden type class test.pkg1.Class4. [HiddenTypeParameter:121]
-                    src/test/pkg1/Usage.java:9: warning: Field Usage.myClass3 references hidden type class test.pkg1.Class5. [HiddenTypeParameter:121]
-                    """,
             api = """
-                    package test.pkg1 {
-                      public abstract class Usage implements java.util.List {
-                        ctor public Usage();
-                        method public void arrayType(test.pkg1.Class9[]);
-                        method public void ellipsisType(test.pkg1.Class8...);
-                        method public <T extends test.pkg1.Class6> void mySort(java.util.List<test.pkg1.Class7>, T);
-                        field public test.pkg1.Class3 myClass1;
-                        field public java.util.List<? extends test.pkg1.Class4> myClass2;
-                        field public java.util.Map<java.lang.String, ? extends test.pkg1.Class5> myClass3;
+                    package test.pkg {
+                      public class Foo {
                       }
                     }
                 """
diff --git a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
index 70d8020..f056ef8 100644
--- a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
@@ -43,6 +43,94 @@
     }
 
     @Test
+    fun `Handle lambas as default values`() {
+        val source = """
+            // Signature format: $SIGNATURE_FORMAT
+            package androidx.collection {
+              public final class LruCacheKt {
+                ctor public LruCacheKt();
+                method public static <K, V> androidx.collection.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { null as V? }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });
+              }
+            }
+        """
+
+        check(
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            signatureSource = source,
+            includeSignatureVersion = true,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Handle enum constants as default values`() {
+        val source = """
+            // Signature format: $SIGNATURE_FORMAT
+            package test.pkg {
+              public final class Foo {
+                ctor public Foo();
+                method public android.graphics.Bitmap? drawToBitmap(android.view.View, android.graphics.Bitmap.Config config = android.graphics.Bitmap.Config.ARGB_8888);
+                method public void emptyLambda(kotlin.jvm.functions.Function0<kotlin.Unit> sizeOf = {});
+                method public void method1(int p = 42, Integer? int2 = null, int p1 = 42, String str = "hello world", java.lang.String... args);
+                method public void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE);
+                method public void method3(String str, int p, int int2 = double(int) + str.length);
+                field public static final test.pkg.Foo.Companion! Companion;
+              }
+              public static final class Foo.Companion {
+                method public int double(int p);
+                method public void print(test.pkg.Foo foo = test.pkg.Foo());
+              }
+              public final class LruCacheKt {
+                ctor public LruCacheKt();
+                method public static <K, V> android.util.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (V)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ ->  });
+              }
+            }
+            """
+
+        check(
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            signatureSource = source,
+            includeSignatureVersion = true,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Handle complex expressions as default values`() {
+        val source = """
+            // Signature format: $SIGNATURE_FORMAT
+            package androidx.paging {
+              public final class PagedListConfigKt {
+                ctor public PagedListConfigKt();
+                method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = PagedList.Config.MAX_SIZE_UNBOUNDED);
+              }
+              public final class PagedListKt {
+                ctor public PagedListKt();
+                method public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, Key? initialKey = null);
+              }
+            }
+            package test.pkg {
+              public final class Foo {
+                ctor public Foo();
+                method public void method1(int p = 42, Integer? int2 = null, int p1 = 42, String str = "hello world", java.lang.String... args);
+                method public void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE);
+                method public void method3(str: String = "unbalanced), string", str2: String = ",");
+              }
+            }
+        """
+
+        check(
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            signatureSource = source,
+            includeSignatureVersion = true,
+            api = source
+        )
+    }
+
+    @Test
     fun `Annotation signatures requiring more complicated token matching`() {
         val source = """
                 package test {
@@ -82,19 +170,25 @@
 
     @Test
     fun `Native and strictfp keywords`() {
-        val source = """
-                package test.pkg {
-                  public class MyTest {
-                    method public native float dotWithNormal(float, float, float);
-                    method public static strictfp double toDegrees(double);
-                  }
-                }
-                """
         check(
             compatibilityMode = false,
             outputKotlinStyleNulls = false,
-            signatureSource = source,
-            api = source
+            signatureSource = """
+                    package test.pkg {
+                      public class MyTest {
+                        method public native float dotWithNormal(float, float, float);
+                        method public static strictfp double toDegrees(double);
+                      }
+                    }
+                    """,
+            api = """
+                    package test.pkg {
+                      public class MyTest {
+                        method public float dotWithNormal(float, float, float);
+                        method public static double toDegrees(double);
+                      }
+                    }
+                    """
         )
     }
 
@@ -236,7 +330,7 @@
                 field public static java.util.List<java.lang.String> LIST;
               }
             }
-                """
+            """
 
         check(
             compatibilityMode = true,
@@ -374,6 +468,24 @@
     }
 
     @Test
+    fun `Annotations on packages`() {
+        val source = """
+                package @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) test.pkg {
+                  public abstract class Class1 {
+                    ctor public Class1();
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
     fun `Enums and annotations exported to compat`() {
         val source = """
                 package android.annotation {
@@ -442,11 +554,11 @@
                 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, java.lang.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(java.lang.String! = null, java.lang.String! = "(Hello) World", int = 42);
                   }
                 }
                 """
@@ -467,7 +579,7 @@
                   public @interface NonNull {
                     method public abstract int from() default java.lang.Integer.MIN_VALUE;
                     method public abstract double fromWithCast() default (double)java.lang.Float.NEGATIVE_INFINITY;
-                    method public abstract String! myString() default "This is a \"string\"";
+                    method public abstract String myString() default "This is a \"string\"";
                     method public abstract int to() default java.lang.Integer.MAX_VALUE;
                   }
                 }
@@ -480,4 +592,160 @@
             api = source
         )
     }
+
+    @Test
+    fun `Signatures with many annotations`() {
+        val source = """
+            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;
+              }
+            }
+            package test.pkg {
+              public class Test {
+                ctor public Test();
+                method @NonNull public Object compute();
+              }
+            }
+        """
+
+        check(
+            compatibilityMode = false,
+            signatureSource = source,
+            outputKotlinStyleNulls = false,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Kotlin Properties`() {
+        val source = """
+                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;
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Deprecated enum constant`() {
+        val source = """
+                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 {
+                    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;
+                    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;
+                    enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS;
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            outputKotlinStyleNulls = true,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Type parameters in v2 format`() {
+        val source = """
+                package androidx.collection {
+                  public class Constants {
+                    field public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
+                    field public static final char HEX_INPUT = 61184; // 0xef00 '\uef00'
+                    field protected int field00;
+                    field public static final boolean field01 = true;
+                    field public static final int field02 = 42; // 0x2a
+                    field public static final String field09 = "String with \"escapes\" and \u00a9...";
+                  }
+                  public class MyMap<Key, Value> {
+                    method public Key! getReplacement(Key!);
+                  }
+                }
+                package androidx.paging {
+                  public abstract class DataSource<Key, Value> {
+                    method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+                    method @AnyThread public void invalidate();
+                    method @WorkerThread public boolean isInvalid();
+                    method public abstract <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
+                    method public abstract <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
+                    method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+                  }
+                  public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key, Value> {
+                    method public abstract Key getKey(Value);
+                    method public boolean isContiguous();
+                    method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value>);
+                    method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value>);
+                    method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key>, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value>);
+                    method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
+                    method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
+                  }
+                }
+                """
+        check(
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            outputKotlinStyleNulls = true,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Signatures with reified in type parameters`() {
+        val source = """
+                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);
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Suspended methods`() {
+        val source = """
+                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);
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            api = source
+        )
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt b/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt
index 940447c..24b9595 100644
--- a/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt
+++ b/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt
@@ -80,7 +80,7 @@
                     }
                 """
             ),
-            extraArguments = arrayOf("--error", "NoArtifactData,BrokenArtifactFile"),
+            extraArguments = arrayOf(ARG_ERROR, "NoArtifactData,BrokenArtifactFile"),
             warnings = """
                 src/test/pkg/foo/Foo.java:2: error: Class test.pkg.foo.Foo belongs to multiple artifacts: my.library.group:foo:1.0.0 and my.library.group:bar:3.1.4 [BrokenArtifactFile:130]
                 src/test/pkg/foo/Foo.java:4: error: Class test.pkg.foo.Foo.Inner belongs to multiple artifacts: my.library.group:foo:1.0.0 and my.library.group:bar:3.1.4 [BrokenArtifactFile:130]
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
index 3b06bb9..06c359a 100644
--- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
@@ -25,13 +25,12 @@
     @Test
     fun `Change between class and interface`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 TESTROOT/load-api.txt:2: error: Class test.pkg.MyTest1 changed class/interface declaration [ChangedClass:23]
                 TESTROOT/load-api.txt:4: error: Class test.pkg.MyTest2 changed class/interface declaration [ChangedClass:23]
                 """,
             compatibilityMode = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class MyTest1 {
                   }
@@ -62,13 +61,12 @@
     @Test
     fun `Interfaces should not be dropped`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 TESTROOT/load-api.txt:2: error: Class test.pkg.MyTest1 changed class/interface declaration [ChangedClass:23]
                 TESTROOT/load-api.txt:4: error: Class test.pkg.MyTest2 changed class/interface declaration [ChangedClass:23]
                 """,
             compatibilityMode = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class MyTest1 {
                   }
@@ -99,14 +97,13 @@
     @Test
     fun `Ensure warnings for removed APIs`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                TESTROOT/previous-api.txt:3: error: Removed method test.pkg.MyTest1.method(Float) [RemovedMethod:9]
-                TESTROOT/previous-api.txt:4: error: Removed field test.pkg.MyTest1.field [RemovedField:10]
-                TESTROOT/previous-api.txt:6: error: Removed class test.pkg.MyTest2 [RemovedClass:8]
+                TESTROOT/current-api.txt:3: error: Removed method test.pkg.MyTest1.method(Float) [RemovedMethod:9]
+                TESTROOT/current-api.txt:4: error: Removed field test.pkg.MyTest1.field [RemovedField:10]
+                TESTROOT/current-api.txt:6: error: Removed class test.pkg.MyTest2 [RemovedClass:8]
                 """,
             compatibilityMode = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class MyTest1 {
                     method public Double method(Float);
@@ -132,7 +129,6 @@
     @Test
     fun `Flag invalid nullness changes`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 TESTROOT/load-api.txt:5: error: Attempted to remove @Nullable annotation from method test.pkg.MyTest.convert3(Float) [InvalidNullConversion:135]
                 TESTROOT/load-api.txt:5: error: Attempted to remove @Nullable annotation from parameter arg1 in test.pkg.MyTest.convert3(Float arg1) [InvalidNullConversion:135]
@@ -143,7 +139,7 @@
                 """,
             compatibilityMode = false,
             outputKotlinStyleNulls = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class MyTest {
                     method public Double convert1(Float);
@@ -174,7 +170,6 @@
     @Test
     fun `Kotlin Nullness`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 src/test/pkg/Outer.kt:5: error: Attempted to change method return from @NonNull to @Nullable: incompatible change for method test.pkg.Outer.method2(String,String) [InvalidNullConversion:135]
                 src/test/pkg/Outer.kt:5: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in test.pkg.Outer.method2(String string, String maybeString) [InvalidNullConversion:135]
@@ -186,7 +181,7 @@
             compatibilityMode = false,
             inputKotlinStyleNulls = true,
             outputKotlinStyleNulls = true,
-            previousApi = """
+            checkCompatibilityApi = """
                     package test.pkg {
                       public final class Outer {
                         ctor public Outer();
@@ -224,13 +219,12 @@
     @Test
     fun `Java Parameter Name Change`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 src/test/pkg/JavaClass.java:6: error: Attempted to remove parameter name from parameter newName in test.pkg.JavaClass.method1 in method test.pkg.JavaClass.method1 [ParameterNameChange:136]
                 src/test/pkg/JavaClass.java:7: error: Attempted to change parameter name from secondParameter to newName in method test.pkg.JavaClass.method2 [ParameterNameChange:136]
                 """,
             compatibilityMode = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class JavaClass {
                     ctor public JavaClass();
@@ -254,21 +248,20 @@
                 ),
                 supportParameterName
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation")
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
         )
     }
 
     @Test
     fun `Kotlin Parameter Name Change`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 src/test/pkg/KotlinClass.kt:4: error: Attempted to change parameter name from prevName to newName in method test.pkg.KotlinClass.method1 [ParameterNameChange:136]
                 """,
             compatibilityMode = false,
             inputKotlinStyleNulls = true,
             outputKotlinStyleNulls = true,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public final class KotlinClass {
                     ctor public KotlinClass();
@@ -293,13 +286,12 @@
     @Test
     fun `Add flag new methods but not overrides from platform`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/MyClass.java:6: warning: Added method test.pkg.MyClass.method2(String) [AddedMethod:4]
-                src/test/pkg/MyClass.java:7: warning: Added field test.pkg.MyClass.newField [AddedField:5]
+                src/test/pkg/MyClass.java:6: error: Added method test.pkg.MyClass.method2(String) [AddedMethod:4]
+                src/test/pkg/MyClass.java:7: error: Added field test.pkg.MyClass.newField [AddedField:5]
                 """,
             compatibilityMode = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class MyClass {
                     method public String method1(String);
@@ -327,12 +319,11 @@
     @Test
     fun `Remove operator`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 src/test/pkg/Foo.kt:4: error: Cannot remove `operator` modifier from method test.pkg.Foo.plus(String): Incompatible change [OperatorRemoval:137]
                 """,
             compatibilityMode = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public final class Foo {
                     ctor public Foo();
@@ -357,12 +348,11 @@
     @Test
     fun `Remove vararg`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 src/test/pkg/test.kt:3: error: Changing from varargs to array is an incompatible change: parameter x in test.pkg.TestKt.method2(int[] x) [VarargRemoval:139]
                 """,
             compatibilityMode = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public final class TestKt {
                     ctor public TestKt();
@@ -388,13 +378,12 @@
         // Adding final on class or method is incompatible; adding it on a parameter is fine.
         // Field is iffy.
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Java.java:4: warning: Method test.pkg.Java.method has added 'final' qualifier [AddedFinal:13]
-                src/test/pkg/Kotlin.kt:4: warning: Method test.pkg.Kotlin.method has added 'final' qualifier [AddedFinal:13]
+                src/test/pkg/Java.java:4: error: Method test.pkg.Java.method has added 'final' qualifier [AddedFinal:13]
+                src/test/pkg/Kotlin.kt:4: error: Method test.pkg.Kotlin.method has added 'final' qualifier [AddedFinal:13]
                 """,
             compatibilityMode = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class Java {
                     method public void method(int);
@@ -429,14 +418,215 @@
     }
 
     @Test
+    fun `Inherited final`() {
+        // Make sure that we correctly compare effectively final (inherited from surrounding class)
+        // between the signature file codebase and the real codebase
+        check(
+            warnings = """
+                """,
+            compatibilityMode = false,
+            checkCompatibilityApi = """
+                package test.pkg {
+                  public final class Cls extends test.pkg.Parent {
+                  }
+                  public class Parent {
+                    method public void method(int);
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+                        public final class Cls extends Parent {
+                            private Cls() { }
+                            @Override public void method(final int parameter) { }
+                        }
+                        """
+                ),
+                java(
+                    """
+                        package test.pkg;
+                        public class Parent {
+                            private Parent() { }
+                            public void method(final int parameter) { }
+                        }
+                        """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Implicit concrete`() {
+        // Doclava signature files sometimes leave out overridden methods of
+        // abstract methods. We don't want to list these as having changed
+        // their abstractness.
+        check(
+            warnings = """
+                """,
+            compatibilityMode = false,
+            checkCompatibilityApi = """
+                package test.pkg {
+                  public final class Cls extends test.pkg.Parent {
+                  }
+                  public class Parent {
+                    method public abstract void method(int);
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+                        public final class Cls extends Parent {
+                            private Cls() { }
+                            @Override public void method(final int parameter) { }
+                        }
+                        """
+                ),
+                java(
+                    """
+                        package test.pkg;
+                        public class Parent {
+                            private Parent() { }
+                            public abstract void method(final int parameter);
+                        }
+                        """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Implicit modifiers from inherited super classes`() {
+        check(
+            warnings = """
+                """,
+            compatibilityMode = false,
+            checkCompatibilityApi = """
+                package test.pkg {
+                  public final class Cls implements test.pkg.Interface {
+                    method public void method(int);
+                    method public final void method2(int);
+                  }
+                  public interface Interface {
+                    method public void method2(int);
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+                        public final class Cls extends HiddenParent implements Interface {
+                            private Cls() { }
+                            @Override public void method(final int parameter) { }
+                        }
+                        """
+                ),
+                java(
+                    """
+                        package test.pkg;
+                        class HiddenParent {
+                            private HiddenParent() { }
+                            public abstract void method(final int parameter) { }
+                            public final void method2(final int parameter) { }
+                        }
+                        """
+                ),
+                java(
+                    """
+                        package test.pkg;
+                        public interface Interface {
+                            void method2(final int parameter) { }
+                        }
+                        """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Wildcard comparisons`() {
+        // Doclava signature files sometimes leave out overridden methods of
+        // abstract methods. We don't want to list these as having changed
+        // their abstractness.
+        check(
+            warnings = """
+                """,
+            compatibilityMode = false,
+            checkCompatibilityApi = """
+                package test.pkg {
+                  public abstract class AbstractMap<K, V> implements java.util.Map {
+                    method public java.util.Set<K> keySet();
+                    method public V put(K, V);
+                    method public void putAll(java.util.Map<? extends K, ? extends V>);
+                  }
+                  public abstract class EnumMap<K extends java.lang.Enum<K>, V> extends test.pkg.AbstractMap  {
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+                        @SuppressWarnings({"ConstantConditions", "NullableProblems"})
+                        public abstract class AbstractMap<K, V> implements java.util.Map {
+                            private AbstractMap() { }
+                            public V put(K k, V v) { return null; }
+                            public java.util.Set<K> keySet() { return null; }
+                            public V put(K k, V v) { return null; }
+                            public void putAll(java.util.Map<? extends K, ? extends V> x) { }
+                        }
+                        """
+                ),
+                java(
+                    """
+                        package test.pkg;
+                        public abstract class EnumMap<K extends java.lang.Enum<K>, V> extends test.pkg.AbstractMap  {
+                            private EnumMap() { }
+                            public V put(K k, V v) { return null; }
+                        }
+                        """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Added constructor`() {
+        // Regression test for issue 116619591
+        check(
+            warnings = "src/test/pkg/AbstractMap.java:2: error: Added constructor test.pkg.AbstractMap() [AddedMethod:4]",
+            compatibilityMode = false,
+            checkCompatibilityApi = """
+                package test.pkg {
+                  public abstract class AbstractMap<K, V> implements java.util.Map {
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+                        @SuppressWarnings({"ConstantConditions", "NullableProblems"})
+                        public abstract class AbstractMap<K, V> implements java.util.Map {
+                        }
+                        """
+                )
+            )
+        )
+    }
+
+    @Test
     fun `Remove infix`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 src/test/pkg/Foo.kt:5: error: Cannot remove `infix` modifier from method test.pkg.Foo.add2(String): Incompatible change [InfixRemoval:138]
                 """,
             compatibilityMode = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public final class Foo {
                     ctor public Foo();
@@ -465,12 +655,11 @@
     @Test
     fun `Add seal`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Foo.kt: error: Cannot add `sealed` modifier to class test.pkg.Foo: Incompatible change [AddSealed:140]
+                src/test/pkg/Foo.kt: error: Cannot add 'sealed' modifier to class test.pkg.Foo: Incompatible change [AddSealed:140]
                 """,
             compatibilityMode = false,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class Foo {
                   }
@@ -480,8 +669,7 @@
                 kotlin(
                     """
                     package test.pkg
-                    sealed class Foo {
-                    }
+                    sealed class Foo
                     """
                 )
             )
@@ -491,13 +679,12 @@
     @Test
     fun `Remove default parameter`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 src/test/pkg/Foo.kt:7: error: Attempted to remove default value from parameter s1 in test.pkg.Foo.method4 in method test.pkg.Foo.method4 [DefaultValueChange:144]
                 """,
             compatibilityMode = false,
             inputKotlinStyleNulls = true,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public final class Foo {
                     ctor public Foo();
@@ -528,10 +715,9 @@
     @Test
     fun `Removing method or field when still available via inheritance is OK`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class Child extends test.pkg.Parent {
                     ctor public Child();
@@ -578,18 +764,18 @@
     @Test
     fun `Change field constant value, change field type`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Parent.java:5: warning: Field test.pkg.Parent.field2 has changed value from 2 to 42 [ChangedValue:17]
-                src/test/pkg/Parent.java:6: warning: Field test.pkg.Parent.field3 has changed type from int to char [ChangedType:16]
-                src/test/pkg/Parent.java:7: warning: Field test.pkg.Parent.field4 has added 'final' qualifier [AddedFinal:13]
-                src/test/pkg/Parent.java:8: warning: Field test.pkg.Parent.field5 has changed 'static' qualifier [ChangedStatic:12]
-                src/test/pkg/Parent.java:9: warning: Field test.pkg.Parent.field6 has changed 'transient' qualifier [ChangedTransient:14]
-                src/test/pkg/Parent.java:10: warning: Field test.pkg.Parent.field7 has changed 'volatile' qualifier [ChangedVolatile:15]
-                src/test/pkg/Parent.java:11: warning: Field test.pkg.Parent.field8 has changed deprecation state true --> false [ChangedDeprecated:24]
-                src/test/pkg/Parent.java:12: warning: Field test.pkg.Parent.field9 has changed deprecation state false --> true [ChangedDeprecated:24]
+                src/test/pkg/Parent.java:5: error: Field test.pkg.Parent.field2 has changed value from 2 to 42 [ChangedValue:17]
+                src/test/pkg/Parent.java:6: error: Field test.pkg.Parent.field3 has changed type from int to char [ChangedType:16]
+                src/test/pkg/Parent.java:7: error: Field test.pkg.Parent.field4 has added 'final' qualifier [AddedFinal:13]
+                src/test/pkg/Parent.java:8: error: Field test.pkg.Parent.field5 has changed 'static' qualifier [ChangedStatic:12]
+                src/test/pkg/Parent.java:9: error: Field test.pkg.Parent.field6 has changed 'transient' qualifier [ChangedTransient:14]
+                src/test/pkg/Parent.java:10: error: Field test.pkg.Parent.field7 has changed 'volatile' qualifier [ChangedVolatile:15]
+                src/test/pkg/Parent.java:11: error: Field test.pkg.Parent.field8 has changed deprecation state true --> false [ChangedDeprecated:24]
+                src/test/pkg/Parent.java:12: error: Field test.pkg.Parent.field9 has changed deprecation state false --> true [ChangedDeprecated:24]
+                src/test/pkg/Parent.java:19: error: Field test.pkg.Parent.field94 has changed value from 1 to 42 [ChangedValue:17]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class Parent {
                     ctor public Parent();
@@ -602,6 +788,10 @@
                     field public int field7;
                     field public deprecated int field8;
                     field public int field9;
+                    field public static final int field91 = 1; // 0x1
+                    field public static final int field92 = 1; // 0x1
+                    field public static final int field93 = 1; // 0x1
+                    field public static final int field94 = 1; // 0x1
                   }
                 }
                 """,
@@ -609,7 +799,7 @@
                 java(
                     """
                     package test.pkg;
-
+                    import android.annotation.SuppressLint;
                     public class Parent {
                         public static final int field1 = 1;  // UNCHANGED
                         public static final int field2 = 42; // CHANGED VALUE
@@ -620,6 +810,63 @@
                         public volatile int field7 = 7;      // ADDED VOLATILE
                         public int field8 = 8;               // REMOVED DEPRECATED
                         /** @deprecated */ @Deprecated public int field9 = 8;  // ADDED DEPRECATED
+                        @SuppressLint("ChangedValue")
+                        public static final int field91 = 42;// CHANGED VALUE: Suppressed
+                        @SuppressLint("ChangedValue:Field test.pkg.Parent.field92 has changed value from 1 to 42")
+                        public static final int field92 = 42;// CHANGED VALUE: Suppressed with same message
+                        @SuppressLint("ChangedValue: Field test.pkg.Parent.field93 has changed value from 1 to 42")
+                        public static final int field93 = 42;// CHANGED VALUE: Suppressed with same message
+                        @SuppressLint("ChangedValue:Field test.pkg.Parent.field94 has changed value from 10 to 1")
+                        public static final int field94 = 42;// CHANGED VALUE: Suppressed but with different message
+                    }
+                    """
+                ),
+                suppressLintSource
+            ),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "android.annotation")
+        )
+    }
+
+    @Test
+    fun `Change annotation default method value change`() {
+        check(
+            inputKotlinStyleNulls = true,
+            warnings = """
+                src/test/pkg/ExportedProperty.java:15: error: Method test.pkg.ExportedProperty.category has changed value from "" to nothing [ChangedValue:17]
+                src/test/pkg/ExportedProperty.java:14: error: Method test.pkg.ExportedProperty.floating has changed value from 1.0f to 1.1f [ChangedValue:17]
+                src/test/pkg/ExportedProperty.java:16: error: Method test.pkg.ExportedProperty.formatToHexString has changed value from nothing to false [ChangedValue:17]
+                src/test/pkg/ExportedProperty.java:13: error: Method test.pkg.ExportedProperty.prefix has changed value from "" to "hello" [ChangedValue:17]
+                """,
+            checkCompatibilityApi = """
+                package test.pkg {
+                  public @interface ExportedProperty {
+                    method public abstract boolean resolveId() default false;
+                    method public abstract float floating() default 1.0f;
+                    method public abstract String! prefix() default "";
+                    method public abstract String! category() default "";
+                    method public abstract boolean formatToHexString();
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import java.lang.annotation.ElementType;
+                    import java.lang.annotation.Retention;
+                    import java.lang.annotation.RetentionPolicy;
+                    import java.lang.annotation.Target;
+                    import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+                    @Target({ElementType.FIELD, ElementType.METHOD})
+                    @Retention(RetentionPolicy.RUNTIME)
+                    public @interface ExportedProperty {
+                        boolean resolveId() default false;            // UNCHANGED
+                        String prefix() default "hello";              // CHANGED VALUE
+                        float floating() default 1.1f;                // CHANGED VALUE
+                        String category();                            // REMOVED VALUE
+                        boolean formatToHexString() default false;    // ADDED VALUE
                     }
                     """
                 )
@@ -630,11 +877,10 @@
     @Test
     fun `Incompatible class change -- class to interface`() {
         check(
-            checkCompatibility = true,
             warnings = """
                 src/test/pkg/Parent.java:3: error: Class test.pkg.Parent changed class/interface declaration [ChangedClass:23]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class Parent {
                   }
@@ -656,12 +902,11 @@
     @Test
     fun `Incompatible class change -- change implemented interfaces`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Parent.java:3: warning: Class test.pkg.Parent no longer implements java.io.Closeable [RemovedInterface:11]
-                src/test/pkg/Parent.java:3: warning: Added interface java.util.List to class class test.pkg.Parent [AddedInterface:6]
+                src/test/pkg/Parent.java:3: error: Class test.pkg.Parent no longer implements java.io.Closeable [RemovedInterface:11]
+                src/test/pkg/Parent.java:3: error: Added interface java.util.List to class class test.pkg.Parent [AddedInterface:6]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class Parent implements java.io.Closeable, java.util.Map {
                   }
@@ -684,12 +929,11 @@
     @Test
     fun `Incompatible class change -- change qualifiers`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Parent.java:3: warning: Class test.pkg.Parent changed abstract qualifier [ChangedAbstract:20]
-                src/test/pkg/Parent.java:3: warning: Class test.pkg.Parent changed static qualifier [ChangedStatic:12]
+                src/test/pkg/Parent.java:3: error: Class test.pkg.Parent changed 'abstract' qualifier [ChangedAbstract:20]
+                src/test/pkg/Parent.java:3: error: Class test.pkg.Parent changed 'static' qualifier [ChangedStatic:12]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class Parent {
                   }
@@ -712,14 +956,13 @@
     @Test
     fun `Incompatible class change -- final`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Class1.java:3: warning: Class test.pkg.Class1 added final qualifier [AddedFinal:13]
-                TESTROOT/previous-api.txt:3: error: Removed constructor test.pkg.Class1() [RemovedMethod:9]
-                src/test/pkg/Class2.java:3: warning: Class test.pkg.Class2 added final qualifier but was previously uninstantiable and therefore could not be subclassed [AddedFinalUninstantiable:26]
-                src/test/pkg/Class3.java:3: warning: Class test.pkg.Class3 removed final qualifier [RemovedFinal:27]
+                src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 added 'final' qualifier [AddedFinal:13]
+                TESTROOT/current-api.txt:3: error: Removed constructor test.pkg.Class1() [RemovedMethod:9]
+                src/test/pkg/Class2.java:3: error: Class test.pkg.Class2 added 'final' qualifier but was previously uninstantiable and therefore could not be subclassed [AddedFinalUninstantiable:26]
+                src/test/pkg/Class3.java:3: error: Class test.pkg.Class3 removed 'final' qualifier [RemovedFinal:27]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class Class1 {
                       ctor public Class1();
@@ -765,12 +1008,11 @@
     @Test
     fun `Incompatible class change -- visibility`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Class1.java:3: warning: Class test.pkg.Class1 changed visibility from protected to public [ChangedScope:19]
-                src/test/pkg/Class2.java:3: warning: Class test.pkg.Class2 changed visibility from public to protected [ChangedScope:19]
+                src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 changed visibility from protected to public [ChangedScope:19]
+                src/test/pkg/Class2.java:3: error: Class test.pkg.Class2 changed visibility from public to protected [ChangedScope:19]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   protected class Class1 {
                   }
@@ -804,11 +1046,10 @@
     @Test
     fun `Incompatible class change -- deprecation`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Class1.java:3: warning: Class test.pkg.Class1 has changed deprecation state false --> true [ChangedDeprecated:24]
+                src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 has changed deprecation state false --> true [ChangedDeprecated:24]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class Class1 {
                   }
@@ -832,11 +1073,10 @@
     @Test
     fun `Incompatible class change -- superclass`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Class3.java:3: warning: Class test.pkg.Class3 superclass changed from java.lang.Char to java.lang.Number [ChangedSuperclass:18]
+                src/test/pkg/Class3.java:3: error: Class test.pkg.Class3 superclass changed from java.lang.Char to java.lang.Number [ChangedSuperclass:18]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class Class1 {
                   }
@@ -881,11 +1121,10 @@
     @Test
     fun `Incompatible class change -- type variables`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Class1.java:3: warning: Class test.pkg.Class1 changed number of type parameters from 1 to 2 [ChangedType:16]
+                src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 changed number of type parameters from 1 to 2 [ChangedType:16]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public class Class1<X> {
                   }
@@ -908,16 +1147,15 @@
     @Test
     fun `Incompatible method change -- modifiers`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/MyClass.java:5: warning: Method test.pkg.MyClass.myMethod2 has changed 'abstract' qualifier [ChangedAbstract:20]
-                src/test/pkg/MyClass.java:6: warning: Method test.pkg.MyClass.myMethod3 has changed 'static' qualifier [ChangedStatic:12]
-                src/test/pkg/MyClass.java:7: warning: Method test.pkg.MyClass.myMethod4 has changed deprecation state true --> false [ChangedDeprecated:24]
+                src/test/pkg/MyClass.java:5: error: Method test.pkg.MyClass.myMethod2 has changed 'abstract' qualifier [ChangedAbstract:20]
+                src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.myMethod3 has changed 'static' qualifier [ChangedStatic:12]
+                src/test/pkg/MyClass.java:7: error: Method test.pkg.MyClass.myMethod4 has changed deprecation state true --> false [ChangedDeprecated:24]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class MyClass {
-                      method public abstract void myMethod2();
+                      method public void myMethod2();
                       method public void myMethod3();
                       method deprecated public void myMethod4();
                   }
@@ -930,7 +1168,7 @@
 
                     public abstract class MyClass {
                         private MyClass() {}
-                        public native void myMethod2(); // Note that Errors.CHANGE_NATIVE is hidden by default
+                        public native abstract void myMethod2(); // Note that Errors.CHANGE_NATIVE is hidden by default
                         public static void myMethod3() {}
                         public void myMethod4() {}
                     }
@@ -943,12 +1181,11 @@
     @Test
     fun `Incompatible method change -- final`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/Outer.java:7: warning: Method test.pkg.Outer.Class1.method1 has added 'final' qualifier [AddedFinal:13]
-                src/test/pkg/Outer.java:19: warning: Method test.pkg.Outer.Class4.method4 has removed 'final' qualifier [RemovedFinal:27]
+                src/test/pkg/Outer.java:7: error: Method test.pkg.Outer.Class1.method1 has added 'final' qualifier [AddedFinal:13]
+                src/test/pkg/Outer.java:19: error: Method test.pkg.Outer.Class4.method4 has removed 'final' qualifier [RemovedFinal:27]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class Outer {
                   }
@@ -999,12 +1236,11 @@
     @Test
     fun `Incompatible method change -- visibility`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/MyClass.java:5: warning: Method test.pkg.MyClass.myMethod1 changed visibility from protected to public [ChangedScope:19]
-                src/test/pkg/MyClass.java:6: warning: Method test.pkg.MyClass.myMethod2 changed visibility from public to protected [ChangedScope:19]
+                src/test/pkg/MyClass.java:5: error: Method test.pkg.MyClass.myMethod1 changed visibility from protected to public [ChangedScope:19]
+                src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.myMethod2 changed visibility from public to protected [ChangedScope:19]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class MyClass {
                       method protected void myMethod1();
@@ -1031,15 +1267,14 @@
     @Test
     fun `Incompatible method change -- throws list`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/MyClass.java:6: warning: Method test.pkg.MyClass.method1 added thrown exception java.io.IOException [ChangedThrows:21]
-                src/test/pkg/MyClass.java:7: warning: Method test.pkg.MyClass.method2 no longer throws exception java.io.IOException [ChangedThrows:21]
-                src/test/pkg/MyClass.java:8: warning: Method test.pkg.MyClass.method3 no longer throws exception java.io.IOException [ChangedThrows:21]
-                src/test/pkg/MyClass.java:8: warning: Method test.pkg.MyClass.method3 no longer throws exception java.lang.NumberFormatException [ChangedThrows:21]
-                src/test/pkg/MyClass.java:8: warning: Method test.pkg.MyClass.method3 added thrown exception java.lang.UnsupportedOperationException [ChangedThrows:21]
+                src/test/pkg/MyClass.java:7: error: Method test.pkg.MyClass.method1 added thrown exception java.io.IOException [ChangedThrows:21]
+                src/test/pkg/MyClass.java:8: error: Method test.pkg.MyClass.method2 no longer throws exception java.io.IOException [ChangedThrows:21]
+                src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method3 no longer throws exception java.io.IOException [ChangedThrows:21]
+                src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method3 no longer throws exception java.lang.NumberFormatException [ChangedThrows:21]
+                src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method3 added thrown exception java.lang.UnsupportedOperationException [ChangedThrows:21]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class MyClass {
                       method public void finalize() throws java.lang.Throwable;
@@ -1054,6 +1289,7 @@
                     """
                     package test.pkg;
 
+                    @SuppressWarnings("RedundantThrows")
                     public abstract class MyClass {
                         private MyClass() {}
                         public void finalize() {}
@@ -1070,18 +1306,17 @@
     @Test
     fun `Incompatible method change -- return types`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/MyClass.java:5: warning: Method test.pkg.MyClass.method1 has changed return type from float to int [ChangedType:16]
-                src/test/pkg/MyClass.java:6: warning: Method test.pkg.MyClass.method2 has changed return type from java.util.List<Number> to java.util.List<java.lang.Integer> [ChangedType:16]
-                src/test/pkg/MyClass.java:7: warning: Method test.pkg.MyClass.method3 has changed return type from java.util.List<Integer> to java.util.List<java.lang.Number> [ChangedType:16]
-                src/test/pkg/MyClass.java:8: warning: Method test.pkg.MyClass.method4 has changed return type from String to String[] [ChangedType:16]
-                src/test/pkg/MyClass.java:9: warning: Method test.pkg.MyClass.method5 has changed return type from String[] to String[][] [ChangedType:16]
-                src/test/pkg/MyClass.java:10: warning: Method test.pkg.MyClass.method6 has changed return type from T (extends java.lang.Object) to U (extends java.lang.Number) [ChangedType:16]
-                src/test/pkg/MyClass.java:11: warning: Method test.pkg.MyClass.method7 has changed return type from T to Number [ChangedType:16]
-                src/test/pkg/MyClass.java:13: warning: Method test.pkg.MyClass.method9 has changed return type from X (extends java.lang.Object) to U (extends java.lang.Number) [ChangedType:16]
+                src/test/pkg/MyClass.java:5: error: Method test.pkg.MyClass.method1 has changed return type from float to int [ChangedType:16]
+                src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.method2 has changed return type from java.util.List<Number> to java.util.List<java.lang.Integer> [ChangedType:16]
+                src/test/pkg/MyClass.java:7: error: Method test.pkg.MyClass.method3 has changed return type from java.util.List<Integer> to java.util.List<java.lang.Number> [ChangedType:16]
+                src/test/pkg/MyClass.java:8: error: Method test.pkg.MyClass.method4 has changed return type from String to String[] [ChangedType:16]
+                src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method5 has changed return type from String[] to String[][] [ChangedType:16]
+                src/test/pkg/MyClass.java:10: error: Method test.pkg.MyClass.method6 has changed return type from T (extends java.lang.Object) to U (extends java.lang.Number) [ChangedType:16]
+                src/test/pkg/MyClass.java:11: error: Method test.pkg.MyClass.method7 has changed return type from T to Number [ChangedType:16]
+                src/test/pkg/MyClass.java:13: error: Method test.pkg.MyClass.method9 has changed return type from X (extends java.lang.Throwable) to U (extends java.lang.Number) [ChangedType:16]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class MyClass<T extends Number> {
                       method public float method1();
@@ -1122,13 +1357,12 @@
     @Test
     fun `Incompatible field change -- visibility and removing final`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/MyClass.java:5: warning: Field test.pkg.MyClass.myField1 changed visibility from protected to public [ChangedScope:19]
-                src/test/pkg/MyClass.java:6: warning: Field test.pkg.MyClass.myField2 changed visibility from public to protected [ChangedScope:19]
-                src/test/pkg/MyClass.java:7: warning: Field test.pkg.MyClass.myField3 has removed 'final' qualifier [RemovedFinal:27]
+                src/test/pkg/MyClass.java:5: error: Field test.pkg.MyClass.myField1 changed visibility from protected to public [ChangedScope:19]
+                src/test/pkg/MyClass.java:6: error: Field test.pkg.MyClass.myField2 changed visibility from public to protected [ChangedScope:19]
+                src/test/pkg/MyClass.java:7: error: Field test.pkg.MyClass.myField3 has removed 'final' qualifier [RemovedFinal:27]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class MyClass {
                       field protected int myField1;
@@ -1157,15 +1391,14 @@
     @Test
     fun `Adding classes, interfaces and packages, and removing these`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/MyClass.java:3: warning: Added class test.pkg.MyClass [AddedClass:3]
-                src/test/pkg/MyInterface.java:3: warning: Added class test.pkg.MyInterface [AddedInterface:6]
-                TESTROOT/previous-api.txt:2: error: Removed class test.pkg.MyOldClass [RemovedClass:8]
-                warning: Added package test.pkg2 [AddedPackage:2]
-                TESTROOT/previous-api.txt:5: error: Removed package test.pkg3 [RemovedPackage:7]
+                src/test/pkg/MyClass.java:3: error: Added class test.pkg.MyClass [AddedClass:3]
+                src/test/pkg/MyInterface.java:3: error: Added class test.pkg.MyInterface [AddedInterface:6]
+                TESTROOT/current-api.txt:2: error: Removed class test.pkg.MyOldClass [RemovedClass:8]
+                error: Added package test.pkg2 [AddedPackage:2]
+                TESTROOT/current-api.txt:5: error: Removed package test.pkg3 [RemovedPackage:7]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class MyOldClass {
                   }
@@ -1209,11 +1442,10 @@
     @Test
     fun `Test removing public constructor`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                TESTROOT/previous-api.txt:3: error: Removed constructor test.pkg.MyClass() [RemovedMethod:9]
+                TESTROOT/current-api.txt:3: error: Removed constructor test.pkg.MyClass() [RemovedMethod:9]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class MyClass {
                     ctor public MyClass();
@@ -1237,11 +1469,10 @@
     @Test
     fun `Test type variables from text signature files`() {
         check(
-            checkCompatibility = true,
             warnings = """
-                src/test/pkg/MyClass.java:8: warning: Method test.pkg.MyClass.myMethod4 has changed return type from S (extends java.lang.Object) to S (extends java.lang.Float) [ChangedType:16]
+                src/test/pkg/MyClass.java:8: error: Method test.pkg.MyClass.myMethod4 has changed return type from S (extends java.lang.Object) to S (extends java.lang.Float) [ChangedType:16]
                 """,
-            previousApi = """
+            checkCompatibilityApi = """
                 package test.pkg {
                   public abstract class MyClass<T extends test.pkg.Number,T_SPLITR> {
                     method public T myMethod1();
@@ -1286,15 +1517,12 @@
     @Test
     fun `Test Kotlin extensions`() {
         check(
-            checkCompatibility = true,
-            extraArguments = arrayOf(
-                "--compatible-output=no",
-                "--omit-common-packages=yes",
-                "--output-kotlin-nulls=yes",
-                "--input-kotlin-nulls=yes"
-            ),
+            inputKotlinStyleNulls = true,
+            outputKotlinStyleNulls = true,
+            omitCommonPackages = true,
+            compatibilityMode = false,
             warnings = "",
-            previousApi = """
+            checkCompatibilityApi = """
                 package androidx.content {
                   public final class ContentValuesKt {
                     ctor public ContentValuesKt();
@@ -1336,6 +1564,560 @@
         )
     }
 
+    @Test
+    fun `Test Kotlin type bounds`() {
+        check(
+            inputKotlinStyleNulls = false,
+            outputKotlinStyleNulls = true,
+            omitCommonPackages = true,
+            compatibilityMode = false,
+            warnings = "",
+            checkCompatibilityApi = """
+                package androidx.navigation {
+                  public final class NavDestination {
+                    ctor public NavDestination();
+                  }
+                  public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
+                    ctor public NavDestinationBuilder(int id);
+                    method public D build();
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    package androidx.navigation
+
+                    open class NavDestinationBuilder<out D : NavDestination>(
+                            id: Int
+                    ) {
+                        open fun build(): D {
+                            TODO()
+                        }
+                    }
+
+                    class NavDestination
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test inherited methods`() {
+        check(
+            warnings = """
+                """,
+            checkCompatibilityApi = """
+                package test.pkg {
+                  public class Child1 extends test.pkg.Parent {
+                  }
+                  public class Child2 extends test.pkg.Parent {
+                    method public void method0(java.lang.String, int);
+                    method public void method4(java.lang.String, int);
+                  }
+                  public class Child3 extends test.pkg.Parent {
+                    method public void method1(java.lang.String, int);
+                    method public void method2(java.lang.String, int);
+                  }
+                  public class Parent {
+                    method public void method1(java.lang.String, int);
+                    method public void method2(java.lang.String, int);
+                    method public void method3(java.lang.String, int);
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public class Child1 extends Parent {
+                        private Child1() {}
+                        @Override
+                        public void method1(String first, int second) {
+                        }
+                        @Override
+                        public void method2(String first, int second) {
+                        }
+                        @Override
+                        public void method3(String first, int second) {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    public class Child2 extends Parent {
+                        private Child2() {}
+                        @Override
+                        public void method0(String first, int second) {
+                        }
+                        @Override
+                        public void method1(String first, int second) {
+                        }
+                        @Override
+                        public void method2(String first, int second) {
+                        }
+                        @Override
+                        public void method3(String first, int second) {
+                        }
+                        @Override
+                        public void method4(String first, int second) {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    public class Child3 extends Parent {
+                        private Child3() {}
+                        @Override
+                        public void method1(String first, int second) {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public class Parent {
+                        private Parent() { }
+                        public void method1(String first, int second) {
+                        }
+                        public void method2(String first, int second) {
+                        }
+                        public void method3(String first, int second) {
+                        }
+                    }
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Partial text file which references inner classes not listed elsewhere`() {
+        // This happens in system and test files where we only include APIs that differ
+        // from the base IDE. When parsing these code bases we need to gracefully handle
+        // references to inner classes.
+        check(
+            includeSystemApiAnnotations = true,
+            warnings = """
+                src/test/pkg/Bar.java:17: error: Added method test.pkg.Bar.Inner1.Inner2.addedMethod() to the system API [AddedMethod:4]
+                TESTROOT/current-api.txt:4: error: Removed method test.pkg.Bar.Inner1.Inner2.removedMethod() [RemovedMethod:9]
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package other.pkg;
+
+                    public class MyClass {
+                        public class MyInterface {
+                            public void test() { }
+                        }
+                    }
+                    """
+                ).indented(),
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.SystemApi;
+
+                    public class Bar {
+                        public class Inner1 {
+                            private Inner1() { }
+                            @SuppressWarnings("JavaDoc")
+                            public class Inner2 {
+                                private Inner2() { }
+
+                                /**
+                                 * @hide
+                                 */
+                                @SystemApi
+                                public void method() { }
+
+                                /**
+                                 * @hide
+                                 */
+                                @SystemApi
+                                public void addedMethod() { }
+                            }
+                        }
+                    }
+                    """
+                ),
+                systemApiSource
+            ),
+
+            extraArguments = arrayOf(
+                ARG_SHOW_ANNOTATION, "android.annotation.TestApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
+            ),
+
+            checkCompatibilityApi =
+            """
+                package test.pkg {
+                  public class Bar.Inner1.Inner2 {
+                    method public void method();
+                    method public void removedMethod();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Test verifying simple removed API`() {
+        check(
+            warnings = """
+                src/test/pkg/Bar.java:8: error: Added method test.pkg.Bar.newlyRemoved() to the removed API [AddedMethod:4]
+                """,
+            checkCompatibilityRemovedApiCurrent = """
+                package test.pkg {
+                  public class Bar {
+                    ctor public Bar();
+                    method public void removedMethod();
+                  }
+                  public class Bar.Inner {
+                    ctor public Bar.Inner();
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("JavaDoc")
+                    public class Bar {
+                        /** @removed */
+                        public Bar() { }
+                        // No longer removed: /** @removed */
+                        public void removedMethod() { }
+                        /** @removed */
+                        public void newlyRemoved() { }
+
+                        public void newlyAdded() { }
+
+                        /** @removed */
+                        public class Inner { }
+                    }
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test verifying removed API`() {
+        check(
+            warnings = """
+                """,
+            checkCompatibilityRemovedApiCurrent = """
+                package test.pkg {
+                  public class Bar {
+                    ctor public Bar();
+                    method public void removedMethod();
+                    field public int removedField;
+                  }
+                  public class Bar.Inner {
+                    ctor public Bar.Inner();
+                  }
+                  public class Bar.Inner2.Inner3.Inner4 {
+                    ctor public Bar.Inner2.Inner3.Inner4();
+                  }
+                  public class Bar.Inner5.Inner6.Inner7 {
+                    field public int removed;
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("JavaDoc")
+                    public class Bar {
+                        /** @removed */
+                        public Bar() { }
+                        public int field;
+                        public void test() { }
+                        /** @removed */
+                        public int removedField;
+                        /** @removed */
+                        public void removedMethod() { }
+                        /** @removed and @hide - should not be listed */
+                        public int hiddenField;
+
+                        /** @removed */
+                        public class Inner { }
+
+                        public class Inner2 {
+                            public class Inner3 {
+                                /** @removed */
+                                public class Inner4 { }
+                            }
+                        }
+
+                        public class Inner5 {
+                            public class Inner6 {
+                                public class Inner7 {
+                                    /** @removed */
+                                    public int removed;
+                                }
+                            }
+                        }
+                    }
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Test release compatibility checking`() {
+        // Different checks are enforced for current vs release API comparisons:
+        // we don't flag AddedClasses etc. Removed classes *are* enforced.
+        check(
+            warnings = """
+                src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 added 'final' qualifier [AddedFinal:13]
+                TESTROOT/released-api.txt:3: error: Removed constructor test.pkg.Class1() [RemovedMethod:9]
+                src/test/pkg/MyClass.java:5: warning: Method test.pkg.MyClass.myMethod2 has changed 'abstract' qualifier [ChangedAbstract:20]
+                src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.myMethod3 has changed 'static' qualifier [ChangedStatic:12]
+                TESTROOT/released-api.txt:14: error: Removed class test.pkg.MyOldClass [RemovedClass:8]
+                TESTROOT/released-api.txt:17: error: Removed package test.pkg3 [RemovedPackage:7]
+                """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  public class Class1 {
+                      ctor public Class1();
+                  }
+                  public class Class2 {
+                  }
+                  public final class Class3 {
+                  }
+                  public abstract class MyClass {
+                      method public void myMethod2();
+                      method public void myMethod3();
+                      method deprecated public void myMethod4();
+                  }
+                  public abstract class MyOldClass {
+                  }
+                }
+                package test.pkg3 {
+                  public abstract class MyOldClass {
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public final class Class1 {
+                        private Class1() {}
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    public final class Class2 {
+                        private Class2() {}
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    public class Class3 {
+                        private Class3() {}
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    public abstract class MyNewClass {
+                        private MyNewClass() {}
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    public abstract class MyClass {
+                        private MyClass() {}
+                        public native abstract void myMethod2(); // Note that Errors.CHANGE_NATIVE is hidden by default
+                        public static void myMethod3() {}
+                        public void myMethod4() {}
+                    }
+                    """
+                )
+            )
+        )
+    }
+
+    @Test
+    fun `Implicit nullness`() {
+        check(
+            compatibilityMode = false,
+            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 {
+                    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;
+                    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;
+                    enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS;
+                  }
+                }
+                """,
+
+            sourceFiles = *arrayOf(
+                restrictToSource
+            )
+        )
+    }
+
+    @Test
+    fun `Java String constants`() {
+        check(
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            checkCompatibilityApi = """
+                package androidx.browser.browseractions {
+                  public class BrowserActionsIntent {
+                    field public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+                  }
+                }
+                """,
+
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                     package androidx.browser.browseractions;
+                     public class BrowserActionsIntent {
+                        private BrowserActionsIntent() { }
+                        public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+
+                     }
+                    """
+                ).indented()
+            )
+        )
+    }
+
+    @Test
+    fun `Classes with maps`() {
+        check(
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            checkCompatibilityApi = """
+                // Signature format: 2.0
+                package androidx.collection {
+                  public class SimpleArrayMap<K, V> {
+                  }
+                }
+                """,
+
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package androidx.collection;
+
+                    public class SimpleArrayMap<K, V> {
+                        private SimpleArrayMap() { }
+                    }
+                    """
+                ).indented()
+            )
+        )
+    }
+
+    @Test
+    fun `Referencing type parameters in types`() {
+        check(
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            checkCompatibilityApi = """
+                // Signature format: 2.0
+                package androidx.collection {
+                  public class MyMap<Key, Value> {
+                    ctor public MyMap();
+                    field public Key! myField;
+                    method public Key! getReplacement(Key!);
+                  }
+                }
+                """,
+
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package androidx.collection;
+
+                    public class MyMap<Key, Value> {
+                        public Key getReplacement(Key key) { return null; }
+                        public Key myField = null;
+                    }
+                    """
+                ).indented()
+            )
+        )
+    }
+
+    @Test
+    fun `Adding and removing reified`() {
+        check(
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            warnings = """
+            src/test/pkg/test.kt:5: error: Method test.pkg.TestKt.add made type variable T reified: incompatible change [ChangedThrows:21]
+            src/test/pkg/test.kt:8: error: Method test.pkg.TestKt.two made type variable S reified: incompatible change [ChangedThrows:21]
+            """,
+            checkCompatibilityApi = """
+                package test.pkg {
+                  public final class TestKt {
+                    ctor public TestKt();
+                    method public static inline <T> void add(T! t);
+                    method public static inline <reified T> void remove(T! t);
+                    method public static inline <reified T> void unchanged(T! t);
+                    method public static inline <S, reified T> void two(S! s, T! t);
+                  }
+                }
+                """,
+
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    @file:Suppress("NOTHING_TO_INLINE", "RedundantVisibilityModifier", "unused")
+
+                    package test.pkg
+
+                    inline fun <reified T> add(t: T) { }
+                    inline fun <T> remove(t: T) { }
+                    inline fun <reified T> unchanged(t: T) { }
+                    inline fun <reified S, T> two(s: S, t: T) { }
+                    """
+                ).indented()
+            )
+        )
+    }
+
     @Ignore("Not currently working: we're getting the wrong PSI results; I suspect caching across the two codebases")
     @Test
     fun `Test All Android API levels`() {
@@ -1448,17 +2230,16 @@
 
             check(
                 checkDoclava1 = false,
-                checkCompatibility = true,
                 extraArguments = arrayOf(
                     "--omit-locations",
-                    "--hide",
+                    ARG_HIDE,
                     suppressLevels[apiLevel]
                         ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass" +
                         (if ((apiLevel == 19 || apiLevel == 20) && loadPrevAsSignature) ",ChangedType" else "")
 
                 ),
                 warnings = expected[apiLevel]?.trimIndent() ?: "",
-                previousApi = previousApi,
+                checkCompatibilityApi = previousApi,
                 apiJar = current
             )
 
@@ -1476,15 +2257,14 @@
 
                 check(
                     checkDoclava1 = false,
-                    checkCompatibility = true,
                     extraArguments = arrayOf(
                         "--omit-locations",
-                        "--hide",
+                        ARG_HIDE,
                         suppressLevels[apiLevel]
                             ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass"
                     ),
                     warnings = expected[apiLevel]?.trimIndent() ?: "",
-                    previousApi = previousSignatureApi,
+                    checkCompatibilityApi = previousSignatureApi,
                     apiJar = current
                 )
             }
diff --git a/src/test/java/com/android/tools/metalava/CoreApiTest.kt b/src/test/java/com/android/tools/metalava/CoreApiTest.kt
new file mode 100644
index 0000000..9d1cec3
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/CoreApiTest.kt
@@ -0,0 +1,328 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("JavaDoc", "DanglingJavadoc")
+
+package com.android.tools.metalava
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import org.junit.Test
+
+/** Test to explore hidden versus public APIs via annotations */
+class CoreApiTest : DriverTest() {
+    @Test
+    fun `Hidden with hide annotation`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                      /**
+                       * Hide everything in this package:
+                       */
+                      @libcore.api.LibCoreHidden
+                      package test.pkg;
+                      """
+                ).indented(),
+
+                java(
+                    """
+                    package test.pkg;
+                    // Not included: hidden by default from package annotation
+                    public class NotExposed {
+                    }
+                    """
+                ).indented(),
+
+                java(
+                    """
+                    package test.pkg;
+                    import libcore.api.IntraCoreApi;
+
+                    /**
+                     * Included because it is annotated with a --show-single-annotation
+                     */
+                    @libcore.api.LibCoreHidden
+                    @IntraCoreApi
+                    public class Exposed {
+                        public void stillHidden() { }
+                        public String stillHidden;
+                        @IntraCoreApi
+                        public void exposed() { }
+                        @IntraCoreApi
+                        public String exposed;
+
+                        public class StillHidden {
+                        }
+                    }
+                    """
+                ).indented(),
+
+                libcoreCoreApi,
+                libcoreCoreHidden
+            ),
+            checkDoclava1 = false, // doclava does not have non-recursive show annotation
+            api =
+            """
+                package libcore.api {
+                  public abstract class IntraCoreApi implements java.lang.annotation.Annotation {
+                  }
+                }
+                package test.pkg {
+                  public class Exposed {
+                    method public void exposed();
+                    field public java.lang.String exposed;
+                  }
+                }
+                """,
+            stubs = arrayOf(
+                """
+                /**
+                 * Hide everything in this package:
+                 */
+                package test.pkg;
+                """,
+                // Specify target with [] since NotExposed is not included in the stubs, so
+                // matching up stub expected files here with source inputs doesn't match
+                """
+                [test/pkg/Exposed.java]
+                package test.pkg;
+                /**
+                 * Included because it is annotated with a --show-single-annotation
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Exposed {
+                Exposed() { throw new RuntimeException("Stub!"); }
+                public void exposed() { throw new RuntimeException("Stub!"); }
+                public java.lang.String exposed;
+                }
+                """
+            ),
+            extraArguments = arrayOf(
+                ARG_SHOW_SINGLE_ANNOTATION, "libcore.api.IntraCoreApi",
+                ARG_HIDE_ANNOTATION, "libcore.api.LibCoreHidden"
+            )
+        )
+    }
+
+    @Test
+    fun `Hidden with package javadoc and hiding default constructor explicitly`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                      /**
+                       * Hide everything in this package:
+                       * @hide
+                       */
+                      package test.pkg;
+                      """
+                ).indented(),
+
+                java(
+                    """
+                    package test.pkg;
+                    // Not included: hidden by default from package annotation
+                    public class NotExposed {
+                    }
+                    """
+                ).indented(),
+
+                java(
+                    """
+                    package test.pkg;
+                    import libcore.api.IntraCoreApi;
+
+                    /**
+                     * Included because it is annotated with a --show-single-annotation
+                     * @hide
+                     */
+                    @IntraCoreApi
+                    public class Exposed {
+                        /** @hide */
+                        public Exposed() { }
+                        public void stillHidden() { }
+                        @IntraCoreApi
+                        public void exposed() { }
+
+                        public class StillHidden {
+                        }
+                    }
+                    """
+                ).indented(),
+
+                libcoreCoreApi,
+                libcoreCoreHidden
+            ),
+            checkDoclava1 = false, // doclava does not have non-recursive show annotation
+            api =
+            """
+                package libcore.api {
+                  public abstract class IntraCoreApi implements java.lang.annotation.Annotation {
+                  }
+                }
+                package test.pkg {
+                  public class Exposed {
+                    method public void exposed();
+                  }
+                }
+                """,
+            stubs = arrayOf(
+                """
+                /**
+                 * Hide everything in this package:
+                 * @hide
+                 */
+                package test.pkg;
+                """,
+                // Specify target with [] since NotExposed is not included in the stubs, so
+                // matching up stub expected files here with source inputs doesn't match
+                """
+                [test/pkg/Exposed.java]
+                package test.pkg;
+                /**
+                 * Included because it is annotated with a --show-single-annotation
+                 * @hide
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Exposed {
+                /** @hide */
+                Exposed() { throw new RuntimeException("Stub!"); }
+                public void exposed() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            ),
+            extraArguments = arrayOf(
+                ARG_SHOW_SINGLE_ANNOTATION, "libcore.api.IntraCoreApi",
+                ARG_HIDE_ANNOTATION, "libcore.api.LibCoreHidden"
+            )
+        )
+    }
+
+    @Test
+    fun `Complain if annotating a member and the surrounding class is not included`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                      /**
+                       * Hide everything in this package:
+                       * @hide
+                       */
+                      package test.pkg;
+                      """
+                ).indented(),
+
+                java(
+                    """
+                    package test.pkg;
+                    import libcore.api.IntraCoreApi;
+
+                    /**
+                    * Included because it is annotated with a --show-single-annotation
+                    * @hide
+                    */
+                    public class Exposed {
+                        public void stillHidden() { }
+                        public String stillHidden;
+                        @IntraCoreApi // error: can only expose methods in class also exposed
+                        public void exposed() { }
+
+                        @IntraCoreApi
+                        public String exposed;
+
+                        @IntraCoreApi // error: can only expose inner classes in exported outer class
+                        public class StillHidden {
+                        }
+                    }
+                    """
+                ).indented(),
+
+                libcoreCoreApi,
+                libcoreCoreHidden
+            ),
+            checkDoclava1 = false, // doclava does not have non-recursive show annotation
+            api =
+            """
+                package libcore.api {
+                  public abstract class IntraCoreApi implements java.lang.annotation.Annotation {
+                  }
+                }
+                """,
+            extraArguments = arrayOf(
+                ARG_SHOW_SINGLE_ANNOTATION, "libcore.api.IntraCoreApi",
+                ARG_HIDE_ANNOTATION, "libcore.api.LibCoreHidden"
+            ),
+            warnings = """
+            src/test/pkg/Exposed.java:11: error: Attempting to unhide method test.pkg.Exposed.exposed(), but surrounding class test.pkg.Exposed is hidden and should also be annotated with @libcore.api.IntraCoreApi [ShowingMemberInHiddenClass:156]
+            src/test/pkg/Exposed.java:14: error: Attempting to unhide field test.pkg.Exposed.exposed, but surrounding class test.pkg.Exposed is hidden and should also be annotated with @libcore.api.IntraCoreApi [ShowingMemberInHiddenClass:156]
+            src/test/pkg/Exposed.java:17: error: Attempting to unhide class test.pkg.Exposed.StillHidden, but surrounding class test.pkg.Exposed is hidden and should also be annotated with @libcore.api.IntraCoreApi [ShowingMemberInHiddenClass:156]
+            """
+        )
+    }
+}
+
+val libcoreCoreApi: TestFile = TestFiles.java(
+    """
+    package libcore.api;
+
+    import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+    import static java.lang.annotation.ElementType.CONSTRUCTOR;
+    import static java.lang.annotation.ElementType.FIELD;
+    import static java.lang.annotation.ElementType.METHOD;
+    import static java.lang.annotation.ElementType.PACKAGE;
+    import static java.lang.annotation.ElementType.TYPE;
+
+    import java.lang.annotation.Retention;
+    import java.lang.annotation.RetentionPolicy;
+    import java.lang.annotation.Target;
+
+    /**
+     * @hide
+     */
+    @SuppressWarnings("ALL")
+    @IntraCoreApi // @IntraCoreApi is itself part of the intra-core API
+    @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface IntraCoreApi {
+    }
+    """
+).indented()
+
+val libcoreCoreHidden: TestFile = TestFiles.java(
+    """
+    package libcore.api;
+
+    import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+    import static java.lang.annotation.ElementType.CONSTRUCTOR;
+    import static java.lang.annotation.ElementType.FIELD;
+    import static java.lang.annotation.ElementType.METHOD;
+    import static java.lang.annotation.ElementType.PACKAGE;
+    import static java.lang.annotation.ElementType.TYPE;
+
+    import java.lang.annotation.Retention;
+    import java.lang.annotation.RetentionPolicy;
+    import java.lang.annotation.Target;
+
+    /**
+     * @hide
+     */
+    @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LibCoreHidden {
+    }
+    """
+).indented()
diff --git a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
index 0b5b239..2c6b1e8 100644
--- a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
+++ b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
@@ -5,7 +5,6 @@
 import com.google.common.io.Files
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
-import org.junit.Ignore
 import org.junit.Test
 import java.io.File
 
@@ -49,19 +48,22 @@
                  * @param factor2 This value must never be {@code null}.
                  * @return This value may be {@code null}.
                  */
-                @androidx.annotation.Nullable public java.lang.Double method1(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.Nullable
+                public java.lang.Double method1(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
                 /**
                  * These are the docs for method2. It can sometimes return null.
                  * @param factor1 This value must never be {@code null}.
                  * @param factor2 This value must never be {@code null}.
                  */
-                @androidx.annotation.Nullable public java.lang.Double method2(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.Nullable
+                public java.lang.Double method2(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
                 /**
                  * @param factor1 This value must never be {@code null}.
                  * @param factor2 This value must never be {@code null}.
                  * @return This value may be {@code null}.
                  */
-                @androidx.annotation.Nullable public java.lang.Double method3(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.Nullable
+                public java.lang.Double method3(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
                 }
                 """
             )
@@ -102,7 +104,10 @@
                  * to be a String resource reference (e.g.&nbsp;{@code android.R.string.ok}).
                  */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface StringRes {
+                @java.lang.annotation.Documented
+                @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)
+                @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE})
+                public @interface StringRes {
                 }
                 """
             )
@@ -116,21 +121,25 @@
                 java(
                     """
                     package test.pkg;
-                    /** This is an API for Andriod */
+                    /** This is an API for Andriod. Replace all occurrences: Andriod. */
                     public class Foo {
+                        /** Ignore matches within words: xAndriodx */
+                        public Foo() {
+                        }
                     }
                     """
                 )
             ),
             checkCompilation = true,
             checkDoclava1 = false,
-            warnings = "src/test/pkg/Foo.java:2: warning: Replaced Andriod with Android in documentation for class test.pkg.Foo [Typo:131]",
+            warnings = "src/test/pkg/Foo.java:2: warning: Replaced Andriod with Android in the documentation for class test.pkg.Foo [Typo:131]",
             stubs = arrayOf(
                 """
                 package test.pkg;
-                /** This is an API for Android */
+                /** This is an API for Android. Replace all occurrences: Android. */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
                 public class Foo {
+                /** Ignore matches within words: xAndriodx */
                 public Foo() { throw new RuntimeException("Stub!"); }
                 }
                 """
@@ -200,20 +209,25 @@
                 /**
                  * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
                  */
-                @androidx.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void test1() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+                public void test1() { throw new RuntimeException("Stub!"); }
                 /**
                  * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
                  */
-                @androidx.annotation.RequiresPermission(allOf=android.Manifest.permission.ACCESS_COARSE_LOCATION) public void test2() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RequiresPermission(allOf=android.Manifest.permission.ACCESS_COARSE_LOCATION)
+                public void test2() { throw new RuntimeException("Stub!"); }
                 /**
                  * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
                  */
-                @androidx.annotation.RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void test3() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION})
+                public void test3() { throw new RuntimeException("Stub!"); }
                 /**
                  * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} and {@link android.Manifest.permission#ACCOUNT_MANAGER}
                  */
-                @androidx.annotation.RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCOUNT_MANAGER}) public void test4() { throw new RuntimeException("Stub!"); }
-                @androidx.annotation.RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true) public void test5() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCOUNT_MANAGER})
+                public void test4() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true)
+                public void test5() { throw new RuntimeException("Stub!"); }
                 }
                 """
             )
@@ -257,15 +271,18 @@
                  * @param range2 Value is 20 or greater
                  * @return Value is 10 or greater
                  */
-                @androidx.annotation.IntRange(from=10) public int test1(@androidx.annotation.IntRange(from=20) int range2) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.IntRange(from=10)
+                public int test1(@androidx.annotation.IntRange(from=20) int range2) { throw new RuntimeException("Stub!"); }
                 /**
                  * @return Value is between 10 and 20 inclusive
                  */
-                @androidx.annotation.IntRange(from=10, to=20) public int test2() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.IntRange(from=10, to=20)
+                public int test2() { throw new RuntimeException("Stub!"); }
                 /**
                  * @return Value is 100 or less
                  */
-                @androidx.annotation.IntRange(to=100) public int test3() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.IntRange(to=100)
+                public int test3() { throw new RuntimeException("Stub!"); }
                 }
                 """
             )
@@ -296,15 +313,21 @@
             stubs = arrayOf(
                 """
                 package test.pkg;
-                /** Methods in this class must be called on the thread that originally created
-                 *            this UI element, unless otherwise noted. This is typically the
-                 *            main thread of your app. * */
+                /**
+                 * Methods in this class must be called on the thread that originally created
+                 * this UI element, unless otherwise noted. This is typically the
+                 * main thread of your app. *
+                 */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @androidx.annotation.UiThread public class RangeTest {
+                @androidx.annotation.UiThread
+                public class RangeTest {
                 public RangeTest() { throw new RuntimeException("Stub!"); }
-                /** This method may take several seconds to complete, so it should
-                 *            only be called from a worker thread. */
-                @androidx.annotation.WorkerThread public int test1() { throw new RuntimeException("Stub!"); }
+                /**
+                 * This method may take several seconds to complete, so it should
+                 * only be called from a worker thread.
+                 */
+                @androidx.annotation.WorkerThread
+                public int test1() { throw new RuntimeException("Stub!"); }
                 }
                 """
             )
@@ -338,12 +361,80 @@
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
                 public class RangeTest {
                 public RangeTest() { throw new RuntimeException("Stub!"); }
-                /** This method must be called on the thread that originally created
-                 *            this UI element. This is typically the main thread of your app.
+                /**
+                 * This method must be called on the thread that originally created
+                 * this UI element. This is typically the main thread of your app.
+                 * <br>
                  * This method may take several seconds to complete, so it should
-                 *  *            only be called from a worker thread.
+                 * only be called from a worker thread.
                  */
-                @androidx.annotation.UiThread @androidx.annotation.WorkerThread public int test1() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.UiThread
+                @androidx.annotation.WorkerThread
+                public int test1() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Merge Multiple sections`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package android.widget;
+                    import androidx.annotation.UiThread;
+
+                    public class Toolbar2 {
+                        /**
+                        * Existing documentation for {@linkplain #getCurrentContentInsetEnd()} here.
+                        * @return blah blah blah
+                        */
+                        @UiThread
+                        public int getCurrentContentInsetEnd() {
+                            return 0;
+                        }
+                    }
+                    """
+                ),
+                uiThreadSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            applyApiLevelsXml = """
+                    <?xml version="1.0" encoding="utf-8"?>
+                    <api version="2">
+                        <class name="android/widget/Toolbar2" since="21">
+                            <method name="&lt;init>(Landroid/content/Context;)V"/>
+                            <method name="collapseActionView()V"/>
+                            <method name="getContentInsetStartWithNavigation()I" since="24"/>
+                            <method name="getCurrentContentInsetEnd()I" since="24"/>
+                            <method name="getCurrentContentInsetLeft()I" since="24"/>
+                            <method name="getCurrentContentInsetRight()I" since="24"/>
+                            <method name="getCurrentContentInsetStart()I" since="24"/>
+                        </class>
+                    </api>
+                    """,
+            stubs = arrayOf(
+                """
+                package android.widget;
+                /**
+                 * @since 21
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Toolbar2 {
+                public Toolbar2() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Existing documentation for {@linkplain #getCurrentContentInsetEnd()} here.
+                 * <br>
+                 * This method must be called on the thread that originally created
+                 * this UI element. This is typically the main thread of your app.
+                 * @since 24
+                 * @return blah blah blah
+                 */
+                @androidx.annotation.UiThread
+                public int getCurrentContentInsetEnd() { throw new RuntimeException("Stub!"); }
                 }
                 """
             )
@@ -504,7 +595,8 @@
                 /**
                  * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
                  */
-                @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public void test1() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION)
+                public void test1() { throw new RuntimeException("Stub!"); }
                 public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
                 }
                 """
@@ -541,7 +633,8 @@
                 /**
                  * Requires "MyPermission"
                  */
-                @androidx.annotation.RequiresPermission("MyPermission") public void test1() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RequiresPermission("MyPermission")
+                public void test1() { throw new RuntimeException("Stub!"); }
                 }
                 """
             )
@@ -577,9 +670,11 @@
                 public RangeTest() { throw new RuntimeException("Stub!"); }
                 /**
                  * This is the existing documentation.
+                 * <br>
                  * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
                  */
-                @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION)
+                public int test1() { throw new RuntimeException("Stub!"); }
                 public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
                 }
                 """
@@ -620,9 +715,11 @@
                 /**
                  * This is the existing documentation.
                  * Multiple lines of it.
+                 * <br>
                  * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
                  */
-                @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION)
+                public int test1() { throw new RuntimeException("Stub!"); }
                 public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
                 }
                 """
@@ -698,13 +795,15 @@
                 public RangeTest() { throw new RuntimeException("Stub!"); }
                 /**
                  * This is the existing documentation.
+                 * <br>
                  * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
                  * @param parameter1 docs for parameter1
                  * @param parameter2 docs for parameter2
                  * @param parameter3 docs for parameter2
                  * @return return value documented here
                  */
-                @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION)
+                public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
                 public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
                 }
                 """
@@ -866,7 +965,8 @@
                 /**
                  * @return Value is 10 or greater
                  */
-                @androidx.annotation.IntRange(from=10) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.IntRange(from=10)
+                public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
                 }
                 """
             )
@@ -906,7 +1006,8 @@
                  * @return return value documented here
                  * Value is 10 or greater
                  */
-                @androidx.annotation.IntRange(from=10) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.IntRange(from=10)
+                public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
                 }
                 """
             )
@@ -927,7 +1028,6 @@
         )
     }
 
-    @Ignore("This test for some reason is flaky; it works when run directly but sometimes fails when running all tests")
     @Test
     fun `Merge API levels`() {
         check(
@@ -969,16 +1069,14 @@
                 """
                 package android.widget;
                 /**
-                 * Requires API level 21
-                 * @since 5.0 Lollipop (21)
+                 * @since 21
                  */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
                 public class Toolbar {
                 public Toolbar() { throw new RuntimeException("Stub!"); }
                 /**
                  * Existing documentation for {@linkplain #getCurrentContentInsetEnd()} here.
-                 * Requires API level 24
-                 * @since 7.0 Nougat (24)
+                 * @since 24
                  * @return blah blah blah
                  */
                 public int getCurrentContentInsetEnd() { throw new RuntimeException("Stub!"); }
@@ -1034,15 +1132,14 @@
                  *             applications.*
                  */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @Deprecated public class Camera {
+                @Deprecated
+                public class Camera {
                 public Camera() { throw new RuntimeException("Stub!"); }
                 /**
-                 *
-                 * Requires API level 14
                  * @deprecated
                  * <p class="caution"><strong>This class was deprecated in API level 21.</strong></p>
                  *  Use something else.
-                 * @since 4.0 IceCreamSandwich (14)
+                 * @since 14
                  */
                 @Deprecated public static final java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
                 }
@@ -1058,7 +1155,22 @@
         check(
             checkDoclava1 = false,
             sourceFiles = *arrayOf(
-                source("src/test/visible/overview.html", "<html>My docs</html>"),
+                source("src/overview.html", "<html>My overview docs</html>"),
+                source(
+                    "src/test/visible/package.html",
+                    """
+                    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+                    <!-- not a body tag: <body> -->
+                    <html>
+                    <body bgcolor="white">
+                    My package docs<br>
+                    <!-- comment -->
+                    Sample code: /** code here */
+                    Another line.<br>
+                    </BODY>
+                    </html>
+                    """
+                ).indented(),
                 java(
                     """
                     package test.visible;
@@ -1068,9 +1180,20 @@
                     """
                 )
             ),
+            docStubs = true,
             stubs = arrayOf(
                 """
-                <html>My docs</html>
+                <html>My overview docs</html>
+                """,
+                """
+                [test/visible/package-info.java]
+                /**
+                 * My package docs<br>
+                 * <!-- comment -->
+                 * Sample code: /** code here &#42;/
+                 * Another line.<br>
+                 */
+                package test.visible;
                 """,
                 """
                 package test.visible;
@@ -1104,6 +1227,7 @@
                     package android.content.pm;
                     public abstract class PackageManager {
                         public static final String FEATURE_LOCATION = "android.hardware.location";
+                        public boolean hasSystemFeature(String feature) { return false; }
                     }
                     """
                 ),
@@ -1144,18 +1268,196 @@
 
                 requiresApiSource
             ),
+            checkCompilation = false, // duplicate class: androidx.annotation.RequiresApi
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                /**
+                 * @since 21
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @androidx.annotation.RequiresApi(21)
+                public class MyClass1 {
+                public MyClass1() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Include Kotlin deprecation text`() {
+        check(
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    package test.pkg
+
+                    @Suppress("DeprecatedCallableAddReplaceWith","EqualsOrHashCode")
+                    @Deprecated("Use Jetpack preference library", level = DeprecationLevel.ERROR)
+                    class Foo {
+                        fun foo()
+
+                        @Deprecated("Blah blah blah 1", level = DeprecationLevel.ERROR)
+                        override fun toString(): String = "Hello World"
+
+                        /**
+                         * My description
+                         * @deprecated Existing deprecation message.
+                         */
+                        @Deprecated("Blah blah blah 2", level = DeprecationLevel.ERROR)
+                        override fun hashCode(): Int = 0
+                    }
+
+                    """
+                )
+            ),
             checkCompilation = true,
             checkDoclava1 = false,
             stubs = arrayOf(
                 """
                 package test.pkg;
                 /**
-                 * Requires API level 21
-                 * @since 5.0 Lollipop (21)
+                 * @deprecated Use Jetpack preference library
                  */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @androidx.annotation.RequiresApi(21) public class MyClass1 {
-                public MyClass1() { throw new RuntimeException("Stub!"); }
+                @Deprecated
+                public final class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                public void foo() { throw new RuntimeException("Stub!"); }
+                /**
+                 * {@inheritDoc}
+                 * @deprecated Blah blah blah 1
+                 */
+                @Deprecated
+                @androidx.annotation.NonNull
+                public java.lang.String toString() { throw new RuntimeException("Stub!"); }
+                /**
+                 * My description
+                 * @deprecated Existing deprecation message.
+                 * Blah blah blah 2
+                 */
+                @Deprecated
+                public int hashCode() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Annotation annotating self`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+                        import java.lang.annotation.Retention;
+                        import java.lang.annotation.RetentionPolicy;
+                        /**
+                         * Documentation here
+                         */
+                        @SuppressWarnings("WeakerAccess")
+                        @MyAnnotation
+                        @Retention(RetentionPolicy.SOURCE)
+                        public @interface MyAnnotation {
+                        }
+                    """
+                ),
+                java(
+                    """
+                        package test.pkg;
+
+                        /**
+                         * Other documentation here
+                         */
+                        @SuppressWarnings("WeakerAccess")
+                        @MyAnnotation
+                        public class OtherClass {
+                        }
+                    """
+                )
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                /**
+                 * Documentation here
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+                public @interface MyAnnotation {
+                }
+                """,
+                """
+                package test.pkg;
+                /**
+                 * Other documentation here
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class OtherClass {
+                public OtherClass() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Annotation annotating itself indirectly`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+
+                        /**
+                         * Documentation 1 here
+                         */
+                        @SuppressWarnings("WeakerAccess")
+                        @MyAnnotation2
+                        public @interface MyAnnotation1 {
+                        }
+                    """
+                ),
+                java(
+                    """
+                        package test.pkg;
+
+                        /**
+                         * Documentation 2 here
+                         */
+                        @SuppressWarnings("WeakerAccess")
+                        @MyAnnotation1
+                        public @interface MyAnnotation2 {
+                        }
+                    """
+                )
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                /**
+                 * Documentation 1 here
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @test.pkg.MyAnnotation2
+                public @interface MyAnnotation1 {
+                }
+                """,
+                """
+                package test.pkg;
+                /**
+                 * Documentation 2 here
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @test.pkg.MyAnnotation1
+                public @interface MyAnnotation2 {
                 }
                 """
             )
@@ -1186,9 +1488,9 @@
 
         check(
             extraArguments = arrayOf(
-                "--write-stubs-source-list",
+                ARG_STUBS_SOURCE_LIST,
                 sourceList,
-                "--generate-documentation",
+                ARG_GENERATE_DOCUMENTATION,
                 javadoc.path,
                 "-sourcepath",
                 "STUBS_DIR",
@@ -1226,6 +1528,7 @@
             ),
             checkCompilation = true,
             checkDoclava1 = false,
+
             stubs = arrayOf(
                 """
                 package test.pkg;
diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt
index 1449791..038a201 100644
--- a/src/test/java/com/android/tools/metalava/DriverTest.kt
+++ b/src/test/java/com/android/tools/metalava/DriverTest.kt
@@ -19,7 +19,6 @@
 import com.android.SdkConstants
 import com.android.SdkConstants.DOT_JAVA
 import com.android.SdkConstants.DOT_KT
-import com.android.SdkConstants.VALUE_TRUE
 import com.android.ide.common.process.DefaultProcessExecutor
 import com.android.ide.common.process.LoggedProcessOutputHandler
 import com.android.ide.common.process.ProcessException
@@ -30,7 +29,9 @@
 import com.android.tools.lint.checks.infrastructure.TestFiles
 import com.android.tools.lint.checks.infrastructure.TestFiles.java
 import com.android.tools.lint.checks.infrastructure.stripComments
+import com.android.tools.metalava.doclava1.ApiFile
 import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.parseDocument
 import com.android.utils.FileUtils
 import com.android.utils.SdkUtils
 import com.android.utils.StdLogger
@@ -43,23 +44,32 @@
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
+import org.junit.Before
 import org.junit.Rule
 import org.junit.rules.TemporaryFolder
+import java.io.ByteArrayOutputStream
 import java.io.File
+import java.io.FileNotFoundException
+import java.io.PrintStream
 import java.io.PrintWriter
 import java.io.StringWriter
 import java.net.URL
+import kotlin.text.Charsets.UTF_8
 
 const val CHECK_OLD_DOCLAVA_TOO = false
 const val CHECK_STUB_COMPILATION = false
-const val SKIP_NON_COMPAT = false
 
 abstract class DriverTest {
     @get:Rule
     var temporaryFolder = TemporaryFolder()
 
-    protected fun createProject(vararg files: TestFile): File {
-        val dir = temporaryFolder.newFolder()
+    @Before
+    fun setup() {
+        System.setProperty(ENV_VAR_METALAVA_TESTS_RUNNING, SdkConstants.VALUE_TRUE)
+    }
+
+    private fun createProject(vararg files: TestFile): File {
+        val dir = temporaryFolder.newFolder("project")
 
         files
             .map { it.createFile(dir) }
@@ -71,16 +81,68 @@
     protected fun runDriver(vararg args: String, expectedFail: String = ""): String {
         resetTicker()
 
-        val sw = StringWriter()
-        val writer = PrintWriter(sw)
-        if (!com.android.tools.metalava.run(arrayOf(*args), writer, writer)) {
-            val actualFail = sw.toString().trim()
-            if (expectedFail != actualFail.replace(".", "").trim()) {
-                fail(actualFail)
+        // Capture the actual input and output from System.out/err and compare it
+        // to the output printed through the official writer; they should be the same,
+        // otherwise we have stray println's littered in the code!
+        val previousOut = System.out
+        val previousErr = System.err
+        try {
+            val output = OutputForbiddenWriter("stdout")
+            System.setOut(PrintStream(output))
+            val error = OutputForbiddenWriter("stderr")
+            System.setErr(PrintStream(error))
+
+            val sw = StringWriter()
+            val writer = PrintWriter(sw)
+            if (!com.android.tools.metalava.run(arrayOf(*args), writer, writer)) {
+                val actualFail = cleanupString(sw.toString(), null)
+                if (expectedFail != actualFail.replace(".", "").trim()) {
+                    if (expectedFail == "Aborting: Found compatibility problems with --check-compatibility" &&
+                        actualFail.startsWith("Aborting: Found compatibility problems checking the ")
+                    ) {
+                        // Special case for compat checks; we don't want to force each one of them
+                        // to pass in the right string (which may vary based on whether writing out
+                        // the signature was passed at the same time
+                        // ignore
+                    } else {
+                        fail(actualFail)
+                    }
+                }
             }
+
+            val stdout = output.toString(UTF_8.name())
+            assertTrue(stdout, stdout.isEmpty())
+
+            val stderr = error.toString(UTF_8.name())
+            assertTrue(stderr, stderr.isEmpty())
+
+            val printedOutput = sw.toString()
+            if (printedOutput.isNotEmpty() && printedOutput.trim().isEmpty()) {
+                fail("Printed newlines with nothing else")
+            }
+
+            return printedOutput
+        } finally {
+            System.setOut(previousOut)
+            System.setErr(previousErr)
+        }
+    }
+
+    class OutputForbiddenWriter(private val stream: String) : ByteArrayOutputStream() {
+        override fun write(b: ByteArray?, off: Int, len: Int) {
+            fail("Unexpected write directly to $stream")
+            super.write(b, off, len)
         }
 
-        return sw.toString()
+        override fun write(b: ByteArray?) {
+            fail("Unexpected write directly to $stream")
+            super.write(b)
+        }
+
+        override fun write(b: Int) {
+            fail("Unexpected write directly to $stream")
+            super.write(b)
+        }
     }
 
     private fun findKotlinStdlibPath(): List<String> {
@@ -121,8 +183,14 @@
     protected fun check(
         /** The source files to pass to the analyzer */
         vararg sourceFiles: TestFile,
+        /** Any jars to add to the class path */
+        classpath: Array<TestFile>? = null,
         /** The API signature content (corresponds to --api) */
+        @Language("TEXT")
         api: String? = null,
+        /** The API signature content (corresponds to --api-xml) */
+        @Language("XML")
+        apiXml: String? = null,
         /** The exact API signature content (corresponds to --exact-api) */
         exactApi: String? = null,
         /** The removed API (corresponds to --removed-api) */
@@ -135,6 +203,8 @@
         privateDexApi: String? = null,
         /** The DEX API (corresponds to --dex-api) */
         dexApi: String? = null,
+        /** The DEX mapping API (corresponds to --dex-api-mapping) */
+        dexApiMapping: String? = null,
         /** Expected stubs (corresponds to --stubs) */
         @Language("JAVA") stubs: Array<String> = emptyArray(),
         /** Stub source file list generated */
@@ -155,24 +225,32 @@
         checkCompilation: Boolean = false,
         /** Annotations to merge in (in .xml format) */
         @Language("XML") mergeXmlAnnotations: String? = null,
-        /** Annotations to merge in (in .jaif format) */
-        @Language("TEXT") mergeJaifAnnotations: String? = null,
         /** Annotations to merge in (in .txt/.signature format) */
         @Language("TEXT") mergeSignatureAnnotations: String? = null,
+        /** Qualifier annotations to merge in (in Java stub format) */
+        @Language("JAVA") mergeJavaStubAnnotations: String? = null,
+        /** Inclusion annotations to merge in (in Java stub format) */
+        @Language("JAVA") mergeInclusionAnnotations: String? = null,
         /** An optional API signature file content to load **instead** of Java/Kotlin source files */
         @Language("TEXT") signatureSource: String? = null,
         /** An optional API jar file content to load **instead** of Java/Kotlin source files */
         apiJar: File? = null,
-        /** An optional API signature representing the previous API level to diff */
-        @Language("TEXT") previousApi: String? = null,
+        /** An optional API signature to check the current API's compatibility with */
+        @Language("TEXT") checkCompatibilityApi: String? = null,
+        /** An optional API signature to check the last released API's compatibility with */
+        @Language("TEXT") checkCompatibilityApiReleased: String? = null,
+        /** An optional API signature to check the current removed API's compatibility with */
+        @Language("TEXT") checkCompatibilityRemovedApiCurrent: String? = null,
+        /** An optional API signature to check the last released removed API's compatibility with */
+        @Language("TEXT") checkCompatibilityRemovedApiReleased: String? = null,
+        /** An optional API signature to compute nullness migration status from */
+        @Language("TEXT") migrateNullsApi: String? = null,
         /** An optional Proguard keep file to generate */
         @Language("Proguard") proguard: String? = null,
-        /** Whether we should migrate nullness information */
-        migrateNulls: Boolean = false,
-        /** Whether we should check compatibility */
-        checkCompatibility: Boolean = false,
         /** Show annotations (--show-annotation arguments) */
         showAnnotations: Array<String> = emptyArray(),
+        /** Hide annotations (--hideAnnotation arguments) */
+        hideAnnotations: Array<String> = emptyArray(),
         /** If using [showAnnotations], whether to include unannotated */
         showUnannotated: Boolean = false,
         /** Additional arguments to supply */
@@ -220,37 +298,74 @@
         artifacts: Map<String, String>? = null,
         /** Extract annotations and check that the given packages contain the given extracted XML files */
         extractAnnotations: Map<String, String>? = null,
+        /** Creates the nullability annotations validator, and check that the report has the given lines (does not define files to be validated) */
+        validateNullability: Set<String>? = null,
+        /** Enable nullability validation for the listed classes */
+        validateNullabilityFromList: String? = null,
         /**
          * Whether to include source retention annotations in the stubs (in that case they do not
          * go into the extracted annotations zip file)
          */
-        includeSourceRetentionAnnotations: Boolean = true
+        includeSourceRetentionAnnotations: Boolean = true,
+        /**
+         * Whether to include the signature version in signatures
+         */
+        includeSignatureVersion: Boolean = false,
+        /**
+         * List of signature files to convert to JDiff XML and the
+         * expected XML output
+         */
+        convertToJDiff: List<Pair<String, String>> = emptyList(),
+        /**
+         * Signature file format
+         */
+        format: Int? = null,
+        /**
+         * Hook for performing additional initialization of the project
+         * directory
+         */
+        projectSetup: ((File) -> Unit)? = null
     ) {
-        System.setProperty("METALAVA_TESTS_RUNNING", VALUE_TRUE)
+        // Ensure different API clients don't interfere with each other
+        try {
+            val method = ApiLookup::class.java.getDeclaredMethod("dispose")
+            method.isAccessible = true
+            method.invoke(null)
+        } catch (ignore: Throwable) {
+            ignore.printStackTrace()
+        }
 
         if (compatibilityMode && mergeXmlAnnotations != null) {
             fail(
-                "Can't specify both compatibilityMode and mergeAnnotations: there were no " +
+                "Can't specify both compatibilityMode and mergeXmlAnnotations: there were no " +
                     "annotations output in doclava1"
             )
         }
-        if (compatibilityMode && mergeJaifAnnotations != null) {
-            fail(
-                "Can't specify both compatibilityMode and mergeJaifAnnotations: there were no " +
-                    "annotations output in doclava1"
-            )
-        }
-
         if (compatibilityMode && mergeSignatureAnnotations != null) {
             fail(
                 "Can't specify both compatibilityMode and mergeSignatureAnnotations: there were no " +
                     "annotations output in doclava1"
             )
         }
+        if (compatibilityMode && mergeJavaStubAnnotations != null) {
+            fail(
+                "Can't specify both compatibilityMode and mergeJavaStubAnnotations: there were no " +
+                    "annotations output in doclava1"
+            )
+        }
+        if (compatibilityMode && mergeInclusionAnnotations != null) {
+            fail(
+                "Can't specify both compatibilityMode and mergeInclusionAnnotations"
+            )
+        }
         Errors.resetLevels()
 
         /** Expected output if exiting with an error code */
-        val expectedFail = if (checkCompatibility) {
+        val expectedFail = if (checkCompatibilityApi != null ||
+            checkCompatibilityApiReleased != null ||
+            checkCompatibilityRemovedApiCurrent != null ||
+            checkCompatibilityRemovedApiReleased != null
+        ) {
             "Aborting: Found compatibility problems with --check-compatibility"
         } else {
             ""
@@ -264,19 +379,22 @@
         val packages = sourceFiles.asSequence().map { findPackage(it.getContents()!!) }.filterNotNull().toSet()
 
         val sourcePathDir = File(project, "src")
+        if (!sourcePathDir.isDirectory) {
+            sourcePathDir.mkdirs()
+        }
         val sourcePath = sourcePathDir.path
         val sourceList =
             if (signatureSource != null) {
                 sourcePathDir.mkdirs()
                 assert(sourceFiles.isEmpty()) { "Shouldn't combine sources with signature file loads" }
                 val signatureFile = File(project, "load-api.txt")
-                Files.asCharSink(signatureFile, Charsets.UTF_8).write(signatureSource.trimIndent())
+                signatureFile.writeText(signatureSource.trimIndent())
                 if (includeStrippedSuperclassWarnings) {
                     arrayOf(signatureFile.path)
                 } else {
                     arrayOf(
                         signatureFile.path,
-                        "--hide",
+                        ARG_HIDE,
                         "HiddenSuperclass"
                     ) // Suppress warning #111
                 }
@@ -288,79 +406,162 @@
                 sourceFiles.asSequence().map { File(project, it.targetPath).path }.toList().toTypedArray()
             }
 
+        val classpathArgs: Array<String> = if (classpath != null) {
+            val classpathString = classpath
+                .map { it.createFile(project) }
+                .map { it.path }
+                .joinToString(separator = File.pathSeparator) { it }
+
+            arrayOf(ARG_CLASS_PATH, classpathString)
+        } else {
+            emptyArray()
+        }
+
         val reportedWarnings = StringBuilder()
         reporter = object : Reporter(project) {
             override fun print(message: String) {
-                reportedWarnings.append(message.replace(project.path, "TESTROOT").trim()).append('\n')
+                reportedWarnings.append(cleanupString(message, project).trim()).append('\n')
             }
         }
 
         val mergeAnnotationsArgs = if (mergeXmlAnnotations != null) {
             val merged = File(project, "merged-annotations.xml")
-            Files.asCharSink(merged, Charsets.UTF_8).write(mergeXmlAnnotations.trimIndent())
-            arrayOf("--merge-annotations", merged.path)
-        } else {
-            emptyArray()
-        }
-
-        val jaifAnnotationsArgs = if (mergeJaifAnnotations != null) {
-            val merged = File(project, "merged-annotations.jaif")
-            Files.asCharSink(merged, Charsets.UTF_8).write(mergeJaifAnnotations.trimIndent())
-            arrayOf("--merge-annotations", merged.path)
+            merged.writeText(mergeXmlAnnotations.trimIndent())
+            arrayOf(ARG_MERGE_QUALIFIER_ANNOTATIONS, merged.path)
         } else {
             emptyArray()
         }
 
         val signatureAnnotationsArgs = if (mergeSignatureAnnotations != null) {
             val merged = File(project, "merged-annotations.txt")
-            Files.asCharSink(merged, Charsets.UTF_8).write(mergeSignatureAnnotations.trimIndent())
-            arrayOf("--merge-annotations", merged.path)
+            merged.writeText(mergeSignatureAnnotations.trimIndent())
+            arrayOf(ARG_MERGE_QUALIFIER_ANNOTATIONS, merged.path)
         } else {
             emptyArray()
         }
 
-        val previousApiFile = if (previousApi != null) {
-            val prevApiJar = File(previousApi)
-            if (prevApiJar.isFile) {
-                prevApiJar
+        val javaStubAnnotationsArgs = if (mergeJavaStubAnnotations != null) {
+            val merged = File(project, "merged-qualifier-annotations.java")
+            merged.writeText(mergeJavaStubAnnotations.trimIndent())
+            arrayOf(ARG_MERGE_QUALIFIER_ANNOTATIONS, merged.path)
+        } else {
+            emptyArray()
+        }
+
+        val inclusionAnnotationsArgs = if (mergeInclusionAnnotations != null) {
+            val merged = File(project, "merged-inclusion-annotations.java")
+            merged.writeText(mergeInclusionAnnotations.trimIndent())
+            arrayOf(ARG_MERGE_INCLUSION_ANNOTATIONS, merged.path)
+        } else {
+            emptyArray()
+        }
+
+        val checkCompatibilityApiFile = if (checkCompatibilityApi != null) {
+            val jar = File(checkCompatibilityApi)
+            if (jar.isFile) {
+                jar
             } else {
-                val file = File(project, "previous-api.txt")
-                Files.asCharSink(file, Charsets.UTF_8).write(previousApi.trimIndent())
+                val file = File(project, "current-api.txt")
+                file.writeText(checkCompatibilityApi.trimIndent())
                 file
             }
         } else {
             null
         }
 
-        val previousApiArgs = if (previousApiFile != null) {
-            arrayOf("--previous-api", previousApiFile.path)
+        val checkCompatibilityApiReleasedFile = if (checkCompatibilityApiReleased != null) {
+            val jar = File(checkCompatibilityApiReleased)
+            if (jar.isFile) {
+                jar
+            } else {
+                val file = File(project, "released-api.txt")
+                file.writeText(checkCompatibilityApiReleased.trimIndent())
+                file
+            }
         } else {
-            emptyArray()
+            null
+        }
+
+        val checkCompatibilityRemovedApiCurrentFile = if (checkCompatibilityRemovedApiCurrent != null) {
+            val jar = File(checkCompatibilityRemovedApiCurrent)
+            if (jar.isFile) {
+                jar
+            } else {
+                val file = File(project, "removed-current-api.txt")
+                file.writeText(checkCompatibilityRemovedApiCurrent.trimIndent())
+                file
+            }
+        } else {
+            null
+        }
+
+        val checkCompatibilityRemovedApiReleasedFile = if (checkCompatibilityRemovedApiReleased != null) {
+            val jar = File(checkCompatibilityRemovedApiReleased)
+            if (jar.isFile) {
+                jar
+            } else {
+                val file = File(project, "removed-released-api.txt")
+                file.writeText(checkCompatibilityRemovedApiReleased.trimIndent())
+                file
+            }
+        } else {
+            null
+        }
+
+        val migrateNullsApiFile = if (migrateNullsApi != null) {
+            val jar = File(migrateNullsApi)
+            if (jar.isFile) {
+                jar
+            } else {
+                val file = File(project, "stable-api.txt")
+                file.writeText(migrateNullsApi.trimIndent())
+                file
+            }
+        } else {
+            null
         }
 
         val manifestFileArgs = if (manifest != null) {
             val file = File(project, "manifest.xml")
-            Files.asCharSink(file, Charsets.UTF_8).write(manifest.trimIndent())
-            arrayOf("--manifest", file.path)
+            file.writeText(manifest.trimIndent())
+            arrayOf(ARG_MANIFEST, file.path)
         } else {
             emptyArray()
         }
 
-        val migrateNullsArguments = if (migrateNulls) {
-            arrayOf("--migrate-nullness")
+        val migrateNullsArguments = if (migrateNullsApiFile != null) {
+            arrayOf(ARG_MIGRATE_NULLNESS, migrateNullsApiFile.path)
         } else {
             emptyArray()
         }
 
-        val checkCompatibilityArguments = if (checkCompatibility) {
-            arrayOf("--check-compatibility")
+        val checkCompatibilityArguments = if (checkCompatibilityApiFile != null) {
+            arrayOf(ARG_CHECK_COMPATIBILITY_API_CURRENT, checkCompatibilityApiFile.path)
         } else {
             emptyArray()
         }
 
-        val quiet = if (expectedOutput != null && !extraArguments.contains("--verbose")) {
+        val checkCompatibilityApiReleasedArguments = if (checkCompatibilityApiReleasedFile != null) {
+            arrayOf(ARG_CHECK_COMPATIBILITY_API_RELEASED, checkCompatibilityApiReleasedFile.path)
+        } else {
+            emptyArray()
+        }
+
+        val checkCompatibilityRemovedCurrentArguments = if (checkCompatibilityRemovedApiCurrentFile != null) {
+            arrayOf(ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT, checkCompatibilityRemovedApiCurrentFile.path)
+        } else {
+            emptyArray()
+        }
+
+        val checkCompatibilityRemovedReleasedArguments = if (checkCompatibilityRemovedApiReleasedFile != null) {
+            arrayOf(ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED, checkCompatibilityRemovedApiReleasedFile.path)
+        } else {
+            emptyArray()
+        }
+
+        val quiet = if (expectedOutput != null && !extraArguments.contains(ARG_VERBOSE)) {
             // If comparing output, avoid noisy output such as the banner etc
-            arrayOf("--quiet")
+            arrayOf(ARG_QUIET)
         } else {
             emptyArray()
         }
@@ -376,7 +577,7 @@
                 val file = jar.createFile(root)
                 sb.append(file.path)
             }
-            arrayOf("--annotation-coverage-of", sb.toString())
+            arrayOf(ARG_ANNOTATION_COVERAGE_OF, sb.toString())
         } else {
             emptyArray()
         }
@@ -384,7 +585,7 @@
         var proguardFile: File? = null
         val proguardKeepArguments = if (proguard != null) {
             proguardFile = File(project, "proguard.cfg")
-            arrayOf("--proguard", proguardFile.path)
+            arrayOf(ARG_PROGUARD, proguardFile.path)
         } else {
             emptyArray()
         }
@@ -392,16 +593,27 @@
         val showAnnotationArguments = if (showAnnotations.isNotEmpty() || includeSystemApiAnnotations) {
             val args = mutableListOf<String>()
             for (annotation in showAnnotations) {
-                args.add("--show-annotation")
+                args.add(ARG_SHOW_ANNOTATION)
                 args.add(annotation)
             }
             if (includeSystemApiAnnotations && !args.contains("android.annotation.SystemApi")) {
-                args.add("--show-annotation")
+                args.add(ARG_SHOW_ANNOTATION)
                 args.add("android.annotation.SystemApi")
             }
-            if (includeSystemApiAnnotations && !args.contains("android.annotation.SystemService")) {
-                args.add("--show-annotation")
-                args.add("android.annotation.SystemService")
+            if (includeSystemApiAnnotations && !args.contains("android.annotation.TestApi")) {
+                args.add(ARG_SHOW_ANNOTATION)
+                args.add("android.annotation.TestApi")
+            }
+            args.toTypedArray()
+        } else {
+            emptyArray()
+        }
+
+        val hideAnnotationArguments = if (hideAnnotations.isNotEmpty()) {
+            val args = mutableListOf<String>()
+            for (annotation in hideAnnotations) {
+                args.add(ARG_HIDE_ANNOTATION)
+                args.add(annotation)
             }
             args.toTypedArray()
         } else {
@@ -410,14 +622,14 @@
 
         val showUnannotatedArgs =
             if (showUnannotated) {
-                arrayOf("--show-unannotated")
+                arrayOf(ARG_SHOW_UNANNOTATED)
             } else {
                 emptyArray()
             }
 
         val includeSourceRetentionAnnotationArgs =
             if (includeSourceRetentionAnnotations) {
-                arrayOf("--include-source-retention")
+                arrayOf(ARG_INCLUDE_SOURCE_RETENTION)
             } else {
                 emptyArray()
             }
@@ -425,7 +637,7 @@
         var removedApiFile: File? = null
         val removedArgs = if (removedApi != null) {
             removedApiFile = temporaryFolder.newFile("removed.txt")
-            arrayOf("--removed-api", removedApiFile.path)
+            arrayOf(ARG_REMOVED_API, removedApiFile.path)
         } else {
             emptyArray()
         }
@@ -433,7 +645,7 @@
         var removedDexApiFile: File? = null
         val removedDexArgs = if (removedDexApi != null) {
             removedDexApiFile = temporaryFolder.newFile("removed-dex.txt")
-            arrayOf("--removed-dex-api", removedDexApiFile.path)
+            arrayOf(ARG_REMOVED_DEX_API, removedDexApiFile.path)
         } else {
             emptyArray()
         }
@@ -441,7 +653,7 @@
         var apiFile: File? = null
         val apiArgs = if (api != null) {
             apiFile = temporaryFolder.newFile("public-api.txt")
-            arrayOf("--api", apiFile.path)
+            arrayOf(ARG_API, apiFile.path)
         } else {
             emptyArray()
         }
@@ -449,7 +661,15 @@
         var exactApiFile: File? = null
         val exactApiArgs = if (exactApi != null) {
             exactApiFile = temporaryFolder.newFile("exact-api.txt")
-            arrayOf("--exact-api", exactApiFile.path)
+            arrayOf(ARG_EXACT_API, exactApiFile.path)
+        } else {
+            emptyArray()
+        }
+
+        var apiXmlFile: File? = null
+        val apiXmlArgs = if (apiXml != null) {
+            apiXmlFile = temporaryFolder.newFile("public-api-xml.txt")
+            arrayOf(ARG_XML_API, apiXmlFile.path)
         } else {
             emptyArray()
         }
@@ -457,7 +677,7 @@
         var privateApiFile: File? = null
         val privateApiArgs = if (privateApi != null) {
             privateApiFile = temporaryFolder.newFile("private.txt")
-            arrayOf("--private-api", privateApiFile.path)
+            arrayOf(ARG_PRIVATE_API, privateApiFile.path)
         } else {
             emptyArray()
         }
@@ -465,7 +685,15 @@
         var dexApiFile: File? = null
         val dexApiArgs = if (dexApi != null) {
             dexApiFile = temporaryFolder.newFile("public-dex.txt")
-            arrayOf("--dex-api", dexApiFile.path)
+            arrayOf(ARG_DEX_API, dexApiFile.path)
+        } else {
+            emptyArray()
+        }
+
+        var dexApiMappingFile: File? = null
+        val dexApiMappingArgs = if (dexApiMapping != null) {
+            dexApiMappingFile = temporaryFolder.newFile("api-mapping.txt")
+            arrayOf(ARG_DEX_API_MAPPING, dexApiMappingFile.path)
         } else {
             emptyArray()
         }
@@ -473,7 +701,27 @@
         var privateDexApiFile: File? = null
         val privateDexApiArgs = if (privateDexApi != null) {
             privateDexApiFile = temporaryFolder.newFile("private-dex.txt")
-            arrayOf("--private-dex-api", privateDexApiFile.path)
+            arrayOf(ARG_PRIVATE_DEX_API, privateDexApiFile.path)
+        } else {
+            emptyArray()
+        }
+
+        val convertToJDiffFiles = mutableListOf<Pair<File, File>>()
+        val convertToJDiffArgs = if (convertToJDiff.isNotEmpty()) {
+            val args = mutableListOf<String>()
+            var index = 1
+            for ((signatures, _) in convertToJDiff) {
+                val convertSig = temporaryFolder.newFile("jdiff-signatures$index.txt")
+                convertSig.writeText(signatures.trimIndent(), Charsets.UTF_8)
+                val output = temporaryFolder.newFile("jdiff-output$index.xml")
+                convertToJDiffFiles += Pair(convertSig, output)
+                index++
+
+                args += ARG_CONVERT_TO_JDIFF
+                args += convertSig.path
+                args += output.path
+            }
+            args.toTypedArray()
         } else {
             emptyArray()
         }
@@ -482,9 +730,9 @@
         val stubsArgs = if (stubs.isNotEmpty()) {
             stubsDir = temporaryFolder.newFolder("stubs")
             if (docStubs) {
-                arrayOf("--doc-stubs", stubsDir.path)
+                arrayOf(ARG_DOC_STUBS, stubsDir.path)
             } else {
-                arrayOf("--stubs", stubsDir.path)
+                arrayOf(ARG_STUBS, stubsDir.path)
             }
         } else {
             emptyArray()
@@ -493,7 +741,7 @@
         var stubsSourceListFile: File? = null
         val stubsSourceListArgs = if (stubsSourceList != null) {
             stubsSourceListFile = temporaryFolder.newFile("droiddoc-src-list")
-            arrayOf("--write-stubs-source-list", stubsSourceListFile.path)
+            arrayOf(ARG_STUBS_SOURCE_LIST, stubsSourceListFile.path)
         } else {
             emptyArray()
         }
@@ -502,8 +750,8 @@
         val applyApiLevelsXmlArgs = if (applyApiLevelsXml != null) {
             ApiLookup::class.java.getDeclaredMethod("dispose").apply { isAccessible = true }.invoke(null)
             applyApiLevelsXmlFile = temporaryFolder.newFile("api-versions.xml")
-            Files.asCharSink(applyApiLevelsXmlFile!!, Charsets.UTF_8).write(applyApiLevelsXml.trimIndent())
-            arrayOf("--apply-api-levels", applyApiLevelsXmlFile.path)
+            applyApiLevelsXmlFile?.writeText(applyApiLevelsXml.trimIndent())
+            arrayOf(ARG_APPLY_API_LEVELS, applyApiLevelsXmlFile.path)
         } else {
             emptyArray()
         }
@@ -525,7 +773,7 @@
             if (kotlinPath.isNotEmpty() &&
                 sourceList.asSequence().any { it.endsWith(DOT_KT) }
             ) {
-                arrayOf("--classpath", kotlinPath.joinToString(separator = File.pathSeparator) { it })
+                arrayOf(ARG_CLASS_PATH, kotlinPath.joinToString(separator = File.pathSeparator) { it })
             } else {
                 emptyArray()
             }
@@ -540,7 +788,7 @@
             sdk_widgets != null
         ) {
             val dir = File(project, "sdk-files")
-            sdkFilesArgs = arrayOf("--sdk-values", dir.path)
+            sdkFilesArgs = arrayOf(ARG_SDK_VALUES, dir.path)
             sdkFilesDir = dir
         } else {
             sdkFilesArgs = emptyArray()
@@ -552,10 +800,10 @@
             var index = 1
             for ((artifactId, signatures) in artifacts) {
                 val signatureFile = temporaryFolder.newFile("signature-file-$index.txt")
-                Files.asCharSink(signatureFile, Charsets.UTF_8).write(signatures.trimIndent())
+                signatureFile.writeText(signatures.trimIndent())
                 index++
 
-                args.add("--register-artifact")
+                args.add(ARG_REGISTER_ARTIFACT)
                 args.add(signatureFile.path)
                 args.add(artifactId)
             }
@@ -567,56 +815,102 @@
         val extractedAnnotationsZip: File?
         val extractAnnotationsArgs = if (extractAnnotations != null) {
             extractedAnnotationsZip = temporaryFolder.newFile("extracted-annotations.zip")
-            arrayOf("--extract-annotations", extractedAnnotationsZip.path)
+            arrayOf(ARG_EXTRACT_ANNOTATIONS, extractedAnnotationsZip.path)
         } else {
             extractedAnnotationsZip = null
             emptyArray()
         }
 
+        val validateNullabilityTxt: File?
+        val validateNullabilityArgs = if (validateNullability != null) {
+            validateNullabilityTxt = temporaryFolder.newFile("validate-nullability.txt")
+            arrayOf(
+                ARG_NULLABILITY_WARNINGS_TXT, validateNullabilityTxt.path,
+                ARG_NULLABILITY_ERRORS_NON_FATAL // for testing, report on errors instead of throwing
+            )
+        } else {
+            validateNullabilityTxt = null
+            emptyArray()
+        }
+        val validateNullablityFromListFile: File?
+        val validateNullabilityFromListArgs = if (validateNullabilityFromList != null) {
+            validateNullablityFromListFile = temporaryFolder.newFile("validate-nullability-classes.txt")
+            validateNullablityFromListFile.writeText(validateNullabilityFromList)
+            arrayOf(
+                ARG_VALIDATE_NULLABILITY_FROM_LIST, validateNullablityFromListFile.path
+            )
+        } else {
+            emptyArray()
+        }
+
+        val signatureFormatArgs = if (format != null) {
+            arrayOf("$ARG_FORMAT=v$format")
+        } else {
+            emptyArray()
+        }
+
+        // Run optional additional setup steps on the project directory
+        projectSetup?.invoke(project)
+
         val actualOutput = runDriver(
-            "--no-color",
-            "--no-banner",
+            ARG_NO_COLOR,
+            ARG_NO_BANNER,
+
+            // Tell metalava where to store temp folder: place them under the
+            // test root folder such that we clean up the output strings referencing
+            // paths to the temp folder
+            "--temp-folder",
+            temporaryFolder.newFolder("temp").path,
 
             // For the tests we want to treat references to APIs like java.io.Closeable
             // as a class that is part of the API surface, not as a hidden class as would
             // be the case when analyzing a complete API surface
-            // "--unhide-classpath-classes",
-            "--allow-referencing-unknown-classes",
+            // ARG_UNHIDE_CLASSPATH_CLASSES,
+            ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES,
 
             // Annotation generation temporarily turned off by default while integrating with
             // SDK builds; tests need these
-            "--include-annotations",
+            ARG_INCLUDE_ANNOTATIONS,
 
-            "--sourcepath",
+            ARG_SOURCE_PATH,
             sourcePath,
-            "--classpath",
+            ARG_CLASS_PATH,
             androidJar.path,
+            *classpathArgs,
             *kotlinPathArgs,
             *removedArgs,
             *removedDexArgs,
             *apiArgs,
+            *apiXmlArgs,
             *exactApiArgs,
             *privateApiArgs,
             *dexApiArgs,
             *privateDexApiArgs,
+            *dexApiMappingArgs,
             *stubsArgs,
             *stubsSourceListArgs,
-            "--compatible-output=${if (compatibilityMode) "yes" else "no"}",
-            "--output-kotlin-nulls=${if (outputKotlinStyleNulls) "yes" else "no"}",
-            "--input-kotlin-nulls=${if (inputKotlinStyleNulls) "yes" else "no"}",
-            "--omit-common-packages=${if (omitCommonPackages) "yes" else "no"}",
+            "$ARG_COMPAT_OUTPUT=${if (compatibilityMode) "yes" else "no"}",
+            "$ARG_OUTPUT_KOTLIN_NULLS=${if (outputKotlinStyleNulls) "yes" else "no"}",
+            "$ARG_INPUT_KOTLIN_NULLS=${if (inputKotlinStyleNulls) "yes" else "no"}",
+            "$ARG_OMIT_COMMON_PACKAGES=${if (omitCommonPackages) "yes" else "no"}",
+            "$ARG_INCLUDE_SIG_VERSION=${if (includeSignatureVersion) "yes" else "no"}",
             *coverageStats,
             *quiet,
             *mergeAnnotationsArgs,
-            *jaifAnnotationsArgs,
             *signatureAnnotationsArgs,
-            *previousApiArgs,
+            *javaStubAnnotationsArgs,
+            *inclusionAnnotationsArgs,
             *migrateNullsArguments,
             *checkCompatibilityArguments,
+            *checkCompatibilityApiReleasedArguments,
+            *checkCompatibilityRemovedCurrentArguments,
+            *checkCompatibilityRemovedReleasedArguments,
             *proguardKeepArguments,
             *manifestFileArgs,
+            *convertToJDiffArgs,
             *applyApiLevelsXmlArgs,
             *showAnnotationArguments,
+            *hideAnnotationArguments,
             *showUnannotatedArgs,
             *includeSourceRetentionAnnotationArgs,
             *sdkFilesArgs,
@@ -624,6 +918,9 @@
             *skipEmitPackagesArgs.toTypedArray(),
             *artifactArgs,
             *extractAnnotationsArgs,
+            *validateNullabilityArgs,
+            *validateNullabilityFromListArgs,
+            *signatureFormatArgs,
             *sourceList,
             *extraArguments,
             expectedFail = expectedFail
@@ -635,8 +932,36 @@
 
         if (api != null && apiFile != null) {
             assertTrue("${apiFile.path} does not exist even though --api was used", apiFile.exists())
-            val expectedText = readFile(apiFile, stripBlankLines, trim)
-            assertEquals(stripComments(api, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(apiFile, stripBlankLines, trim)
+            assertEquals(stripComments(api, stripLineComments = false).trimIndent(), actualText)
+            // Make sure we can read back the files we write
+            ApiFile.parseApi(apiFile, options.outputKotlinStyleNulls, true)
+        }
+
+        if (apiXml != null && apiXmlFile != null) {
+            assertTrue(
+                "${apiXmlFile.path} does not exist even though $ARG_XML_API was used",
+                apiXmlFile.exists()
+            )
+            val actualText = readFile(apiXmlFile, stripBlankLines, trim)
+            assertEquals(stripComments(apiXml, stripLineComments = false).trimIndent(), actualText)
+            // Make sure we can read back the files we write
+            parseDocument(apiXmlFile.readText(Charsets.UTF_8), false)
+        }
+
+        if (convertToJDiffFiles.isNotEmpty()) {
+            for (i in 0 until convertToJDiff.size) {
+                val expected = convertToJDiff[i].second
+                val converted = convertToJDiffFiles[i].second
+                assertTrue(
+                    "${converted.path} does not exist even though $ARG_CONVERT_TO_JDIFF was used",
+                    converted.exists()
+                )
+                val actualText = readFile(converted, stripBlankLines, trim)
+                parseDocument(converted.readText(Charsets.UTF_8), false)
+                assertEquals(stripComments(expected, stripLineComments = false).trimIndent(), actualText)
+                // Make sure we can read back the files we write
+            }
         }
 
         if (removedApi != null && removedApiFile != null) {
@@ -644,8 +969,10 @@
                 "${removedApiFile.path} does not exist even though --removed-api was used",
                 removedApiFile.exists()
             )
-            val expectedText = readFile(removedApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(removedApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(removedApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(removedApi, stripLineComments = false).trimIndent(), actualText)
+            // Make sure we can read back the files we write
+            ApiFile.parseApi(removedApiFile, options.outputKotlinStyleNulls, true)
         }
 
         if (removedDexApi != null && removedDexApiFile != null) {
@@ -653,8 +980,8 @@
                 "${removedDexApiFile.path} does not exist even though --removed-dex-api was used",
                 removedDexApiFile.exists()
             )
-            val expectedText = readFile(removedDexApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(removedDexApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(removedDexApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(removedDexApi, stripLineComments = false).trimIndent(), actualText)
         }
 
         if (exactApi != null && exactApiFile != null) {
@@ -662,8 +989,10 @@
                 "${exactApiFile.path} does not exist even though --exact-api was used",
                 exactApiFile.exists()
             )
-            val expectedText = readFile(exactApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(exactApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(exactApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(exactApi, stripLineComments = false).trimIndent(), actualText)
+            // Make sure we can read back the files we write
+            ApiFile.parseApi(exactApiFile, options.outputKotlinStyleNulls, true)
         }
 
         if (privateApi != null && privateApiFile != null) {
@@ -671,8 +1000,10 @@
                 "${privateApiFile.path} does not exist even though --private-api was used",
                 privateApiFile.exists()
             )
-            val expectedText = readFile(privateApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(privateApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(privateApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(privateApi, stripLineComments = false).trimIndent(), actualText)
+            // Make sure we can read back the files we write
+            ApiFile.parseApi(privateApiFile, options.outputKotlinStyleNulls, true)
         }
 
         if (dexApi != null && dexApiFile != null) {
@@ -680,8 +1011,8 @@
                 "${dexApiFile.path} does not exist even though --dex-api was used",
                 dexApiFile.exists()
             )
-            val expectedText = readFile(dexApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(dexApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(dexApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(dexApi, stripLineComments = false).trimIndent(), actualText)
         }
 
         if (privateDexApi != null && privateDexApiFile != null) {
@@ -689,8 +1020,17 @@
                 "${privateDexApiFile.path} does not exist even though --private-dex-api was used",
                 privateDexApiFile.exists()
             )
-            val expectedText = readFile(privateDexApiFile, stripBlankLines, trim)
-            assertEquals(stripComments(privateDexApi, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(privateDexApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(privateDexApi, stripLineComments = false).trimIndent(), actualText)
+        }
+
+        if (dexApiMapping != null && dexApiMappingFile != null) {
+            assertTrue(
+                "${dexApiMappingFile.path} does not exist even though --dex-api-maping was used",
+                dexApiMappingFile.exists()
+            )
+            val actualText = readFile(dexApiMappingFile, stripBlankLines, trim)
+            assertEquals(stripComments(dexApiMapping, stripLineComments = false).trimIndent(), actualText)
         }
 
         if (proguard != null && proguardFile != null) {
@@ -735,10 +1075,7 @@
         if (warnings != null) {
             assertEquals(
                 warnings.trimIndent().trim(),
-                reportedWarnings.toString().replace(project.path, "TESTROOT").replace(
-                    project.canonicalPath,
-                    "TESTROOT"
-                ).trim()
+                cleanupString(reportedWarnings.toString(), project)
             )
         }
 
@@ -752,9 +1089,19 @@
             }
         }
 
+        if (validateNullabilityTxt != null) {
+            assertTrue(
+                "Using $ARG_NULLABILITY_WARNINGS_TXT but $validateNullabilityTxt was not created",
+                validateNullabilityTxt.isFile
+            )
+            val actualReport =
+                Files.asCharSource(validateNullabilityTxt, Charsets.UTF_8).readLines().map(String::trim).toSet()
+            assertEquals(validateNullability, actualReport)
+        }
+
         if (stubs.isNotEmpty() && stubsDir != null) {
             for (i in 0 until stubs.size) {
-                val stub = stubs[i]
+                var stub = stubs[i].trimIndent()
                 val sourceFile = sourceFiles[i]
                 val targetPath = if (sourceFile.targetPath.endsWith(DOT_KT)) {
                     // Kotlin source stubs are rewritten as .java files for now
@@ -762,9 +1109,35 @@
                 } else {
                     sourceFile.targetPath
                 }
-                val stubFile = File(stubsDir, targetPath.substring("src/".length))
-                val expectedText = readFile(stubFile, stripBlankLines, trim)
-                assertEquals(stub.trimIndent(), expectedText)
+                var stubFile = File(stubsDir, targetPath.substring("src/".length))
+                if (!stubFile.isFile) {
+                    if (stub.startsWith("[") && stub.contains("]")) {
+                        val pathEnd = stub.indexOf("]\n")
+                        val path = stub.substring(1, pathEnd)
+                        stubFile = File(stubsDir, path)
+                        if (stubFile.isFile) {
+                            stub = stub.substring(pathEnd + 2)
+                        }
+                    }
+                    if (!stubFile.exists()) {
+                        /* Example:
+                            stubs = arrayOf(
+                                """
+                                [test/visible/package-info.java]
+                                <html>My package docs</html>
+                                package test.visible;
+                                """,
+                                ...
+                           Here the stub will be read from $stubsDir/test/visible/package-info.java.
+                         */
+                        throw FileNotFoundException(
+                            "Could not find generated stub for $targetPath; consider " +
+                                "setting target relative path in stub header as prefix surrounded by []"
+                        )
+                    }
+                }
+                val actualText = readFile(stubFile, stripBlankLines, trim)
+                assertEquals(stub, actualText)
             }
         }
 
@@ -773,12 +1146,12 @@
                 "${stubsSourceListFile.path} does not exist even though --write-stubs-source-list was used",
                 stubsSourceListFile.exists()
             )
-            val expectedText = readFile(stubsSourceListFile, stripBlankLines, trim)
-            assertEquals(stripComments(stubsSourceList, stripLineComments = false).trimIndent(), expectedText)
+            val actualText = readFile(stubsSourceListFile, stripBlankLines, trim)
+            assertEquals(stripComments(stubsSourceList, stripLineComments = false).trimIndent(), actualText)
         }
 
         if (checkCompilation && stubsDir != null && CHECK_STUB_COMPILATION) {
-            val generated = gatherSources(listOf(stubsDir)).map { it.path }.toList().toTypedArray()
+            val generated = gatherSources(listOf(stubsDir)).asSequence().map { it.path }.toList().toTypedArray()
 
             // Also need to include on the compile path annotation classes referenced in the stubs
             val extraAnnotationsDir = File("stub-annotations/src/main/java")
@@ -786,7 +1159,8 @@
                 fail("Couldn't find $extraAnnotationsDir: Is the pwd set to the root of the metalava source code?")
                 fail("Couldn't find $extraAnnotationsDir: Is the pwd set to the root of an Android source tree?")
             }
-            val extraAnnotations = gatherSources(listOf(extraAnnotationsDir)).map { it.path }.toList().toTypedArray()
+            val extraAnnotations =
+                gatherSources(listOf(extraAnnotationsDir)).asSequence().map { it.path }.toList().toTypedArray()
 
             if (!runCommand(
                     "${getJdkPath()}/bin/javac", arrayOf(
@@ -810,7 +1184,7 @@
             api != null && apiFile != null
         ) {
             apiFile.delete()
-            checkSignaturesWithDoclava1(
+            checkSignaturesWithDoclava(
                 api = api,
                 argument = "-api",
                 output = apiFile,
@@ -823,15 +1197,38 @@
                 stripBlankLines = stripBlankLines,
                 showAnnotationArgs = showAnnotationArguments,
                 stubImportPackages = importedPackages,
-                showUnannotated = showUnannotated
+                showUnannotated = showUnannotated,
+                project = project,
+                extraArguments = extraArguments
             )
         }
 
+        if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && apiXml != null && apiXmlFile != null) {
+            apiXmlFile.delete()
+
+            // Either we write the signatureSource, or you must have specified an API report
+            val signatureFile: File =
+                apiFile ?: if (signatureSource != null) {
+                    val temp = temporaryFolder.newFile("jdiff-doclava-api.txt")
+                    temp.writeText(signatureSource.trimIndent(), Charsets.UTF_8)
+                    temp
+                } else {
+                    fail("When verifying XML files with doclava you must either specify signatureSource or api")
+                    error("Unreachable")
+                }
+
+            // Need to emit the codebase
+            generateJDiffXmlWithDoclava1(signatureFile, apiXmlFile)
+
+            val actualText = cleanupString(readFile(apiXmlFile, stripBlankLines, trim), project, true)
+            assertEquals(stripComments(apiXml, stripLineComments = false).trimIndent(), actualText)
+        }
+
         if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null &&
             exactApi != null && exactApiFile != null
         ) {
             exactApiFile.delete()
-            checkSignaturesWithDoclava1(
+            checkSignaturesWithDoclava(
                 api = exactApi,
                 argument = "-exactApi",
                 output = exactApiFile,
@@ -844,7 +1241,9 @@
                 stripBlankLines = stripBlankLines,
                 showAnnotationArgs = showAnnotationArguments,
                 stubImportPackages = importedPackages,
-                showUnannotated = showUnannotated
+                showUnannotated = showUnannotated,
+                project = project,
+                extraArguments = extraArguments
             )
         }
 
@@ -852,7 +1251,7 @@
             removedApi != null && removedApiFile != null
         ) {
             removedApiFile.delete()
-            checkSignaturesWithDoclava1(
+            checkSignaturesWithDoclava(
                 api = removedApi,
                 argument = "-removedApi",
                 output = removedApiFile,
@@ -865,14 +1264,16 @@
                 stripBlankLines = stripBlankLines,
                 showAnnotationArgs = showAnnotationArguments,
                 stubImportPackages = importedPackages,
-                showUnannotated = showUnannotated
+                showUnannotated = showUnannotated,
+                project = project,
+                extraArguments = extraArguments
             )
         }
 
         if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null && stubsDir != null) {
             stubsDir.deleteRecursively()
             val firstFile = File(stubsDir, sourceFiles[0].targetPath.substring("src/".length))
-            checkSignaturesWithDoclava1(
+            checkSignaturesWithDoclava(
                 api = stubs[0],
                 argument = "-stubs",
                 output = stubsDir,
@@ -885,13 +1286,15 @@
                 stripBlankLines = stripBlankLines,
                 showAnnotationArgs = showAnnotationArguments,
                 stubImportPackages = importedPackages,
-                showUnannotated = showUnannotated
+                showUnannotated = showUnannotated,
+                project = project,
+                extraArguments = extraArguments
             )
         }
 
         if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && proguard != null && proguardFile != null) {
             proguardFile.delete()
-            checkSignaturesWithDoclava1(
+            checkSignaturesWithDoclava(
                 api = proguard,
                 argument = "-proguard",
                 output = proguardFile,
@@ -904,7 +1307,9 @@
                 stripBlankLines = stripBlankLines,
                 showAnnotationArgs = showAnnotationArguments,
                 stubImportPackages = importedPackages,
-                showUnannotated = showUnannotated
+                showUnannotated = showUnannotated,
+                project = project,
+                extraArguments = extraArguments
             )
         }
 
@@ -912,7 +1317,7 @@
             privateApi != null && privateApiFile != null
         ) {
             privateApiFile.delete()
-            checkSignaturesWithDoclava1(
+            checkSignaturesWithDoclava(
                 api = privateApi,
                 argument = "-privateApi",
                 output = privateApiFile,
@@ -926,8 +1331,10 @@
                 showAnnotationArgs = showAnnotationArguments,
                 stubImportPackages = importedPackages,
                 // Workaround: -privateApi is a no-op if you don't also provide -api
-                extraArguments = arrayOf("-api", File(privateApiFile.parentFile, "dummy-api.txt").path),
-                showUnannotated = showUnannotated
+                extraDoclavaArguments = arrayOf("-api", File(privateApiFile.parentFile, "dummy-api.txt").path),
+                showUnannotated = showUnannotated,
+                project = project,
+                extraArguments = extraArguments
             )
         }
 
@@ -935,7 +1342,7 @@
             privateDexApi != null && privateDexApiFile != null
         ) {
             privateDexApiFile.delete()
-            checkSignaturesWithDoclava1(
+            checkSignaturesWithDoclava(
                 api = privateDexApi,
                 argument = "-privateDexApi",
                 output = privateDexApiFile,
@@ -949,8 +1356,10 @@
                 showAnnotationArgs = showAnnotationArguments,
                 stubImportPackages = importedPackages,
                 // Workaround: -privateDexApi is a no-op if you don't also provide -api
-                extraArguments = arrayOf("-api", File(privateDexApiFile.parentFile, "dummy-api.txt").path),
-                showUnannotated = showUnannotated
+                extraDoclavaArguments = arrayOf("-api", File(privateDexApiFile.parentFile, "dummy-api.txt").path),
+                showUnannotated = showUnannotated,
+                project = project,
+                extraArguments = extraArguments
             )
         }
 
@@ -958,7 +1367,7 @@
             dexApi != null && dexApiFile != null
         ) {
             dexApiFile.delete()
-            checkSignaturesWithDoclava1(
+            checkSignaturesWithDoclava(
                 api = dexApi,
                 argument = "-dexApi",
                 output = dexApiFile,
@@ -972,8 +1381,36 @@
                 showAnnotationArgs = showAnnotationArguments,
                 stubImportPackages = importedPackages,
                 // Workaround: -dexApi is a no-op if you don't also provide -api
-                extraArguments = arrayOf("-api", File(dexApiFile.parentFile, "dummy-api.txt").path),
-                showUnannotated = showUnannotated
+                extraDoclavaArguments = arrayOf("-api", File(dexApiFile.parentFile, "dummy-api.txt").path),
+                showUnannotated = showUnannotated,
+                project = project,
+                extraArguments = extraArguments
+            )
+        }
+
+        if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null &&
+            dexApiMapping != null && dexApiMappingFile != null
+        ) {
+            dexApiMappingFile.delete()
+            checkSignaturesWithDoclava(
+                api = dexApiMapping,
+                argument = "-apiMapping",
+                output = dexApiMappingFile,
+                expected = dexApiMappingFile,
+                sourceList = sourceList,
+                sourcePath = sourcePath,
+                packages = packages,
+                androidJar = androidJar,
+                trim = trim,
+                stripBlankLines = stripBlankLines,
+                showAnnotationArgs = showAnnotationArguments,
+                stubImportPackages = importedPackages,
+                // Workaround: -apiMapping is a no-op if you don't also provide -api
+                extraDoclavaArguments = arrayOf("-api", File(dexApiMappingFile.parentFile, "dummy-api.txt").path),
+                showUnannotated = showUnannotated,
+                project = project,
+                skipTestRoot = true,
+                extraArguments = extraArguments
             )
         }
     }
@@ -997,7 +1434,59 @@
         }
     }
 
-    private fun checkSignaturesWithDoclava1(
+    /** Hides path prefixes from /tmp folders used by the testing infrastructure */
+    private fun cleanupString(string: String, project: File?, dropTestRoot: Boolean = false): String {
+        var s = string
+
+        if (project != null) {
+            s = s.replace(project.path, "TESTROOT")
+            s = s.replace(project.canonicalPath, "TESTROOT")
+        }
+
+        s = s.replace(temporaryFolder.root.path, "TESTROOT")
+
+        val tmp = System.getProperty("java.io.tmpdir")
+        if (tmp != null) {
+            s = s.replace(tmp, "TEST")
+        }
+
+        s = s.trim()
+
+        if (dropTestRoot) {
+            s = s.replace("TESTROOT/", "")
+        }
+
+        return s
+    }
+
+    private fun generateJDiffXmlWithDoclava1(signatureFile: File, xmlOutput: File) {
+        val docLava1 = findDoclava()
+
+        val args = arrayOf(
+            "-convert2xml",
+            signatureFile.path,
+            xmlOutput.path
+        )
+
+        val message = "\n${args.joinToString(separator = "\n") { "\"$it\"," }}"
+        println("Running doclava1 with the following args:\n$message")
+
+        val jdkPath = findJdk()
+        if (!runCommand(
+                "$jdkPath/bin/java",
+                arrayOf(
+                    "-classpath",
+                    "${docLava1.path}:$jdkPath/lib/tools.jar",
+                    "com.google.doclava.apicheck.ApiCheck",
+                    *args
+                )
+            )
+        ) {
+            return
+        }
+    }
+
+    private fun checkSignaturesWithDoclava(
         api: String,
         argument: String,
         output: File,
@@ -1010,8 +1499,11 @@
         stripBlankLines: Boolean = true,
         showAnnotationArgs: Array<String> = emptyArray(),
         stubImportPackages: List<String>,
-        extraArguments: Array<String> = emptyArray(),
-        showUnannotated: Boolean
+        extraArguments: Array<String>,
+        extraDoclavaArguments: Array<String> = emptyArray(),
+        showUnannotated: Boolean,
+        project: File,
+        skipTestRoot: Boolean = false
     ) {
         // We have to run Doclava out of process because running it in process
         // (with Doclava1 jars on the test classpath) only works once; it leaves
@@ -1019,13 +1511,40 @@
         // separately on each test; slower but reliable.
 
         val doclavaArg = when (argument) {
-            "--api" -> "-api"
-            "--removed-api" -> "-removedApi"
+            ARG_API -> "-api"
+            ARG_REMOVED_API -> "-removedApi"
             else -> if (argument.startsWith("--")) argument.substring(1) else argument
         }
 
-        val showAnnotationArgsDoclava1: Array<String> = if (showAnnotationArgs.isNotEmpty()) {
-            showAnnotationArgs.map { if (it == "--show-annotation") "-showAnnotation" else it }.toTypedArray()
+        val showAnnotationArgsDoclava1: Array<String> =
+            if (showAnnotationArgs.isNotEmpty() || extraArguments.isNotEmpty()) {
+                val shown = mutableListOf<String>()
+                extraArguments.forEachIndexed { index, s ->
+                    if (s == ARG_SHOW_ANNOTATION) {
+                        shown += "-showAnnotation"
+                        shown += extraArguments[index + 1]
+                    }
+                }
+                showAnnotationArgs.forEach { s ->
+                    shown += if (s == ARG_SHOW_ANNOTATION) {
+                        "-showAnnotation"
+                    } else {
+                        s
+                    }
+                }
+                shown.toTypedArray()
+            } else {
+                emptyArray()
+            }
+        val hideAnnotationArgsDoclava1: Array<String> = if (extraArguments.isNotEmpty()) {
+            val hidden = mutableListOf<String>()
+            extraArguments.forEachIndexed { index, s ->
+                if (s == ARG_HIDE_ANNOTATION) {
+                    hidden += "-hideAnnotation"
+                    hidden += extraArguments[index + 1]
+                }
+            }
+            hidden.toTypedArray()
         } else {
             emptyArray()
         }
@@ -1035,35 +1554,8 @@
             emptyArray()
         }
 
-        val docLava1 = File("testlibs/doclava-1.0.6-full-SNAPSHOT.jar")
-        if (!docLava1.isFile) {
-            /*
-                Not checked in (it's 22MB).
-                To generate the doclava1 jar, add this to external/doclava/build.gradle and run ./gradlew shadowJar:
-
-                // shadow jar: Includes all dependencies
-                buildscript {
-                    repositories {
-                        jcenter()
-                    }
-                    dependencies {
-                        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2'
-                    }
-                }
-                apply plugin: 'com.github.johnrengelman.shadow'
-                shadowJar {
-                   baseName = "doclava-$version-full-SNAPSHOT"
-                   classifier = null
-                   version = null
-                }
-             */
-            fail("Couldn't find $docLava1: Is the pwd set to the root of the metalava source code?")
-        }
-
-        val jdkPath = getJdkPath()
-        if (jdkPath == null) {
-            fail("JDK not found in the environment; make sure \$JAVA_HOME is set.")
-        }
+        val docLava1 = findDoclava()
+        val jdkPath = findJdk()
 
         val hidePackageArgs = mutableListOf<String>()
         options.hidePackages.forEach {
@@ -1098,9 +1590,10 @@
             androidJar.path,
 
             *showAnnotationArgsDoclava1,
+            *hideAnnotationArgsDoclava1,
             *showUnannotatedArgs,
             *hidePackageArgs.toTypedArray(),
-            *extraArguments,
+            *extraDoclavaArguments,
 
             // -api, or // -stub, etc
             doclavaArg,
@@ -1123,8 +1616,63 @@
             return
         }
 
-        val expectedText = readFile(expected, stripBlankLines, trim)
-        assertEquals(stripComments(api, stripLineComments = false).trimIndent(), expectedText)
+        // If there's a discrepancy between doclava1 and metalava, you can debug
+        // doclava; to do this, open the Doclava project, and take all the
+        // metalava arguments, drop the ones that don't apply and use the
+        // doclava-style names instead of metalava (e.g. -stubs instead of --stubs,
+        // -showAnnotation instead of --show-annotation, -sourcepath instead of
+        // --sourcepath, and so on. Finally, and most importantly, add these
+        // 4 arguments at the beginning:
+        //   "-doclet",
+        //   "com.google.doclava.Doclava",
+        //   "-docletpath",
+        //   "out/host/linux-x86/framework/jsilver.jar:out/host/linux-x86/framework/doclava.jar",
+        // ..and finally from your Main entry point take this array of strings
+        // and call Doclava.main(newArgs)
+
+        val actualText = cleanupString(readFile(expected, stripBlankLines, trim), project, skipTestRoot)
+        assertEquals(stripComments(api, stripLineComments = false).trimIndent(), actualText)
+    }
+
+    private fun findJdk(): String? {
+        val jdkPath = getJdkPath()
+        if (jdkPath == null) {
+            fail("JDK not found in the environment; make sure \$JAVA_HOME is set.")
+        }
+        return jdkPath
+    }
+
+    private fun findDoclava(): File {
+        val docLava1 = File("testlibs/doclava-1.0.6-full-SNAPSHOT.jar")
+        if (!docLava1.isFile) {
+            /*
+                Not checked in (it's 22MB).
+                To generate the doclava1 jar, add this to external/doclava/build.gradle and run ./gradlew shadowJar:
+
+                // shadow jar: Includes all dependencies
+                buildscript {
+                    repositories {
+                        jcenter()
+                    }
+                    dependencies {
+                        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2'
+                    }
+                }
+                apply plugin: 'com.github.johnrengelman.shadow'
+                shadowJar {
+                   baseName = "doclava-$version-full-SNAPSHOT"
+                   classifier = null
+                   version = null
+                }
+
+                and finally
+                $ cp ../../out/host/gradle/external/jdiff/build/libs/doclava-*-SNAPSHOT-full-SNAPSHOT.jar \
+                     testlibs/doclava-1.0.6-full-SNAPSHOT.jar
+
+             */
+            fail("Couldn't find $docLava1: Is the pwd set to the root of the metalava source code?")
+        }
+        return docLava1
     }
 
     private fun runCommand(executable: String, args: Array<String>): Boolean {
@@ -1163,8 +1711,12 @@
             val localFile = File("../../prebuilts/sdk/$apiLevel/public/android.jar")
             if (localFile.exists()) {
                 return localFile
+            } else {
+                val androidJar = File("../../prebuilts/sdk/$apiLevel/android.jar")
+                if (androidJar.exists()) {
+                    return androidJar
+                }
             }
-
             return null
         }
 
@@ -1279,7 +1831,7 @@
     """
 ).indented()
 
-val libcoreNonNullSource: TestFile = DriverTest.java(
+val libcoreNonNullSource: TestFile = java(
     """
     package libcore.util;
     import static java.lang.annotation.ElementType.*;
@@ -1295,7 +1847,7 @@
     """
 ).indented()
 
-val libcoreNullableSource: TestFile = DriverTest.java(
+val libcoreNullableSource: TestFile = java(
     """
     package libcore.util;
     import static java.lang.annotation.ElementType.*;
@@ -1310,6 +1862,7 @@
     }
     """
 ).indented()
+
 val requiresPermissionSource: TestFile = java(
     """
     package android.annotation;
@@ -1636,3 +2189,27 @@
     }
     """
 ).indented()
+
+val restrictToSource: TestFile = java(
+    """
+    package androidx.annotation;
+    import java.lang.annotation.*;
+    import static java.lang.annotation.ElementType.*;
+    import static java.lang.annotation.RetentionPolicy.*;
+    @SuppressWarnings("WeakerAccess")
+    @Retention(CLASS)
+    @Target({ANNOTATION_TYPE, TYPE, METHOD, CONSTRUCTOR, FIELD, PACKAGE})
+    public @interface RestrictTo {
+        Scope[] value();
+        enum Scope {
+            LIBRARY,
+            LIBRARY_GROUP,
+            /** @deprecated */
+            @Deprecated
+            GROUP_ID,
+            TESTS,
+            SUBCLASSES,
+        }
+    }
+    """
+).indented()
diff --git a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
index 5a8ee6f..acd1f4b 100644
--- a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
+++ b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
@@ -164,7 +164,7 @@
                       <item name="test.pkg.LongDefTest void setFlags(java.lang.Object, int) 1">
                         <annotation name="androidx.annotation.LongDef">
                           <val name="flag" val="true" />
-                          <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4}" />
+                          <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4L}" />
                         </annotation>
                       </item>
                       <item name="test.pkg.LongDefTest void setStyle(int, int) 0">
@@ -178,7 +178,7 @@
                       <item name="test.pkg.LongDefTest.Inner void setInner(int) 0">
                         <annotation name="androidx.annotation.LongDef">
                           <val name="flag" val="true" />
-                          <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4}" />
+                          <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4L}" />
                         </annotation>
                       </item>
                       <item name="test.pkg.LongDefTestKt TYPE_1">
@@ -254,7 +254,7 @@
                       <item name="test.pkg.LongDefTest void setFlags(java.lang.Object, int) 1">
                         <annotation name="androidx.annotation.LongDef">
                           <val name="flag" val="true" />
-                          <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4}" />
+                          <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4L}" />
                         </annotation>
                       </item>
                       <item name="test.pkg.LongDefTest void setStyle(int, int) 0">
@@ -265,7 +265,7 @@
                       <item name="test.pkg.LongDefTest.Inner void setInner(int) 0">
                         <annotation name="androidx.annotation.LongDef">
                           <val name="flag" val="true" />
-                          <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4}" />
+                          <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4L}" />
                         </annotation>
                       </item>
                     </root>
@@ -343,7 +343,7 @@
         )
     }
 
-    @Ignore("Not working reliably")
+    @Ignore("Not working reliably -- fails when run and passes when debugged...")
     @Test
     fun `Include merged annotations in exported source annotations`() {
         check(
@@ -425,4 +425,80 @@
             )
         )
     }
+
+    @Test
+    fun `Check warning about unexpected returns from typedef method`() {
+        check(
+            includeSourceRetentionAnnotations = false,
+            warnings = "src/test/pkg/IntDefTest.java:36: warning: Returning unexpected constant UNRELATED; is @DialogStyle missing this constant? Expected one of STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT [ReturningUnexpectedConstant:151]",
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import android.annotation.IntDef;
+                    import android.annotation.IntRange;
+                    import java.lang.annotation.Retention;
+                    import java.lang.annotation.RetentionPolicy;
+
+                    @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"})
+                    public class IntDefTest {
+                        @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
+                        @IntRange(from = 20)
+                        @Retention(RetentionPolicy.SOURCE)
+                        private @interface DialogStyle {
+                        }
+
+                        public static final int STYLE_NORMAL = 0;
+                        public static final int STYLE_NO_TITLE = 1;
+                        public static final int STYLE_NO_FRAME = 2;
+                        public static final int STYLE_NO_INPUT = 3;
+                        public static final int UNRELATED = 3;
+                        public static final int[] EMPTY_ARRAY = new int[0];
+
+                        private int mField1 = 4;
+                        private int mField2 = 5;
+
+                        @DialogStyle
+                        public int getStyle1() {
+                            //noinspection ConstantConditions
+                            if (mField1 < 1) {
+                                return STYLE_NO_TITLE; // OK
+                            } else if (mField1 < 2) {
+                                return 0; // OK
+                            } else if (mField1 < 3) {
+                                return mField2; // OK
+                            } else {
+                                return UNRELATED; // WARN
+                            }
+                        }
+
+                        @DialogStyle
+                        public int[] getStyle2() {
+                            return EMPTY_ARRAY; // OK
+                        }
+                    }
+                    """
+                ).indented(),
+                intDefAnnotationSource
+            ),
+            extractAnnotations = mapOf(
+                "test.pkg" to """
+                <?xml version="1.0" encoding="UTF-8"?>
+                <root>
+                  <item name="test.pkg.IntDefTest int getStyle1()">
+                    <annotation name="androidx.annotation.IntDef">
+                      <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}" />
+                    </annotation>
+                  </item>
+                  <item name="test.pkg.IntDefTest int[] getStyle2()">
+                    <annotation name="androidx.annotation.IntDef">
+                      <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}" />
+                    </annotation>
+                  </item>
+                </root>
+                """
+            )
+        )
+    }
 }
\ 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
new file mode 100644
index 0000000..f4c023f
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt
@@ -0,0 +1,734 @@
+/*
+ * 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 JDiffXmlTest : DriverTest() {
+
+    @Test
+    fun `Loading a signature file and writing the API back out`() {
+        check(
+            compatibilityMode = true,
+            signatureSource =
+            """
+            package test.pkg {
+              public deprecated class MyTest {
+                ctor public MyTest();
+                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;
+              }
+            }
+            """,
+            apiXml =
+            """
+            <api>
+            <package name="test.pkg"
+            >
+            <class name="MyTest"
+             extends="java.lang.Object"
+             abstract="false"
+             static="false"
+             final="false"
+             deprecated="deprecated"
+             visibility="public"
+            >
+            <constructor name="MyTest"
+             type="test.pkg.MyTest"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </constructor>
+            <method name="clamp"
+             return="int"
+             abstract="false"
+             native="false"
+             synchronized="false"
+             static="false"
+             final="false"
+             deprecated="deprecated"
+             visibility="public"
+            >
+            <parameter name="null" type="int">
+            </parameter>
+            </method>
+            <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>
+            <field name="myNumber"
+             type="java.lang.Number"
+             transient="false"
+             volatile="false"
+             static="false"
+             final="false"
+             deprecated="deprecated"
+             visibility="public"
+            >
+            </field>
+            </class>
+            </package>
+            </api>
+            """
+        )
+    }
+
+    @Test
+    fun `Test generics, superclasses and interfaces`() {
+        val source = """
+            package a.b.c {
+              public abstract interface MyStream<T, S extends a.b.c.MyStream<T, S>> {
+              }
+            }
+            package test.pkg {
+              public final class Foo extends java.lang.Enum {
+                ctor public Foo(int);
+                ctor public Foo(int, int);
+                method public static test.pkg.Foo valueOf(java.lang.String);
+                method public static final test.pkg.Foo[] values();
+              }
+              public abstract interface MyBaseInterface {
+              }
+              public abstract interface MyInterface<T> implements test.pkg.MyBaseInterface {
+              }
+              public abstract interface MyInterface2<T extends java.lang.Number> implements test.pkg.MyBaseInterface {
+              }
+              public static abstract class MyInterface2.Range<T extends java.lang.Comparable<? super T>> {
+                ctor public MyInterface2.Range();
+              }
+              public static class MyInterface2.TtsSpan<C extends test.pkg.MyInterface<?>> {
+                ctor public MyInterface2.TtsSpan();
+              }
+              public final class Test<T> {
+                ctor public Test();
+              }
+            }
+            """
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            checkDoclava1 = true,
+            apiXml =
+            """
+            <api>
+            <package name="a.b.c"
+            >
+            <interface name="MyStream"
+             abstract="true"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </interface>
+            </package>
+            <package name="test.pkg"
+            >
+            <class name="Foo"
+             extends="java.lang.Enum"
+             abstract="false"
+             static="false"
+             final="true"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <constructor name="Foo"
+             type="test.pkg.Foo"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <parameter name="null" type="int">
+            </parameter>
+            </constructor>
+            <constructor name="Foo"
+             type="test.pkg.Foo"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <parameter name="null" type="int">
+            </parameter>
+            <parameter name="null" type="int">
+            </parameter>
+            </constructor>
+            <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>
+            </class>
+            <interface name="MyBaseInterface"
+             abstract="true"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </interface>
+            <interface name="MyInterface"
+             abstract="true"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <implements name="test.pkg.MyBaseInterface">
+            </implements>
+            </interface>
+            <interface name="MyInterface2"
+             abstract="true"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <implements name="test.pkg.MyBaseInterface">
+            </implements>
+            </interface>
+            <class name="MyInterface2.Range"
+             extends="java.lang.Object"
+             abstract="true"
+             static="true"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <constructor name="MyInterface2.Range"
+             type="test.pkg.MyInterface2.Range"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </constructor>
+            </class>
+            <class name="MyInterface2.TtsSpan"
+             extends="java.lang.Object"
+             abstract="false"
+             static="true"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <constructor name="MyInterface2.TtsSpan"
+             type="test.pkg.MyInterface2.TtsSpan"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </constructor>
+            </class>
+            <class name="Test"
+             extends="java.lang.Object"
+             abstract="false"
+             static="false"
+             final="true"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <constructor name="Test"
+             type="test.pkg.Test"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </constructor>
+            </class>
+            </package>
+            </api>
+            """
+        )
+    }
+
+    @Test
+    fun `Test enums`() {
+        val source = """
+            package test.pkg {
+              public final class Foo extends java.lang.Enum {
+                ctor public Foo(int);
+                ctor public Foo(int, int);
+                method public static test.pkg.Foo valueOf(java.lang.String);
+                method public static final test.pkg.Foo[] values();
+                enum_constant public static final test.pkg.Foo A;
+                enum_constant public static final test.pkg.Foo B;
+              }
+            }
+            """
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            checkDoclava1 = false, // broken because doclava1 does not include enum fields
+            apiXml =
+            """
+            <api>
+            <package name="test.pkg"
+            >
+            <class name="Foo"
+             extends="java.lang.Enum"
+             abstract="false"
+             static="false"
+             final="true"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <constructor name="Foo"
+             type="test.pkg.Foo"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <parameter name="null" type="int">
+            </parameter>
+            </constructor>
+            <constructor name="Foo"
+             type="test.pkg.Foo"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <parameter name="null" type="int">
+            </parameter>
+            <parameter name="null" type="int">
+            </parameter>
+            </constructor>
+            <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>
+            <field name="A"
+             type="test.pkg.Foo"
+             transient="false"
+             volatile="false"
+             static="true"
+             final="true"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </field>
+            <field name="B"
+             type="test.pkg.Foo"
+             transient="false"
+             volatile="false"
+             static="true"
+             final="true"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </field>
+            </class>
+            </package>
+            </api>
+            """
+        )
+    }
+
+    @Test
+    fun `Throws Lists`() {
+        check(
+            compatibilityMode = true,
+            signatureSource = """
+                    package android.accounts {
+                      public abstract interface AccountManagerFuture<V> {
+                        method public abstract V getResult() throws android.accounts.OperationCanceledException, java.io.IOException, android.accounts.AuthenticatorException;
+                        method public abstract V getResult(long, java.util.concurrent.TimeUnit) throws android.accounts.OperationCanceledException, java.io.IOException, android.accounts.AuthenticatorException;
+                      }
+                    }
+                    """,
+            apiXml =
+            """
+            <api>
+            <package name="android.accounts"
+            >
+            <interface name="AccountManagerFuture"
+             abstract="true"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <method name="getResult"
+             return="V"
+             abstract="true"
+             native="false"
+             synchronized="false"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <exception name="AuthenticatorException" type="android.accounts.AuthenticatorException">
+            </exception>
+            <exception name="IOException" type="java.io.IOException">
+            </exception>
+            <exception name="OperationCanceledException" type="android.accounts.OperationCanceledException">
+            </exception>
+            </method>
+            <method name="getResult"
+             return="V"
+             abstract="true"
+             native="false"
+             synchronized="false"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <parameter name="null" type="long">
+            </parameter>
+            <parameter name="null" type="java.util.concurrent.TimeUnit">
+            </parameter>
+            <exception name="AuthenticatorException" type="android.accounts.AuthenticatorException">
+            </exception>
+            <exception name="IOException" type="java.io.IOException">
+            </exception>
+            <exception name="OperationCanceledException" type="android.accounts.OperationCanceledException">
+            </exception>
+            </method>
+            </interface>
+            </package>
+            </api>
+            """
+        )
+    }
+
+    @Test
+    fun `Test conversion flag`() {
+        check(
+            compatibilityMode = true,
+            convertToJDiff = listOf(
+                Pair(
+                    """
+                    package test.pkg {
+                      public class MyTest1 {
+                        ctor public MyTest1();
+                      }
+                    }
+                    """,
+                    """
+                    <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>
+                    """
+                ),
+                Pair(
+                    """
+                    package test.pkg {
+                      public class MyTest2 {
+                      }
+                    }
+                    """,
+                    """
+                    <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(
+            compatibilityMode = false,
+            signatureSource = """
+                    package android.accounts {
+                      public class ArgbEvaluator implements android.animation.DefaultEvaluator<D> implements android.animation.TypeEvaluator<V> {
+                      }
+                    }
+                    """,
+            apiXml =
+            """
+            <api>
+            <package name="android.accounts"
+            >
+            <class name="ArgbEvaluator"
+             extends="java.lang.Object"
+             abstract="false"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            <implements name="android.animation.DefaultEvaluator&lt;D>">
+            </implements>
+            <implements name="android.animation.TypeEvaluator&lt;V>">
+            </implements>
+            <implements name="java.lang.implements">
+            </implements>
+            </class>
+            </package>
+            </api>
+            """
+        )
+    }
+
+    @Test
+    fun `Type Parameter Mapping`() {
+        check(
+            compatibilityMode = false,
+            signatureSource = """
+                package test.pkg {
+                  public interface AbstractList<D,E,F> extends test.pkg.List<A,B,C> {
+                  }
+                  public interface ConcreteList<G,H,I> extends test.pkg.AbstractList<D,E,F> {
+                  }
+                  public interface List<A,B,C> {
+                  }
+                }
+                """,
+            apiXml =
+            """
+            <api>
+            <package name="test.pkg"
+            >
+            <interface name="AbstractList"
+             extends="test.pkg.List&lt;A,B,C>"
+             abstract="false"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </interface>
+            <interface name="ConcreteList"
+             extends="test.pkg.AbstractList&lt;D,E,F>"
+             abstract="false"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </interface>
+            <interface name="List"
+             abstract="false"
+             static="false"
+             final="false"
+             deprecated="not deprecated"
+             visibility="public"
+            >
+            </interface>
+            </package>
+            </api>
+            """
+        )
+    }
+
+    @Test
+    fun `Half float short from signature file`() {
+        check(
+            compatibilityMode = false,
+            signatureSource = """
+                package test.pkg {
+                  public class Test {
+                    ctor public Test();
+                    field public static final short LOWEST_VALUE = -1025; // 0xfffffbff
+                  }
+                }
+            """,
+            apiXml =
+            """
+                <api>
+                <package name="test.pkg"
+                >
+                <class name="Test"
+                 extends="java.lang.Object"
+                 abstract="false"
+                 static="false"
+                 final="false"
+                 deprecated="not deprecated"
+                 visibility="public"
+                >
+                <constructor name="Test"
+                 type="test.pkg.Test"
+                 static="false"
+                 final="false"
+                 deprecated="not deprecated"
+                 visibility="public"
+                >
+                </constructor>
+                <field name="LOWEST_VALUE"
+                 type="short"
+                 transient="false"
+                 volatile="false"
+                 value="-1025"
+                 static="true"
+                 final="true"
+                 deprecated="not deprecated"
+                 visibility="public"
+                >
+                </field>
+                </class>
+                </package>
+                </api>
+            """
+        )
+    }
+
+    @Test
+    fun `Half float short from source`() {
+        check(
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                      package test.pkg;
+                      public class Test {
+                        public static final short LOWEST_VALUE = (short) 0xfbff;
+                      }
+                      """
+                )
+            ),
+            apiXml =
+            """
+                <api>
+                <package name="test.pkg"
+                >
+                <class name="Test"
+                 extends="java.lang.Object"
+                 abstract="false"
+                 static="false"
+                 final="false"
+                 deprecated="not deprecated"
+                 visibility="public"
+                >
+                <constructor name="Test"
+                 type="test.pkg.Test"
+                 static="false"
+                 final="false"
+                 deprecated="not deprecated"
+                 visibility="public"
+                >
+                </constructor>
+                <field name="LOWEST_VALUE"
+                 type="short"
+                 transient="false"
+                 volatile="false"
+                 value="-1025"
+                 static="true"
+                 final="true"
+                 deprecated="not deprecated"
+                 visibility="public"
+                >
+                </field>
+                </class>
+                </package>
+                </api>
+            """
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt b/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt
index e5233fa..4b218f9 100644
--- a/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt
+++ b/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt
@@ -48,7 +48,7 @@
                   }
                 }
                 """,
-            extraArguments = arrayOf("--java-source", "1.9")
+            extraArguments = arrayOf(ARG_JAVA_SOURCE, "1.9")
         )
     }
 
@@ -143,7 +143,7 @@
                   }
                 }
                 """,
-            extraArguments = arrayOf("--java-source", "1.9")
+            extraArguments = arrayOf(ARG_JAVA_SOURCE, "1.9")
         )
     }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/KeepFileTest.kt b/src/test/java/com/android/tools/metalava/KeepFileTest.kt
index 442b2c1..960d825 100644
--- a/src/test/java/com/android/tools/metalava/KeepFileTest.kt
+++ b/src/test/java/com/android/tools/metalava/KeepFileTest.kt
@@ -79,7 +79,7 @@
                     <init>();
                 }
                 """,
-            extraArguments = arrayOf("--hide", "KotlinKeyword")
+            extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
         )
     }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
index 2360fb2..4e32d1d 100644
--- a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
+++ b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
@@ -22,7 +22,7 @@
     @Test
     fun `Hard Kotlin keywords`() {
         check(
-            extraArguments = arrayOf("--check-kotlin-interop"),
+            extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
             warnings = """
                 src/test/pkg/Test.java:5: warning: Avoid method names that are Kotlin hard keywords ("fun"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword:141]
                 src/test/pkg/Test.java:6: warning: Avoid parameter names that are Kotlin hard keywords ("typealias"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword:141]
@@ -49,7 +49,7 @@
     @Test
     fun `Sam-compatible parameters should be last`() {
         check(
-            extraArguments = arrayOf("--check-kotlin-interop"),
+            extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
             warnings = """
                 src/test/pkg/Test.java:10: warning: SAM-compatible parameters (such as parameter 1, "run", in test.pkg.Test.error) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions [SamShouldBeLast:142]
                 src/test/pkg/test.kt:7: warning: lambda parameters (such as parameter 1, "bar", in test.pkg.TestKt.error) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions [SamShouldBeLast:142]
@@ -88,7 +88,7 @@
     @Test
     fun `Companion object methods should be marked with JvmStatic`() {
         check(
-            extraArguments = arrayOf("--check-kotlin-interop"),
+            extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
             warnings = """
                 src/test/pkg/Foo.kt:7: warning: Companion object constants like INTEGER_ONE should be marked @JvmField for Java interoperability; see https://android.github.io/kotlin-guides/interop.html#companion-constants [MissingJvmstatic:143]
                 src/test/pkg/Foo.kt:13: warning: Companion object methods like missing should be marked @JvmStatic for Java interoperability; see https://android.github.io/kotlin-guides/interop.html#companion-functions [MissingJvmstatic:143]
@@ -123,7 +123,7 @@
     @Test
     fun `Methods with default parameters should specify JvmOverloads`() {
         check(
-            extraArguments = arrayOf("--check-kotlin-interop"),
+            extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
             warnings = """
                 src/test/pkg/Foo.kt:8: warning: A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults [MissingJvmstatic:143]
                 """,
@@ -150,7 +150,7 @@
     @Test
     fun `Methods which throw exceptions should document them`() {
         check(
-            extraArguments = arrayOf("--check-kotlin-interop"),
+            extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
             warnings = """
                 src/test/pkg/Foo.kt:6: error: Method Foo.error_throws_multiple_times appears to be throwing java.io.FileNotFoundException; this should be recorded with a @Throws annotation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions [DocumentExceptions:145]
                 src/test/pkg/Foo.kt:16: error: Method Foo.error_throwsCheckedExceptionWithWrongExceptionClassInThrows appears to be throwing java.io.FileNotFoundException; this should be recorded with a @Throws annotation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions [DocumentExceptions:145]
diff --git a/src/test/java/com/android/tools/metalava/NullabilityAnnotationsValidatorTest.kt b/src/test/java/com/android/tools/metalava/NullabilityAnnotationsValidatorTest.kt
new file mode 100644
index 0000000..50203df
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/NullabilityAnnotationsValidatorTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2017 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 NullabilityAnnotationsValidatorTest : DriverTest() {
+
+    @Test
+    fun `Empty report when all expected annotations present`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+
+                        public interface Appendable {
+                            Appendable append(CharSequence csq) throws IOException;
+                        }
+
+                        public interface List<T> {
+                            T get(int index);
+                        }
+
+                        // This is not annotated at all, shouldn't complain about this.
+                        public interface NotAnnotated {
+                            NotAnnotated combine(NotAnnotated other);
+                        }
+                    """
+                )
+            ),
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            omitCommonPackages = false,
+            mergeJavaStubAnnotations = """
+                package test.pkg;
+
+                import libcore.util.NonNull;
+                import libcore.util.Nullable;
+                import libcore.util.NullFromTypeParam;
+
+                public interface Appendable {
+                    @NonNull Appendable append(@Nullable java.lang.CharSequence csq);
+                }
+
+                public interface List<T> {
+                    @NullFromTypeParam T get(int index);
+                }
+                """,
+            extraArguments = arrayOf(ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS),
+            validateNullability = setOf()
+        )
+    }
+
+    @Test
+    fun `Missing parameter annotation`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+
+                        public interface Appendable {
+                            Appendable append(CharSequence csq) throws IOException;
+                        }
+
+                        public interface List<T> {
+                            T get(int index);
+                        }
+                    """
+                )
+            ),
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            omitCommonPackages = false,
+            mergeJavaStubAnnotations = """
+                package test.pkg;
+
+                import libcore.util.NonNull;
+                import libcore.util.NullFromTypeParam;
+
+                public interface Appendable {
+                    @NonNull Appendable append(java.lang.CharSequence csq);
+                }
+
+                public interface List<T> {
+                    @NullFromTypeParam T get(int index);
+                }
+                """,
+            extraArguments = arrayOf(ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS),
+            validateNullability = setOf(
+                "WARNING: method test.pkg.Appendable.append(CharSequence), parameter csq, MISSING"
+            )
+        )
+    }
+
+    @Test
+    fun `Missing return type annotations`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+
+                        public interface Appendable {
+                            Appendable append(CharSequence csq) throws IOException;
+                        }
+
+                        public interface List<T> {
+                            T get(int index);
+                        }
+                    """
+                )
+            ),
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            omitCommonPackages = false,
+            mergeJavaStubAnnotations = """
+                package test.pkg;
+
+                import libcore.util.Nullable;
+
+                public interface Appendable {
+                    Appendable append(@Nullable java.lang.CharSequence csq);
+                }
+
+                public interface List<T> {
+                }
+                """,
+            extraArguments = arrayOf(ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS),
+            validateNullability = setOf(
+                "WARNING: method test.pkg.Appendable.append(CharSequence), return value, MISSING",
+                "WARNING: method test.pkg.List.get(int), return value, MISSING"
+            )
+        )
+    }
+
+    @Test
+    fun `Error from annotation on primitive`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+
+                        public interface Appendable {
+                            Appendable append(CharSequence csq) throws IOException;
+                        }
+
+                        public interface List<T> {
+                            T get(int index);
+                        }
+                    """
+                )
+            ),
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            omitCommonPackages = false,
+            mergeJavaStubAnnotations = """
+                package test.pkg;
+
+                import libcore.util.NonNull;
+                import libcore.util.Nullable;
+                import libcore.util.NullFromTypeParam;
+
+                public interface Appendable {
+                    @NonNull Appendable append(@Nullable java.lang.CharSequence csq);
+                }
+
+                public interface List<T> {
+                    @NullFromTypeParam T get(@NonNull int index);
+                }
+                """,
+            extraArguments = arrayOf(ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS),
+            validateNullability = setOf(
+                "ERROR: method test.pkg.List.get(int), parameter index, ON_PRIMITIVE"
+            )
+        )
+    }
+
+    @Test
+    fun `Error from NullFromTypeParam not on type param`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+
+                        public interface Appendable {
+                            Appendable append(CharSequence csq) throws IOException;
+                        }
+
+                        public interface List<T> {
+                            T get(int index);
+                        }
+                    """
+                )
+            ),
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            omitCommonPackages = false,
+            mergeJavaStubAnnotations = """
+                package test.pkg;
+
+                import libcore.util.Nullable;
+                import libcore.util.NullFromTypeParam;
+
+                public interface Appendable {
+                    @NullFromTypeParam Appendable append(@Nullable java.lang.CharSequence csq);
+                }
+
+                public interface List<T> {
+                    @NullFromTypeParam T get(int index);
+                }
+                """,
+            extraArguments = arrayOf(ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS),
+            validateNullability = setOf(
+                "ERROR: method test.pkg.Appendable.append(CharSequence), return value, BAD_TYPE_PARAM"
+            )
+        )
+    }
+
+    @Test
+    fun `Using class list`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+
+                        import libcore.util.Nullable;
+
+                        // This will be validated. It is missing an annotation on its return type.
+                        public interface Appendable {
+                            Appendable append(@Nullable CharSequence csq) throws IOException;
+                        }
+
+                        // This is missing an annotation on its return type, but will not be validated.
+                        public interface List<T> {
+                            T get(int index);
+                        }
+                    """
+                ),
+                libcoreNullableSource
+            ),
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            omitCommonPackages = false,
+            extraArguments = arrayOf(ARG_VALIDATE_NULLABILITY_FROM_MERGED_STUBS),
+            validateNullabilityFromList =
+            """
+                # a comment, then a blank line, then the class to validate
+
+                test.pkg.Appendable
+            """,
+            validateNullability = setOf(
+                "WARNING: method test.pkg.Appendable.append(CharSequence), return value, MISSING"
+            )
+        )
+    }
+}
diff --git a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
index 6c1e2ef..c43bfc6 100644
--- a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
+++ b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
@@ -24,18 +24,23 @@
     fun `Test Kotlin-style null signatures`() {
         check(
             compatibilityMode = false,
-            signatureSource = """
-                package test.pkg {
-                  public class MyTest {
-                    ctor public MyTest();
-                    method public Double convert0(Float);
-                    method @Nullable public Double convert1(@NonNull Float);
-                    method @Nullable public Double convert2(@NonNull Float);
-                    method @Nullable public Double convert3(@NonNull Float);
-                    method @Nullable public Double convert4(@NonNull Float);
-                  }
-                }
-                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import androidx.annotation.Nullable;
+                    public class MyTest {
+                        public Double convert0(Float f) { return null; }
+                        @Nullable public Double convert1(@NonNull Float f) { return null; }
+                        @Nullable public Double convert2(@NonNull Float f) { return null; }
+                        @Nullable public Double convert3(@NonNull Float f) { return null; }
+                        @Nullable public Double convert4(@NonNull Float f) { return null; }
+                    }
+                    """
+                ),
+                androidxNonNullSource,
+                androidxNullableSource
+            ),
             api = """
                 package test.pkg {
                   public class MyTest {
@@ -47,54 +52,80 @@
                     method public Double? convert4(Float);
                   }
                 }
-                """
+                """,
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
         )
     }
 
     @Test
     fun `Method which is now marked null should be marked as recently migrated null`() {
         check(
-            migrateNulls = true,
             outputKotlinStyleNulls = false,
             compatibilityMode = false,
-            signatureSource = """
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import androidx.annotation.Nullable;
+                    import androidx.annotation.NonNull;
+                    public abstract class MyTest {
+                        private MyTest() { }
+                        @Nullable public Double convert1(Float f) { return null; }
+                    }
+                    """
+                ),
+                androidxNonNullSource,
+                androidxNullableSource
+            ),
+            migrateNullsApi = """
+                package test.pkg {
+                  public abstract class MyTest {
+                    method public Double convert1(Float);
+                  }
+                }
+                """,
+            api = """
                 package test.pkg {
                   public abstract class MyTest {
                     method @Nullable public Double convert1(Float);
                   }
                 }
                 """,
-            previousApi = """
-                package test.pkg {
-                  public abstract class MyTest {
-                    method public Double convert1(Float);
-                  }
-                }
-                """,
-            api = """
-                package test.pkg {
-                  public abstract class MyTest {
-                    method @RecentlyNullable public Double convert1(Float);
-                  }
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class MyTest {
+                MyTest() { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RecentlyNullable
+                public java.lang.Double convert1(java.lang.Float f) { throw new RuntimeException("Stub!"); }
                 }
                 """
+            ),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
         )
     }
 
     @Test
     fun `Parameter which is now marked null should be marked as recently migrated null`() {
         check(
-            migrateNulls = true,
             outputKotlinStyleNulls = false,
             compatibilityMode = false,
-            signatureSource = """
-                package test.pkg {
-                  public abstract class MyTest {
-                    method public Double convert1(@NonNull Float);
-                  }
-                }
-                """,
-            previousApi = """
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import androidx.annotation.Nullable;
+                    public abstract class MyTest {
+                        private MyTest() { }
+                        public Double convert1(@NonNull Float f) { return null; }
+                    }
+                    """
+                ),
+                androidxNonNullSource,
+                androidxNullableSource
+            ),
+            migrateNullsApi = """
                 package test.pkg {
                   public abstract class MyTest {
                     method public Double convert1(Float);
@@ -104,32 +135,48 @@
             api = """
                 package test.pkg {
                   public abstract class MyTest {
-                    method public Double convert1(@RecentlyNonNull Float);
+                    method public Double convert1(@NonNull Float);
                   }
                 }
+                """,
+            stubs = arrayOf(
                 """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class MyTest {
+                MyTest() { throw new RuntimeException("Stub!"); }
+                public java.lang.Double convert1(@androidx.annotation.RecentlyNonNull java.lang.Float f) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            ),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
         )
     }
 
     @Test
     fun `Comprehensive check of migration`() {
         check(
-            migrateNulls = true,
             outputKotlinStyleNulls = false,
             compatibilityMode = false,
-            signatureSource = """
-                package test.pkg {
-                  public class MyTest {
-                    ctor public MyTest();
-                    method public Double convert0(Float);
-                    method @Nullable public Double convert1(@NonNull Float);
-                    method @Nullable public Double convert2(@NonNull Float);
-                    method @Nullable public Double convert3(@NonNull Float);
-                    method @Nullable public Double convert4(@NonNull Float);
-                  }
-                }
-                """,
-            previousApi = """
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import androidx.annotation.Nullable;
+                    import androidx.annotation.NonNull;
+                    public class MyTest {
+                        public Double convert0(Float f) { return null; }
+                        @Nullable public Double convert1(@NonNull Float f) { return null; }
+                        @Nullable public Double convert2(@NonNull Float f) { return null; }
+                        @Nullable public Double convert3(@NonNull Float f) { return null; }
+                        @Nullable public Double convert4(@NonNull Float f) { return null; }
+                    }
+                    """
+                ),
+                androidxNonNullSource,
+                androidxNullableSource
+            ),
+            migrateNullsApi = """
                 package test.pkg {
                   public class MyTest {
                     ctor public MyTest();
@@ -146,27 +193,6 @@
                   public class MyTest {
                     ctor public MyTest();
                     method public Double convert0(Float);
-                    method @RecentlyNullable public Double convert1(@RecentlyNonNull Float);
-                    method @Nullable public Double convert2(@NonNull Float);
-                    method @Nullable public Double convert3(@NonNull Float);
-                    method @Nullable public Double convert4(@NonNull Float);
-                  }
-                }
-                """
-        )
-    }
-
-    @Test
-    fun `Comprehensive check of migration, Kotlin-style output`() {
-        check(
-            migrateNulls = true,
-            outputKotlinStyleNulls = true,
-            compatibilityMode = false,
-            signatureSource = """
-                package test.pkg {
-                  public class MyTest {
-                    ctor public MyTest();
-                    method public Double convert0(Float);
                     method @Nullable public Double convert1(@NonNull Float);
                     method @Nullable public Double convert2(@NonNull Float);
                     method @Nullable public Double convert3(@NonNull Float);
@@ -174,7 +200,52 @@
                   }
                 }
                 """,
-            previousApi = """
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MyTest {
+                public MyTest() { throw new RuntimeException("Stub!"); }
+                public java.lang.Double convert0(java.lang.Float f) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.RecentlyNullable
+                public java.lang.Double convert1(@androidx.annotation.RecentlyNonNull java.lang.Float f) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.Nullable
+                public java.lang.Double convert2(@androidx.annotation.NonNull java.lang.Float f) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.Nullable
+                public java.lang.Double convert3(@androidx.annotation.NonNull java.lang.Float f) { throw new RuntimeException("Stub!"); }
+                @androidx.annotation.Nullable
+                public java.lang.Double convert4(@androidx.annotation.NonNull java.lang.Float f) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            ),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
+        )
+    }
+
+    @Test
+    fun `Comprehensive check of migration, Kotlin-style output`() {
+        check(
+            outputKotlinStyleNulls = true,
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import androidx.annotation.Nullable;
+                    import androidx.annotation.NonNull;
+                    public class MyTest {
+                        public Double convert0(Float f) { return null; }
+                        @Nullable public Double convert1(@NonNull Float f) { return null; }
+                        @Nullable public Double convert2(@NonNull Float f) { return null; }
+                        @Nullable public Double convert3(@NonNull Float f) { return null; }
+                        @Nullable public Double convert4(@NonNull Float f) { return null; }
+                    }
+                    """
+                ),
+                androidxNonNullSource,
+                androidxNullableSource
+            ),
+            migrateNullsApi = """
                 package test.pkg {
                   public class MyTest {
                     ctor public MyTest();
@@ -197,7 +268,8 @@
                     method public Double? convert4(Float);
                   }
                 }
-                """
+                """,
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
         )
     }
 
@@ -236,18 +308,18 @@
                 )
             ),
             api = """
-                    package libcore.util {
-                      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;
-                      }
-                    }
-                    package test.pkg {
-                      public class Test {
-                        ctor public Test();
-                        method @NonNull public Object compute();
-                      }
-                    }
+                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;
+                  }
+                }
+                package test.pkg {
+                  public class Test {
+                    ctor public Test();
+                    method @NonNull public Object compute();
+                  }
+                }
                 """
         )
     }
@@ -255,8 +327,7 @@
     @Test
     fun `Check type use annotations`() {
         check(
-            outputKotlinStyleNulls = false,
-            compatibilityMode = false,
+            format = 2, // compat=false, kotlin-style-nulls=false
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -277,9 +348,10 @@
                 androidxNonNullSource,
                 androidxNullableSource
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             api = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
                 """
+                // Signature format: 2.0
                 package test.pkg {
                   public class Test {
                     ctor public Test();
@@ -290,6 +362,7 @@
                 """
             } else {
                 """
+                // Signature format: 2.0
                 package test.pkg {
                   public class Test {
                     ctor public Test();
@@ -327,7 +400,7 @@
                 androidxNonNullSource,
                 androidxNullableSource
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             api = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
                 """
                 package test.pkg {
@@ -357,7 +430,76 @@
         check(
             outputKotlinStyleNulls = false,
             compatibilityMode = false,
-            migrateNulls = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import androidx.annotation.Nullable;
+                    import androidx.annotation.NonNull;
+                    public class Foo {
+                       public static char @NonNull [] toChars(int codePoint) { return new char[0]; }
+                       public static int codePointAt(char @NonNull [] a, int index) { throw new RuntimeException("Stub!"); }
+                       public <T> T @NonNull [] toArray(T @NonNull [] a);
+                       // New APIs should not be marked *recently* nullable; they're fully nullable
+                       public static @NonNull String newMethod(@Nullable String argument) { return ""; }
+                    }
+                    """
+                ),
+                androidxNonNullSource,
+                androidxNullableSource
+            ),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
+            // TODO: Handle multiple nullness annotations
+            migrateNullsApi =
+            """
+                package test.pkg {
+                  public class Foo {
+                    ctor public Foo();
+                    method public static int codePointAt(char[], int);
+                    method public <T> T[] toArray(T[]);
+                    method public static char[] toChars(int);
+                  }
+                }
+                """,
+            stubs = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+                arrayOf(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Foo {
+                    public Foo() { throw new RuntimeException("Stub!"); }
+                    public static char @androidx.annotation.RecentlyNonNull [] toChars(int codePoint) { throw new RuntimeException("Stub!"); }
+                    public static int codePointAt(char @androidx.annotation.RecentlyNonNull [] a, int index) { throw new RuntimeException("Stub!"); }
+                    public <T> T @androidx.annotation.RecentlyNonNull [] toArray(T @androidx.annotation.RecentlyNonNull [] a) { throw new RuntimeException("Stub!"); }
+                    @androidx.annotation.NonNull
+                    public static java.lang.String newMethod(@androidx.annotation.Nullable java.lang.String argument) { throw new RuntimeException("Stub!"); }
+                    }
+                """
+                )
+            } else {
+                arrayOf(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Foo {
+                    public Foo() { throw new RuntimeException("Stub!"); }
+                    public static char[] toChars(int codePoint) { throw new RuntimeException("Stub!"); }
+                    public static int codePointAt(char[] a, int index) { throw new RuntimeException("Stub!"); }
+                    public <T> T[] toArray(T[] a) { throw new RuntimeException("Stub!"); }
+                    @androidx.annotation.NonNull
+                    public static java.lang.String newMethod(@androidx.annotation.Nullable java.lang.String argument) { throw new RuntimeException("Stub!"); }
+                    }
+                    """
+                )
+            }
+        )
+    }
+
+    @Test
+    fun `Do not migrate type-use annotations when not changed`() {
+        check(
+            outputKotlinStyleNulls = false,
+            compatibilityMode = false,
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -374,9 +516,9 @@
                 androidxNonNullSource,
                 androidxNullableSource
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             // TODO: Handle multiple nullness annotations
-            previousApi =
+            migrateNullsApi =
             """
                 package test.pkg {
                   public class Foo {
@@ -418,40 +560,41 @@
     }
 
     @Test
-    fun `Do not migrate type-use annotations when not changed`() {
+    fun `Regression test for issue 111054266, type use annotations`() {
         check(
             outputKotlinStyleNulls = false,
             compatibilityMode = false,
-            migrateNulls = true,
             sourceFiles = *arrayOf(
                 java(
                     """
                     package test.pkg;
-                    import androidx.annotation.Nullable;
                     import androidx.annotation.NonNull;
+                    import java.lang.reflect.TypeVariable;
+
                     public class Foo {
-                       public static char @NonNull [] toChars(int codePoint) { return new char[0]; }
-                       public static int codePointAt(char @NonNull [] a, int index) { throw new RuntimeException("Stub!"); }
-                       public <T> T @NonNull [] toArray(T @NonNull [] a);
+                        @NonNull public java.lang.reflect.Constructor<?> @NonNull [] getConstructors() {
+                            return null;
+                        }
+
+                        public synchronized @NonNull TypeVariable<@NonNull Class<T>> @NonNull [] getTypeParameters() {
+                            return null;
+                        }
                     }
                     """
                 ),
                 androidxNonNullSource,
                 androidxNullableSource
             ),
-            extraArguments = arrayOf("--hide-package", "androidx.annotation"),
-            // TODO: Handle multiple nullness annotations
-            previousApi =
-            """
+            migrateNullsApi = """
                 package test.pkg {
                   public class Foo {
                     ctor public Foo();
-                    method public static int codePointAt(char[], int);
-                    method public <T> T[] toArray(T[]);
-                    method public static char[] toChars(int);
+                    method public java.lang.reflect.Constructor<?>[] getConstructors();
+                    method public synchronized java.lang.reflect.TypeVariable<@java.lang.Class<T>>[] getTypeParameters();
                   }
                 }
-                """,
+            """,
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
             stubs = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
                 arrayOf(
                     """
@@ -459,9 +602,10 @@
                     @SuppressWarnings({"unchecked", "deprecation", "all"})
                     public class Foo {
                     public Foo() { throw new RuntimeException("Stub!"); }
-                    public static char @androidx.annotation.RecentlyNonNull [] toChars(int codePoint) { throw new RuntimeException("Stub!"); }
-                    public static int codePointAt(char @androidx.annotation.RecentlyNonNull [] a, int index) { throw new RuntimeException("Stub!"); }
-                    public <T> T @androidx.annotation.RecentlyNonNull [] toArray(T @androidx.annotation.RecentlyNonNull [] a) { throw new RuntimeException("Stub!"); }
+                    @androidx.annotation.RecentlyNonNull
+                    public java.lang.reflect.Constructor<?> @androidx.annotation.RecentlyNonNull [] getConstructors() { throw new RuntimeException("Stub!"); }
+                    @androidx.annotation.RecentlyNonNull
+                    public synchronized java.lang.reflect.TypeVariable<java.lang.@androidx.annotation.RecentlyNonNull Class<T>> @androidx.annotation.RecentlyNonNull [] getTypeParameters() { throw new RuntimeException("Stub!"); }
                     }
                     """
                 )
@@ -472,13 +616,121 @@
                     @SuppressWarnings({"unchecked", "deprecation", "all"})
                     public class Foo {
                     public Foo() { throw new RuntimeException("Stub!"); }
-                    public static char[] toChars(int codePoint) { throw new RuntimeException("Stub!"); }
-                    public static int codePointAt(char[] a, int index) { throw new RuntimeException("Stub!"); }
-                    public <T> T[] toArray(T[] a) { throw new RuntimeException("Stub!"); }
+                    @androidx.annotation.RecentlyNonNull
+                    public java.lang.reflect.Constructor<?>[] getConstructors() { throw new RuntimeException("Stub!"); }
+                    @androidx.annotation.RecentlyNonNull
+                    public synchronized java.lang.reflect.TypeVariable<java.lang.Class<T>>[] getTypeParameters() { throw new RuntimeException("Stub!"); }
                     }
                     """
                 )
             }
         )
     }
+
+    @Test
+    fun `Test inherited methods`() {
+        check(
+            warnings = """
+                """,
+            migrateNullsApi = """
+                package test.pkg {
+                  public class Child1 extends test.pkg.Parent {
+                  }
+                  public class Child2 extends test.pkg.Parent {
+                    method public void method0(java.lang.String, int);
+                    method public void method4(java.lang.String, int);
+                  }
+                  public class Parent {
+                    method public void method1(java.lang.String, int);
+                    method public void method2(java.lang.String, int);
+                    method public void method3(java.lang.String, int);
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import androidx.annotation.NonNull;
+
+                    public class Child1 extends Parent {
+                        private Child1() {}
+                        @Override
+                        public void method1(@NonNull String first, int second) {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    import androidx.annotation.NonNull;
+
+                    public class Child2 extends Parent {
+                        private Child2() {}
+                        @Override
+                        public void method0(String first, int second) {
+                        }
+                        @Override
+                        public void method1(String first, int second) {
+                        }
+                        @Override
+                        public void method2(@NonNull String first, int second) {
+                        }
+                        @Override
+                        public void method3(String first, int second) {
+                        }
+                        @Override
+                        public void method4(String first, int second) {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    import androidx.annotation.Nullable;
+                    import androidx.annotation.NonNull;
+
+                    public class Parent {
+                        private Parent() { }
+                        public void method1(String first, int second) {
+                        }
+                        public void method2(@NonNull String first, int second) {
+                        }
+                        public void method3(String first, int second) {
+                        }
+                    }
+                    """
+                ),
+                androidxNonNullSource
+            ),
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Child1 extends test.pkg.Parent {
+                Child1() { throw new RuntimeException("Stub!"); }
+                public void method1(@androidx.annotation.RecentlyNonNull java.lang.String first, int second) { throw new RuntimeException("Stub!"); }
+                }
+                """,
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Child2 extends test.pkg.Parent {
+                Child2() { throw new RuntimeException("Stub!"); }
+                public void method0(java.lang.String first, int second) { throw new RuntimeException("Stub!"); }
+                public void method1(java.lang.String first, int second) { throw new RuntimeException("Stub!"); }
+                public void method2(@androidx.annotation.RecentlyNonNull java.lang.String first, int second) { throw new RuntimeException("Stub!"); }
+                public void method3(java.lang.String first, int second) { throw new RuntimeException("Stub!"); }
+                public void method4(java.lang.String first, int second) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt
index 5497e8a..6d34b6b 100644
--- a/src/test/java/com/android/tools/metalava/OptionsTest.kt
+++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt
@@ -32,181 +32,266 @@
 Usage: $PROGRAM_NAME <flags>
 
 General:
---help                                 This message.
---version                              Show the version of metalava.
---quiet                                Only include vital output
---verbose                              Include extra diagnostic output
---color                                Attempt to colorize the output (defaults to true if
-                                       ${"$"}TERM is xterm)
---no-color                             Do not attempt to colorize the output
+--help                                    This message.
+--version                                 Show the version of metalava.
+--quiet                                   Only include vital output
+--verbose                                 Include extra diagnostic output
+--color                                   Attempt to colorize the output (defaults to true
+                                          if ${"$"}TERM is xterm)
+--no-color                                Do not attempt to colorize the output
+--no-docs                                 Cancel any other documentation flags supplied to
+                                          metalava. This is here to make it easier
+                                          customize build system tasks.
+--update-api                              Cancel any other "action" flags other than
+                                          generating signature files. This is here to make
+                                          it easier customize build system tasks,
+                                          particularly for the "make update-api" task.
 
 API sources:
---source-files <files>                 A comma separated list of source files to be
-                                       parsed. Can also be @ followed by a path to a text
-                                       file containing paths to the full set of files to
-                                       parse.
---source-path <paths>                  One or more directories (separated by `:`)
-                                       containing source files (within a package
-                                       hierarchy)
---classpath <paths>                    One or more directories or jars (separated by `:`)
-                                       containing classes that should be on the classpath
-                                       when parsing the source files
---merge-annotations <file>             An external annotations file (using IntelliJ's
-                                       external annotations database format) to merge and
-                                       overlay the sources. A subset of .jaif files is
-                                       also supported.
---input-api-jar <file>                 A .jar file to read APIs from directly
---manifest <file>                      A manifest file, used to for check permissions to
-                                       cross check APIs
---hide-package <package>               Remove the given packages from the API even if they
-                                       have not been marked with @hide
---show-annotation <annotation class>   Include the given annotation in the API analysis
---show-unannotated                     Include un-annotated public APIs in the signature
-                                       file as well
---java-source <level>                  Sets the source level for Java source files;
-                                       default is 1.8.
+--source-files <files>                    A comma separated list of source files to be
+                                          parsed. Can also be @ followed by a path to a
+                                          text file containing paths to the full set of
+                                          files to parse.
+--source-path <paths>                     One or more directories (separated by `:`)
+                                          containing source files (within a package
+                                          hierarchy)
+--classpath <paths>                       One or more directories or jars (separated by
+                                          `:`) containing classes that should be on the
+                                          classpath when parsing the source files
+--merge-qualifier-annotations <file>      An external annotations file to merge and
+                                          overlay the sources, or a directory of such
+                                          files. Should be used for annotations intended
+                                          for inclusion in the API to be written out, e.g.
+                                          nullability. Formats supported are: IntelliJ's
+                                          external annotations database format, .jar or
+                                          .zip files containing those, Android signature
+                                          files, and Java stub files.
+--merge-inclusion-annotations <file>      An external annotations file to merge and
+                                          overlay the sources, or a directory of such
+                                          files. Should be used for annotations which
+                                          determine inclusion in the API to be written
+                                          out, i.e. show and hide. The only format
+                                          supported is Java stub files.
+--validate-nullability-from-merged-stubs  Triggers validation of nullability annotations
+                                          for any class where
+                                          --merge-qualifier-annotations includes a Java
+                                          stub file.
+--validate-nullability-from-list          Triggers validation of nullability annotations
+                                          for any class listed in the named file (one
+                                          top-level class per line, # prefix for comment
+                                          line).
+--nullability-warnings-txt <file>         Specifies where to write warnings encountered
+                                          during validation of nullability annotations.
+                                          (Does not trigger validation by itself.)
+--nullability-errors-non-fatal            Specifies that errors encountered during
+                                          validation of nullability annotations should not
+                                          be treated as errors. They will be written out
+                                          to the file specified in
+                                          --nullability-warnings-txt instead.
+--input-api-jar <file>                    A .jar file to read APIs from directly
+--manifest <file>                         A manifest file, used to for check permissions
+                                          to cross check APIs
+--hide-package <package>                  Remove the given packages from the API even if
+                                          they have not been marked with @hide
+--show-annotation <annotation class>      Unhide any hidden elements that are also
+                                          annotated with the given annotation
+--show-single-annotation <annotation>     Like --show-annotation, but does not apply to
+                                          members; these must also be explicitly
+                                          annotated
+--hide-annotation <annotation class>      Treat any elements annotated with the given
+                                          annotation as hidden
+--show-unannotated                        Include un-annotated public APIs in the
+                                          signature file as well
+--java-source <level>                     Sets the source level for Java source files;
+                                          default is 1.8.
 
 Documentation:
---public                               Only include elements that are public
---protected                            Only include elements that are public or protected
---package                              Only include elements that are public, protected or
-                                       package protected
---private                              Include all elements except those that are marked
-                                       hidden
---hidden                               Include all elements, including hidden
+--public                                  Only include elements that are public
+--protected                               Only include elements that are public or
+                                          protected
+--package                                 Only include elements that are public, protected
+                                          or package protected
+--private                                 Include all elements except those that are
+                                          marked hidden
+--hidden                                  Include all elements, including hidden
 
 Extracting Signature Files:
---api <file>                           Generate a signature descriptor file
---private-api <file>                   Generate a signature descriptor file listing the
-                                       exact private APIs
---dex-api <file>                       Generate a DEX signature descriptor file listing
-                                       the APIs
---private-dex-api <file>               Generate a DEX signature descriptor file listing
-                                       the exact private APIs
---removed-api <file>                   Generate a signature descriptor file for APIs that
-                                       have been removed
---output-kotlin-nulls[=yes|no]         Controls whether nullness annotations should be
-                                       formatted as in Kotlin (with "?" for nullable
-                                       types, "" for non nullable types, and "!" for
-                                       unknown. The default is yes.
---output-default-values[=yes|no]       Controls whether default values should be included
-                                       in signature files. The default is yes.
---compatible-output=[yes|no]           Controls whether to keep signature files compatible
-                                       with the historical format (with its various
-                                       quirks) or to generate the new format (which will
-                                       also include annotations that are part of the API,
-                                       etc.)
---omit-common-packages[=yes|no]        Skip common package prefixes like java.lang.* and
-                                       kotlin.* in signature files, along with packages
-                                       for well known annotations like @Nullable and
-                                       @NonNull.
---proguard <file>                      Write a ProGuard keep file for the API
---sdk-values <dir>                     Write SDK values files to the given directory
+--api <file>                              Generate a signature descriptor file
+--private-api <file>                      Generate a signature descriptor file listing the
+                                          exact private APIs
+--dex-api <file>                          Generate a DEX signature descriptor file listing
+                                          the APIs
+--private-dex-api <file>                  Generate a DEX signature descriptor file listing
+                                          the exact private APIs
+--dex-api-mapping <file>                  Generate a DEX signature descriptor along with
+                                          file and line numbers
+--removed-api <file>                      Generate a signature descriptor file for APIs
+                                          that have been removed
+--format=<v1,v2,v3,...>                   Sets the output signature file format to be the
+                                          given version.
+--output-kotlin-nulls[=yes|no]            Controls whether nullness annotations should be
+                                          formatted as in Kotlin (with "?" for nullable
+                                          types, "" for non nullable types, and "!" for
+                                          unknown. The default is yes.
+--output-default-values[=yes|no]          Controls whether default values should be
+                                          included in signature files. The default is
+                                          yes.
+--compatible-output=[yes|no]              Controls whether to keep signature files
+                                          compatible with the historical format (with its
+                                          various quirks) or to generate the new format
+                                          (which will also include annotations that are
+                                          part of the API, etc.)
+--omit-common-packages[=yes|no]           Skip common package prefixes like java.lang.*
+                                          and kotlin.* in signature files, along with
+                                          packages for well known annotations like
+                                          @Nullable and @NonNull.
+--include-signature-version[=yes|no]      Whether the signature files should include a
+                                          comment listing the format version of the
+                                          signature file.
+--proguard <file>                         Write a ProGuard keep file for the API
+--sdk-values <dir>                        Write SDK values files to the given directory
 
 Generating Stubs:
---stubs <dir>                          Generate stub source files for the API
---doc-stubs <dir>                      Generate documentation stub source files for the
-                                       API. Documentation stub files are similar to
-                                       regular stub files, but there are some differences.
-                                       For example, in the stub files, we'll use special
-                                       annotations like @RecentlyNonNull instead of
-                                       @NonNull to indicate that an element is recently
-                                       marked as non null, whereas in the documentation
-                                       stubs we'll just list this as @NonNull. Another
-                                       difference is that @doconly elements are included
-                                       in documentation stubs, but not regular stubs,
-                                       etc.
---exclude-annotations                  Exclude annotations such as @Nullable from the stub
-                                       files
---write-stubs-source-list <file>       Write the list of generated stub files into the
-                                       given source list file. If generating documentation
-                                       stubs and you haven't also specified
-                                       --write-doc-stubs-source-list, this list will refer
-                                       to the documentation stubs; otherwise it's the
-                                       non-documentation stubs.
---write-doc-stubs-source-list <file>   Write the list of generated doc stub files into the
-                                       given source list file
---register-artifact <api-file> <id>    Registers the given id for the packages found in
-                                       the given signature file. metalava will inject an
-                                       @artifactId <id> tag into every top level stub
-                                       class in that API.
+--stubs <dir>                             Generate stub source files for the API
+--doc-stubs <dir>                         Generate documentation stub source files for the
+                                          API. Documentation stub files are similar to
+                                          regular stub files, but there are some
+                                          differences. For example, in the stub files,
+                                          we'll use special annotations like
+                                          @RecentlyNonNull instead of @NonNull to indicate
+                                          that an element is recently marked as non null,
+                                          whereas in the documentation stubs we'll just
+                                          list this as @NonNull. Another difference is
+                                          that @doconly elements are included in
+                                          documentation stubs, but not regular stubs,
+                                          etc.
+--exclude-annotations                     Exclude annotations such as @Nullable from the
+                                          stub files
+--exclude-documentation-from-stubs        Exclude element documentation (javadoc and kdoc)
+                                          from the generated stubs. (Copyright notices are
+                                          not affected by this, they are always included.
+                                          Documentation stubs (--doc-stubs) are not
+                                          affected.)
+--write-stubs-source-list <file>          Write the list of generated stub files into the
+                                          given source list file. If generating
+                                          documentation stubs and you haven't also
+                                          specified --write-doc-stubs-source-list, this
+                                          list will refer to the documentation stubs;
+                                          otherwise it's the non-documentation stubs.
+--write-doc-stubs-source-list <file>      Write the list of generated doc stub files into
+                                          the given source list file
+--register-artifact <api-file> <id>       Registers the given id for the packages found in
+                                          the given signature file. metalava will inject
+                                          an @artifactId <id> tag into every top level
+                                          stub class in that API.
 
 Diffs and Checks:
---previous-api <signature file>        A signature file for the previous version of this
-                                       API to apply diffs with
---input-kotlin-nulls[=yes|no]          Whether the signature file being read should be
-                                       interpreted as having encoded its types using
-                                       Kotlin style types: a suffix of "?" for nullable
-                                       types, no suffix for non nullable types, and "!"
-                                       for unknown. The default is no.
---check-compatibility                  Check compatibility with the previous API
---check-kotlin-interop                 Check API intended to be used from both Kotlin and
-                                       Java for interoperability issues
---current-api <signature file>         A signature file for the current version of this
-                                       API to check compatibility with. If not specified,
-                                       --previous-api will be used instead.
---migrate-nullness                     Compare nullness information with the previous API
-                                       and mark newly annotated APIs as under migration.
---warnings-as-errors                   Promote all warnings to errors
---lints-as-errors                      Promote all API lint warnings to errors
---error <id>                           Report issues of the given id as errors
---warning <id>                         Report issues of the given id as warnings
---lint <id>                            Report issues of the given id as having
-                                       lint-severity
---hide <id>                            Hide/skip issues of the given id
+--input-kotlin-nulls[=yes|no]             Whether the signature file being read should be
+                                          interpreted as having encoded its types using
+                                          Kotlin style types: a suffix of "?" for nullable
+                                          types, no suffix for non nullable types, and "!"
+                                          for unknown. The default is no.
+--check-compatibility:type:state <file>   Check compatibility. Type is one of 'api' and
+                                          'removed', which checks either the public api or
+                                          the removed api. State is one of 'current' and
+                                          'released', to check either the currently in
+                                          development API or the last publicly released
+                                          API, respectively. Different compatibility
+                                          checks apply in the two scenarios. For example,
+                                          to check the code base against the current
+                                          public API, use
+                                          --check-compatibility:api:current.
+--check-kotlin-interop                    Check API intended to be used from both Kotlin
+                                          and Java for interoperability issues
+--migrate-nullness <api file>             Compare nullness information with the previous
+                                          stable API and mark newly annotated APIs as
+                                          under migration.
+--warnings-as-errors                      Promote all warnings to errors
+--lints-as-errors                         Promote all API lint warnings to errors
+--error <id>                              Report issues of the given id as errors
+--warning <id>                            Report issues of the given id as warnings
+--lint <id>                               Report issues of the given id as having
+                                          lint-severity
+--hide <id>                               Hide/skip issues of the given id
+
+JDiff:
+--api-xml <file>                          Like --api, but emits the API in the JDiff XML
+                                          format instead
+--convert-to-jdiff <sig> <xml>            Reads in the given signature file, and writes it
+                                          out in the JDiff XML format. Can be specified
+                                          multiple times.
 
 Statistics:
---annotation-coverage-stats            Whether metalava should emit coverage statistics
-                                       for annotations, listing the percentage of the API
-                                       that has been annotated with nullness information.
---annotation-coverage-of <paths>       One or more jars (separated by `:`) containing
-                                       existing apps that we want to measure annotation
-                                       coverage statistics for. The set of API usages in
-                                       those apps are counted up and the most frequently
-                                       used APIs that are missing annotation metadata are
-                                       listed in descending order.
---skip-java-in-coverage-report         In the coverage annotation report, skip java.** and
-                                       kotlin.** to narrow the focus down to the Android
-                                       framework APIs.
---write-class-coverage-to <path>       Specifies a file to write the annotation coverage
-                                       report for classes to.
---write-member-coverage-to <path>      Specifies a file to write the annotation coverage
-                                       report for members to.
+--annotation-coverage-stats               Whether metalava should emit coverage statistics
+                                          for annotations, listing the percentage of the
+                                          API that has been annotated with nullness
+                                          information.
+--annotation-coverage-of <paths>          One or more jars (separated by `:`) containing
+                                          existing apps that we want to measure annotation
+                                          coverage statistics for. The set of API usages
+                                          in those apps are counted up and the most
+                                          frequently used APIs that are missing annotation
+                                          metadata are listed in descending order.
+--skip-java-in-coverage-report            In the coverage annotation report, skip java.**
+                                          and kotlin.** to narrow the focus down to the
+                                          Android framework APIs.
+--write-class-coverage-to <path>          Specifies a file to write the annotation
+                                          coverage report for classes to.
+--write-member-coverage-to <path>         Specifies a file to write the annotation
+                                          coverage report for members to.
 
 Extracting Annotations:
---extract-annotations <zipfile>        Extracts source annotations from the source files
-                                       and writes them into the given zip file
---include-annotation-classes <dir>     Copies the given stub annotation source files into
-                                       the generated stub sources; <dir> is typically
-                                       metalava/stub-annotations/src/main/java/.
---rewrite-annotations <dir/jar>        For a bytecode folder or output jar, rewrites the
-                                       androidx annotations to be package private
---include-source-retention             If true, include source-retention annotations in
-                                       the stub files. Does not apply to signature files.
-                                       Source retention annotations are extracted into the
-                                       external annotations files instead.
+--extract-annotations <zipfile>           Extracts source annotations from the source
+                                          files and writes them into the given zip file
+--include-annotation-classes <dir>        Copies the given stub annotation source files
+                                          into the generated stub sources; <dir> is
+                                          typically
+                                          metalava/stub-annotations/src/main/java/.
+--rewrite-annotations <dir/jar>           For a bytecode folder or output jar, rewrites
+                                          the androidx annotations to be package private
+--copy-annotations <source> <dest>        For a source folder full of annotation sources,
+                                          generates corresponding package private versions
+                                          of the same annotations.
+--include-source-retention                If true, include source-retention annotations in
+                                          the stub files. Does not apply to signature
+                                          files. Source retention annotations are
+                                          extracted into the external annotations files
+                                          instead.
 
 Injecting API Levels:
---apply-api-levels <api-versions.xml>  Reads an XML file containing API level descriptions
-                                       and merges the information into the documentation
+--apply-api-levels <api-versions.xml>     Reads an XML file containing API level
+                                          descriptions and merges the information into the
+                                          documentation
 
 Extracting API Levels:
---generate-api-levels <xmlfile>        Reads android.jar SDK files and generates an XML
-                                       file recording the API level for each class, method
-                                       and field
---android-jar-pattern <pattern>        Patterns to use to locate Android JAR files. The
-                                       default is
-                                       ${"$"}ANDROID_HOME/platforms/android-%/android.jar.
---current-version                      Sets the current API level of the current source
-                                       code
---current-codename                     Sets the code name for the current source code
---current-jar                          Points to the current API jar, if any
+--generate-api-levels <xmlfile>           Reads android.jar SDK files and generates an XML
+                                          file recording the API level for each class,
+                                          method and field
+--android-jar-pattern <pattern>           Patterns to use to locate Android JAR files. The
+                                          default is
+                                          ${"$"}ANDROID_HOME/platforms/android-%/android.jar.
+--current-version                         Sets the current API level of the current source
+                                          code
+--current-codename                        Sets the code name for the current source code
+--current-jar                             Points to the current API jar, if any
+
+Environment Variables:
+METALAVA_DUMP_ARGV                        Set to true to have metalava emit all the
+                                          arguments it was invoked with. Helpful when
+                                          debugging or reproducing under a debugger what
+                                          the build system is doing.
+METALAVA_PREPEND_ARGS                     One or more arguments (concatenated by space) to
+                                          insert into the command line, before the
+                                          documentation flags.
+METALAVA_APPEND_ARGS                      One or more arguments (concatenated by space) to
+                                          append to the end of the command line, after the
+                                          generate documentation flags.
 
 """.trimIndent()
 
     @Test
     fun `Test invalid arguments`() {
-        val args = listOf("--no-color", "--blah-blah-blah")
+        val args = listOf(ARG_NO_COLOR, "--blah-blah-blah")
 
         val stdout = StringWriter()
         val stderr = StringWriter()
@@ -229,7 +314,7 @@
 
     @Test
     fun `Test help`() {
-        val args = listOf("--no-color", "--help")
+        val args = listOf(ARG_NO_COLOR, "--help")
 
         val stdout = StringWriter()
         val stderr = StringWriter()
@@ -251,4 +336,4 @@
 """.trimIndent(), stdout.toString()
         )
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt
index 1f96736..b8c2a41 100644
--- a/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt
+++ b/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt
@@ -20,6 +20,7 @@
 import com.android.tools.lint.checks.infrastructure.TestFiles.jar
 import com.android.tools.lint.checks.infrastructure.TestFiles.xml
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import java.io.File
@@ -29,24 +30,30 @@
 class RewriteAnnotationsTest : DriverTest() {
     @Test
     fun `Test copying private annotations from one of the stubs`() {
-        val source = File("stub-annotations/src/main/java/androidx/annotation".replace('/', File.separatorChar))
+        val source = File("stub-annotations".replace('/', File.separatorChar))
         assertTrue(source.path, source.isDirectory)
         val target = temporaryFolder.newFolder()
         runDriver(
-            "--no-color",
-            "--no-banner",
+            ARG_NO_COLOR,
+            ARG_NO_BANNER,
 
-            "--copy-annotations",
+            ARG_COPY_ANNOTATIONS,
             source.path,
             target.path
         )
 
-        val converted = File(target, "CallSuper.java")
-        assertTrue("${converted.path} doesn't exist", converted.isFile)
+        // Source retention: Shouldn't exist
+        val nonNull = File(target, "androidx/annotation/NonNull.java")
+        assertFalse("${nonNull.path} exists", nonNull.isFile)
+
+        // Class retention: Should be converted
+
+        val recentlyNull = File(target, "androidx/annotation/RecentlyNullable.java")
+        assertTrue("${recentlyNull.path} doesn't exist", recentlyNull.isFile)
         assertEquals(
             """
             /*
-             * Copyright (C) 2015 The Android Open Source Project
+             * 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.
@@ -62,7 +69,10 @@
              */
             package androidx.annotation;
 
+            import static java.lang.annotation.ElementType.FIELD;
             import static java.lang.annotation.ElementType.METHOD;
+            import static java.lang.annotation.ElementType.PARAMETER;
+            import static java.lang.annotation.ElementType.TYPE_USE;
             import static java.lang.annotation.RetentionPolicy.CLASS;
 
             import java.lang.annotation.Retention;
@@ -70,9 +80,9 @@
 
             /** Stub only annotation. Do not use directly. */
             @Retention(CLASS)
-            @Target({METHOD})
-            @interface CallSuper {}
-        """.trimIndent().trim(), converted.readText(Charsets.UTF_8).trim().replace("\r\n", "\n")
+            @Target({METHOD, PARAMETER, FIELD})
+            @interface RecentlyNullable {}
+        """.trimIndent().trim(), recentlyNull.readText(Charsets.UTF_8).trim().replace("\r\n", "\n")
         )
     }
 
@@ -92,10 +102,10 @@
         bytecode.createFile(compiledStubs)
 
         runDriver(
-            "--no-color",
-            "--no-banner",
+            ARG_NO_COLOR,
+            ARG_NO_BANNER,
 
-            "--rewrite-annotations",
+            ARG_REWRITE_ANNOTATIONS,
             compiledStubs.path
         )
 
@@ -129,10 +139,10 @@
         val jarFile = jarDesc.createFile(temporaryFolder.root)
 
         runDriver(
-            "--no-color",
-            "--no-banner",
+            ARG_NO_COLOR,
+            ARG_NO_BANNER,
 
-            "--rewrite-annotations",
+            ARG_REWRITE_ANNOTATIONS,
             jarFile.path
         )
 
diff --git a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
index e66c35d..4ef3a20 100644
--- a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
+++ b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
@@ -10,6 +10,7 @@
         check(
             includeSystemApiAnnotations = true,
             checkDoclava1 = true,
+            warnings = "src/test/pkg/Foo.java:17: error: @SystemApi APIs must also be marked @hide: method test.pkg.Foo.method4() [UnhiddenSystemApi:155]",
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -46,8 +47,9 @@
             ),
 
             extraArguments = arrayOf(
-                "--hide-package", "android.annotation",
-                "--hide-package", "android.support.annotation"
+                ARG_ERROR, "UnhiddenSystemApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
             ),
 
             api = """
@@ -67,6 +69,7 @@
             includeSystemApiAnnotations = true,
             showUnannotated = true,
             checkDoclava1 = true,
+            warnings = "src/test/pkg/Foo.java:17: error: @SystemApi APIs must also be marked @hide: method test.pkg.Foo.method4() [UnhiddenSystemApi:155]",
             sourceFiles = *arrayOf(
                 java(
                     """
@@ -103,8 +106,9 @@
             ),
 
             extraArguments = arrayOf(
-                "--hide-package", "android.annotation",
-                "--hide-package", "android.support.annotation"
+                ARG_ERROR, "UnhiddenSystemApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
             ),
 
             api = """
@@ -142,6 +146,20 @@
                      */
                     @TestApi
                     public class Bar {
+                        public void test() {
+                        }
+                    }
+                    """
+                ),
+
+                // This isn't necessary for this test, but doclava will ignore @hide classes marked
+                // with an annotation unless there is a public reference it to it from elsewhere.
+                // Include this here such that the checkDoclava1=true step produces any output.
+                java(
+                    """
+                    package test.pkg;
+                    public class Usage {
+                        public Bar bar;
                     }
                     """
                 ),
@@ -149,18 +167,92 @@
             ),
 
             extraArguments = arrayOf(
-                "--show-annotation", "android.annotation.TestApi",
-                "--hide-package", "android.annotation",
-                "--hide-package", "android.support.annotation"
+                ARG_SHOW_ANNOTATION, "android.annotation.TestApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
             ),
 
             api = """
                 package test.pkg {
                   public class Bar {
                     ctor public Bar();
+                    method public void test();
                   }
                 }
                 """
         )
     }
+
+    @Test
+    fun `Include interface-inherited fields in stubs`() {
+        // When applying an annotations filter (-showAnnotation X), doclava
+        // deliberately made the signature files *only* include annotated
+        // elements, e.g. they're just showing the "diffs" between the base API
+        // and the additional API made visible with annotations. However,
+        // in the *stubs*, we have to include everything.
+        check(
+            checkDoclava1 = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg2;
+
+                    import test.pkg1.MyParent;
+                    public class MyChild extends MyParent {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg1;
+                    import java.io.Closeable;
+                    @SuppressWarnings("WeakerAccess")
+                    public class MyParent implements MyConstants, Closeable {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg1;
+                    interface MyConstants {
+                        long CONSTANT1 = 12345;
+                        long CONSTANT2 = 67890;
+                        long CONSTANT3 = 42;
+                    }
+                    """
+                )
+            ),
+            stubs = arrayOf(
+                """
+                package test.pkg2;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MyChild extends test.pkg1.MyParent {
+                public MyChild() { throw new RuntimeException("Stub!"); }
+                public static final long CONSTANT1 = 12345L; // 0x3039L
+                public static final long CONSTANT2 = 67890L; // 0x10932L
+                public static final long CONSTANT3 = 42L; // 0x2aL
+                }
+            """.trimIndent(),
+                """
+                package test.pkg1;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MyParent implements java.io.Closeable {
+                public MyParent() { throw new RuntimeException("Stub!"); }
+                public static final long CONSTANT1 = 12345L; // 0x3039L
+                public static final long CONSTANT2 = 67890L; // 0x10932L
+                public static final long CONSTANT3 = 42L; // 0x2aL
+                }
+                """.trimIndent()
+            ),
+            // Empty API: showUnannotated=false
+            api = """
+                """,
+            includeSystemApiAnnotations = true,
+            extraArguments = arrayOf(
+                ARG_SHOW_ANNOTATION, "android.annotation.TestApi",
+                ARG_HIDE_PACKAGE, "android.annotation",
+                ARG_HIDE_PACKAGE, "android.support.annotation"
+            )
+        )
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/StubsTest.kt b/src/test/java/com/android/tools/metalava/StubsTest.kt
index 1edcbbf..3837b71 100644
--- a/src/test/java/com/android/tools/metalava/StubsTest.kt
+++ b/src/test/java/com/android/tools/metalava/StubsTest.kt
@@ -22,6 +22,7 @@
 import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
 import org.intellij.lang.annotations.Language
 import org.junit.Test
+import java.io.FileNotFoundException
 
 @SuppressWarnings("ALL")
 class StubsTest : DriverTest() {
@@ -63,6 +64,10 @@
             sourceFiles = *arrayOf(
                 java(
                     """
+                    /*
+                     * This is the copyright header.
+                     */
+
                     package test.pkg;
                     /** This is the documentation for the class */
                     @SuppressWarnings("ALL")
@@ -90,6 +95,9 @@
                 )
             ),
             source = """
+                /*
+                 * This is the copyright header.
+                 */
                 package test.pkg;
                 /** This is the documentation for the class */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
@@ -269,7 +277,7 @@
                     package test.pkg;
                     @SuppressWarnings("ALL")
                     public enum Foo {
-                        A, B;
+                        A, /** @deprecated */ @Deprecated B;
                     }
                     """
                 )
@@ -278,7 +286,10 @@
                 package test.pkg;
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
                 public enum Foo {
-                A, B;
+                A,
+                /** @deprecated */
+                @Deprecated
+                B;
                 }
                 """
         )
@@ -309,7 +320,9 @@
             source = """
                 package test.pkg;
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.LOCAL_VARIABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Foo {
+                @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.LOCAL_VARIABLE})
+                @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)
+                public @interface Foo {
                 public java.lang.String value();
                 }
                 """
@@ -445,18 +458,23 @@
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
                 public abstract class Foo {
                 public Foo() { throw new RuntimeException("Stub!"); }
-                @Deprecated public static final synchronized strictfp void method1() { throw new RuntimeException("Stub!"); }
-                @Deprecated public static final synchronized native void method2();
+                @Deprecated
+                public static final synchronized void method1() { throw new RuntimeException("Stub!"); }
+                @Deprecated
+                public static final synchronized native void method2();
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @Deprecated protected static final class Inner1 {
+                @Deprecated
+                protected static final class Inner1 {
                 protected Inner1() { throw new RuntimeException("Stub!"); }
                 }
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @Deprecated protected abstract static class Inner2 {
+                @Deprecated
+                protected abstract static class Inner2 {
                 protected Inner2() { throw new RuntimeException("Stub!"); }
                 }
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @Deprecated protected static interface Inner3 {
+                @Deprecated
+                protected static interface Inner3 {
                 public default void method3() { throw new RuntimeException("Stub!"); }
                 public static void method4() { throw new RuntimeException("Stub!"); }
                 }
@@ -481,10 +499,13 @@
                     package test.pkg;
 
                     public enum FooBar {
+                        /** My 1st documentation */
                         ABC {
                             @Override
                             protected void foo() { }
-                        }, DEF {
+                        },
+                        /** My 2nd documentation */
+                        DEF {
                             @Override
                             protected void foo() { }
                         };
@@ -500,11 +521,57 @@
                 package test.pkg;
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
                 public enum FooBar {
-                ABC, DEF;
+                /** My 1st documentation */
+                ABC,
+                /** My 2nd documentation */
+                DEF;
                 protected void foo() { throw new RuntimeException("Stub!"); }
                 public static int field1 = 1; // 0x1
                 public int field2 = 2; // 0x2
                 }
+                """
+        )
+    }
+
+    @Test
+    fun `Skip hidden enum constants in stubs`() {
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public enum Alignment {
+                        ALIGN_NORMAL,
+                        ALIGN_OPPOSITE,
+                        ALIGN_CENTER,
+                        /** @hide */
+                        ALIGN_LEFT,
+                        /** @hide */
+                        ALIGN_RIGHT
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public final class Alignment extends java.lang.Enum {
+                    method public static test.pkg.Alignment valueOf(java.lang.String);
+                    method public static final test.pkg.Alignment[] values();
+                    enum_constant public static final test.pkg.Alignment ALIGN_CENTER;
+                    enum_constant public static final test.pkg.Alignment ALIGN_NORMAL;
+                    enum_constant public static final test.pkg.Alignment ALIGN_OPPOSITE;
+                  }
+                }
+            """,
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public enum Alignment {
+                ALIGN_NORMAL,
+                ALIGN_OPPOSITE,
+                ALIGN_CENTER;
+                }
             """
         )
     }
@@ -654,7 +721,9 @@
                 package test.pkg;
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
                 public enum ChronUnit implements test.pkg.TempUnit {
-                C, B, A;
+                C,
+                B,
+                A;
                 public java.lang.String valueOf(int x) { throw new RuntimeException("Stub!"); }
                 public java.lang.String toString() { throw new RuntimeException("Stub!"); }
                 }
@@ -1134,9 +1203,10 @@
                     public abstract class HiddenPermission {
                     public HiddenPermission() { throw new RuntimeException("Stub!"); }
                     /**
-                     * Requires {@link android.Manifest.permission#INTERACT_ACROSS_USERS} and {@link android.Manifest.permission#BROADCAST_STICKY}
+                     * Requires android.Manifest.permission.INTERACT_ACROSS_USERS and {@link android.Manifest.permission#BROADCAST_STICKY}
                      */
-                    @androidx.annotation.RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void removeStickyBroadcast(@androidx.annotation.RequiresPermission java.lang.Object intent);
+                    @androidx.annotation.RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY})
+                    public abstract void removeStickyBroadcast(@androidx.annotation.RequiresPermission java.lang.Object intent);
                     }
                 """
         )
@@ -1270,14 +1340,17 @@
                     @SuppressWarnings({"unchecked", "deprecation", "all"})
                     public final class Kotlin extends test.pkg.Parent {
                     public Kotlin(@androidx.annotation.NonNull java.lang.String property1, int arg2) { throw new RuntimeException("Stub!"); }
-                    @androidx.annotation.NonNull public java.lang.String method() { throw new RuntimeException("Stub!"); }
+                    @androidx.annotation.NonNull
+                    public java.lang.String method() { throw new RuntimeException("Stub!"); }
                     /** My method doc */
                     public void otherMethod(boolean ok, int times) { throw new RuntimeException("Stub!"); }
                     /** property doc */
-                    @androidx.annotation.Nullable public java.lang.String getProperty2() { throw new RuntimeException("Stub!"); }
+                    @androidx.annotation.Nullable
+                    public java.lang.String getProperty2() { throw new RuntimeException("Stub!"); }
                     /** property doc */
                     public void setProperty2(@androidx.annotation.Nullable java.lang.String p) { throw new RuntimeException("Stub!"); }
-                    @androidx.annotation.NonNull public java.lang.String getProperty1() { throw new RuntimeException("Stub!"); }
+                    @androidx.annotation.NonNull
+                    public java.lang.String getProperty1() { throw new RuntimeException("Stub!"); }
                     public int someField2;
                     }
                 """,
@@ -2923,106 +2996,6 @@
     }
 
     @Test
-    fun `Rewrite relative documentation links`() {
-        // When generating casts in super constructor calls, use raw types
-        checkStubs(
-            checkDoclava1 = false,
-            sourceFiles =
-            *arrayOf(
-                java(
-                    """
-                    package test.pkg1;
-                    import java.io.IOException;
-                    import test.pkg2.OtherClass;
-
-                    /**
-                     *  Blah blah {@link OtherClass} blah blah.
-                     *  Referencing <b>field</b> {@link OtherClass#foo},
-                     *  and referencing method {@link OtherClass#bar(int,
-                     *   boolean)}.
-                     *  And relative method reference {@link #baz()}.
-                     *  And relative field reference {@link #importance}.
-                     *  Here's an already fully qualified reference: {@link test.pkg2.OtherClass}.
-                     *  And here's one in the same package: {@link LocalClass}.
-                     *
-                     *  @deprecated For some reason
-                     *  @see OtherClass
-                     *  @see OtherClass#bar(int, boolean)
-                     */
-                    @SuppressWarnings("all")
-                    public class SomeClass {
-                       /**
-                       * My method.
-                       * @param focus The focus to find. One of {@link OtherClass#FOCUS_INPUT} or
-                       *         {@link OtherClass#FOCUS_ACCESSIBILITY}.
-                       * @throws IOException when blah blah blah
-                       * @throws {@link RuntimeException} when blah blah blah
-                       */
-                       public void baz(int focus) throws IOException;
-                       public boolean importance;
-                    }
-                    """
-                ),
-                java(
-                    """
-                    package test.pkg2;
-
-                    @SuppressWarnings("all")
-                    public class OtherClass {
-                        public static final int FOCUS_INPUT = 1;
-                        public static final int FOCUS_ACCESSIBILITY = 2;
-                        public int foo;
-                        public void bar(int baz, boolean bar);
-                    }
-                    """
-                ),
-                java(
-                    """
-                    package test.pkg1;
-
-                    @SuppressWarnings("all")
-                    public class LocalClass {
-                    }
-                    """
-                )
-            ),
-            warnings = "",
-            source = """
-                    package test.pkg1;
-                    import test.pkg2.OtherClass;
-                    import java.io.IOException;
-                    /**
-                     *  Blah blah {@link OtherClass} blah blah.
-                     *  Referencing <b>field</b> {@link OtherClass#foo},
-                     *  and referencing method {@link OtherClass#bar(int,
-                     *   boolean)}.
-                     *  And relative method reference {@link #baz()}.
-                     *  And relative field reference {@link #importance}.
-                     *  Here's an already fully qualified reference: {@link test.pkg2.OtherClass}.
-                     *  And here's one in the same package: {@link LocalClass}.
-                     *
-                     *  @deprecated For some reason
-                     *  @see OtherClass
-                     *  @see OtherClass#bar(int, boolean)
-                     */
-                    @SuppressWarnings({"unchecked", "deprecation", "all"})
-                    @Deprecated public class SomeClass {
-                    public SomeClass() { throw new RuntimeException("Stub!"); }
-                    /**
-                     * My method.
-                     * @param focus The focus to find. One of {@link OtherClass#FOCUS_INPUT} or
-                     *         {@link OtherClass#FOCUS_ACCESSIBILITY}.
-                     * @throws IOException when blah blah blah
-                     * @throws {@link RuntimeException} when blah blah blah
-                     */
-                    public void baz(int focus) throws java.io.IOException { throw new RuntimeException("Stub!"); }
-                    public boolean importance;
-                    }
-                    """
-        )
-    }
-
-    @Test
     fun `Annotation default values`() {
         checkStubs(
             compatibilityMode = false,
@@ -3088,8 +3061,8 @@
             warnings = "",
             api = """
                 package test.pkg {
-                  public @interface ExportedProperty {
-                    method public abstract String! category() default "";
+                  @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface ExportedProperty {
+                    method public abstract String category() default "";
                     method public abstract float floating() default 1.0f;
                     method public abstract boolean formatToHexString() default false;
                     method public abstract double from() default java.lang.Double.NEGATIVE_INFINITY;
@@ -3097,23 +3070,23 @@
                     method public abstract boolean hasAdjacentMapping() default false;
                     method public abstract int integer() default 1;
                     method public abstract double large_floating() default 1.0;
-                    method public abstract long large_integer() default 1;
+                    method public abstract long large_integer() default 1L;
                     method public abstract char letter() default 'a';
-                    method public abstract char[]! letters1() default {};
-                    method public abstract char[]! letters2() default {'a', 'b', 'c'};
+                    method public abstract char[] letters1() default {};
+                    method public abstract char[] letters2() default {'a', 'b', 'c'};
                     method public abstract int math() default 7;
                     method public abstract short medium() default 1;
-                    method public abstract Class<? extends java.lang.Number>! myCls() default java.lang.Integer.class;
-                    method public abstract String! prefix() default "";
+                    method public abstract Class<? extends java.lang.Number> myCls() default java.lang.Integer.class;
+                    method public abstract String prefix() default "";
                     method public abstract boolean resolveId() default false;
                     method public abstract byte small() default 1;
-                    method public abstract int unit() default test.pkg.ExportedProperty.PX;
-                    method public abstract test.pkg.ExportedProperty.InnerAnnotation! value() default @test.pkg.ExportedProperty.InnerAnnotation;
+                    method @test.pkg.ExportedProperty.InnerAnnotation public abstract int unit() default test.pkg.ExportedProperty.PX;
+                    method public abstract test.pkg.ExportedProperty.InnerAnnotation value() default @test.pkg.ExportedProperty.InnerAnnotation;
                     field public static final int DP = 0; // 0x0
                     field public static final int PX = 1; // 0x1
                     field public static final int SP = 2; // 0x2
                   }
-                  public static @interface ExportedProperty.InnerAnnotation {
+                  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ExportedProperty.InnerAnnotation {
                   }
                 }
             """,
@@ -3125,7 +3098,9 @@
                  * by this annotation.
                  */
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface ExportedProperty {
+                @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD})
+                @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+                public @interface ExportedProperty {
                 /**
                  * When resolveId is true, and if the annotated field/method return value
                  * is an int, the value is converted to an Android's resource name.
@@ -3146,7 +3121,7 @@
                 public test.pkg.ExportedProperty.InnerAnnotation value() default @test.pkg.ExportedProperty.InnerAnnotation;
                 public char letter() default 'a';
                 public int integer() default 1;
-                public long large_integer() default 1;
+                public long large_integer() default 1L;
                 public float floating() default 1.0f;
                 public double large_floating() default 1.0;
                 public byte small() default 1;
@@ -3157,7 +3132,8 @@
                 public static final int PX = 1; // 0x1
                 public static final int SP = 2; // 0x2
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface InnerAnnotation {
+                @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+                public static @interface InnerAnnotation {
                 }
                 }
                 """
@@ -3189,7 +3165,9 @@
             source = """
                 package java.lang;
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface MyAnnotation {
+                @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD)
+                @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+                public @interface MyAnnotation {
                 }
                 """
         )
@@ -3217,7 +3195,8 @@
             source = """
                 package java.lang;
                 @SuppressWarnings({"unchecked", "deprecation", "all"})
-                @java.lang.FunctionalInterface public interface MyInterface {
+                @java.lang.FunctionalInterface
+                public interface MyInterface {
                 public void run();
                 }
                 """
@@ -3259,7 +3238,399 @@
                 @androidx.annotation.Nullable
                 package test.pkg;
                 """,
-            extraArguments = arrayOf("--hide-package", "androidx.annotation")
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
+        )
+    }
+
+    @Test
+    fun `Test package-info documentation`() {
+        check(
+            checkDoclava1 = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                      /** My package docs */
+                      package test.pkg;
+                      """
+                ).indented(),
+                java("""package test.pkg; public abstract class Class1 { }""")
+            ),
+
+            api = """
+                package test.pkg {
+                  public abstract class Class1 {
+                    ctor public Class1();
+                  }
+                }
+                """,
+            stubs = arrayOf(
+                """
+                /** My package docs */
+                package test.pkg;
+                """,
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class Class1 {
+                public Class1() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Test package-info annotations`() {
+        check(
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                      @RestrictTo(RestrictTo.Scope.SUBCLASSES)
+                      package test.pkg;1
+
+                      import androidx.annotation.RestrictTo;
+                      """
+                ).indented(),
+                java("""package test.pkg; public abstract class Class1 { }"""),
+                restrictToSource
+            ),
+
+            api = """
+                package @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) test.pkg {
+                  public abstract class Class1 {
+                    ctor public Class1();
+                  }
+                }
+                """,
+            stubs = arrayOf(
+                """
+                @androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES)
+                package test.pkg;
+                """,
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class Class1 {
+                public Class1() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            ),
+            extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
+        )
+    }
+
+    @Test
+    fun `Ensure we emit both deprecated javadoc and annotation with exclude-annotations`() {
+        check(
+            extraArguments = arrayOf(ARG_EXCLUDE_ANNOTATIONS),
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class Foo {
+                        /**
+                         * @deprecated Use checkPermission instead.
+                         */
+                        @Deprecated
+                        protected boolean inClass(String name) {
+                            return false;
+                        }
+                    }
+                    """
+                )
+            ),
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                /**
+                 * @deprecated Use checkPermission instead.
+                 */
+                @Deprecated
+                protected boolean inClass(java.lang.String name) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Ensure we emit runtime and deprecated annotations in stubs with exclude-annotations`() {
+        check(
+            extraArguments = arrayOf(ARG_EXCLUDE_ANNOTATIONS),
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    /** @deprecated */
+                    @MySourceRetentionAnnotation
+                    @MyClassRetentionAnnotation
+                    @MyRuntimeRetentionAnnotation
+                    @Deprecated
+                    public class Foo {
+                        private Foo() {}
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    import java.lang.annotation.Retention;
+                    import static java.lang.annotation.RetentionPolicy.SOURCE;
+                    @Retention(SOURCE)
+                    public @interface MySourceRetentionAnnotation {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    import java.lang.annotation.Retention;
+                    import static java.lang.annotation.RetentionPolicy.CLASS;
+                    @Retention(CLASS)
+                    public @interface MyClassRetentionAnnotation {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    import java.lang.annotation.Retention;
+                    import static java.lang.annotation.RetentionPolicy.RUNTIME;
+                    @Retention(RUNTIME)
+                    public @interface MyRuntimeRetentionAnnotation {
+                    }
+                    """
+                )
+            ),
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                /** @deprecated */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @Deprecated
+                @test.pkg.MyRuntimeRetentionAnnotation
+                public class Foo {
+                Foo() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Ensure we include class and runtime and not source annotations in stubs with include-annotations`() {
+        check(
+            extraArguments = arrayOf("--include-annotations"),
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    /** @deprecated */
+                    @MySourceRetentionAnnotation
+                    @MyClassRetentionAnnotation
+                    @MyRuntimeRetentionAnnotation
+                    @Deprecated
+                    public class Foo {
+                        private Foo() {}
+                        protected int foo;
+                        public void bar();
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    import java.lang.annotation.Retention;
+                    import static java.lang.annotation.RetentionPolicy.SOURCE;
+                    @Retention(SOURCE)
+                    public @interface MySourceRetentionAnnotation {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    import java.lang.annotation.Retention;
+                    import static java.lang.annotation.RetentionPolicy.CLASS;
+                    @Retention(CLASS)
+                    public @interface MyClassRetentionAnnotation {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    import java.lang.annotation.Retention;
+                    import static java.lang.annotation.RetentionPolicy.RUNTIME;
+                    @Retention(RUNTIME)
+                    public @interface MyRuntimeRetentionAnnotation {
+                    }
+                    """
+                )
+            ),
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                /** @deprecated */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @Deprecated
+                @test.pkg.MyClassRetentionAnnotation
+                @test.pkg.MyRuntimeRetentionAnnotation
+                public class Foo {
+                Foo() { throw new RuntimeException("Stub!"); }
+                @Deprecated
+                public void bar() { throw new RuntimeException("Stub!"); }
+                @Deprecated protected int foo;
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Generate stubs with --exclude-documentation-from-stubs`() {
+        checkStubs(
+            extraArguments = arrayOf(ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS),
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    /*
+                     * This is the copyright header.
+                     */
+
+                    package test.pkg;
+
+                    /** This is the documentation for the class */
+                    public class Foo {
+
+                        /** My field doc */
+                        protected static final String field = "a\nb\n\"test\"";
+
+                        /**
+                         * Method documentation.
+                         */
+                        protected static void onCreate(String parameter1) {
+                            // This is not in the stub
+                            System.out.println(parameter1);
+                        }
+                    }
+                    """
+                )
+            ),
+            // Excludes javadoc because of ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS:
+            source = """
+                /*
+                 * This is the copyright header.
+                 */
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                protected static void onCreate(java.lang.String parameter1) { throw new RuntimeException("Stub!"); }
+                protected static final java.lang.String field = "a\nb\n\"test\"";
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate documentation stubs with --exclude-documentation-from-stubs`() {
+        checkStubs(
+            extraArguments = arrayOf(ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS),
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    /*
+                     * This is the copyright header.
+                     */
+
+                    package test.pkg;
+
+                    /** This is the documentation for the class */
+                    public class Foo {
+
+                        /** My field doc */
+                        protected static final String field = "a\nb\n\"test\"";
+
+                        /**
+                         * Method documentation.
+                         */
+                        protected static void onCreate(String parameter1) {
+                            // This is not in the stub
+                            System.out.println(parameter1);
+                        }
+                    }
+                    """
+                )
+            ),
+            docStubs = true,
+            // Includes javadoc despite ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS, because of docStubs:
+            source = """
+                /*
+                 * This is the copyright header.
+                 */
+                package test.pkg;
+                /** This is the documentation for the class */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Method documentation.
+                 */
+                protected static void onCreate(java.lang.String parameter1) { throw new RuntimeException("Stub!"); }
+                /** My field doc */
+                protected static final java.lang.String field = "a\nb\n\"test\"";
+                }
+                """
+        )
+    }
+
+    @Test(expected = FileNotFoundException::class)
+    fun `Test update-api should not generate stubs`() {
+        check(
+            extraArguments = arrayOf(
+                ARG_UPDATE_API,
+                ARG_EXCLUDE_ANNOTATIONS
+            ),
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class Foo {
+                        /**
+                         * @deprecated Use checkPermission instead.
+                         */
+                        @Deprecated
+                        protected boolean inClass(String name) {
+                            return false;
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+            package test.pkg {
+              public class Foo {
+                ctor public Foo();
+                method @Deprecated protected boolean inClass(String!);
+              }
+            }
+            """,
+            stubs = arrayOf(
+                """
+                This file should not be generated since --update-api is supplied.
+                """
+            )
         )
     }
 
diff --git a/src/test/java/com/android/tools/metalava/SymlinkTest.kt b/src/test/java/com/android/tools/metalava/SymlinkTest.kt
new file mode 100644
index 0000000..093a13e
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/SymlinkTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.tools.lint.checks.infrastructure.TestFiles.source
+import org.junit.Test
+import java.io.File
+
+/** Tests around symlinks in source trees */
+class SymlinkTest : DriverTest() {
+    @Test
+    fun `Symlink test making sure we don't iterate forever`() {
+        // This test checks two things:
+        // (1) if there are symlinks in the source directory, we
+        //     (a) don't recurse forever and (b) we emit a warning
+        //     that symlink targets are ignored, and
+        // (2) we ignore empty strings as paths. E.g.
+        //      --source-path ""
+        //     means to not look anywhere for the sources.
+
+        // Read the pwd setting that the --pwd flag in extraArguments
+        // below sets such that we can undo the damage for subsequent
+        // tests.
+        val before = System.getProperty("user.dir")
+        try {
+            check(
+                warnings = "TESTROOT/src/test/pkg/sub1/sub2/sub3: info: Ignoring symlink during package.html discovery directory traversal [IgnoringSymlink:159]",
+                sourceFiles = *arrayOf(
+                    java(
+                        """
+                        package test.pkg;
+                        import android.annotation.Nullable;
+                        import android.annotation.NonNull;
+                        public class Foo {
+                            /** These are the docs for method1. */
+                            @Nullable public Double method1(@NonNull Double factor1, @NonNull Double factor2) { }
+                            /** These are the docs for method2. It can sometimes return null. */
+                            @Nullable public Double method2(@NonNull Double factor1, @NonNull Double factor2) { }
+                            @Nullable public Double method3(@NonNull Double factor1, @NonNull Double factor2) { }
+                        }
+                        """
+                    ),
+                    source(
+                        "src/test/pkg/sub1/package.html",
+                        """
+                        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+                        <!-- not a body tag: <body> -->
+                        <html>
+                        <body bgcolor="white">
+                        My package docs<br>
+                        <!-- comment -->
+                        Sample code: /** code here */
+                        Another line.<br>
+                        </BODY>
+                        </html>
+                        """
+                    ).indented(),
+                    nonNullSource,
+                    nullableSource
+                ),
+                projectSetup = { dir ->
+                    // Add a symlink from deep in the source tree back out to the
+                    // root, which makes a cycle
+                    val file = File(dir, "src/test/pkg/sub1/sub2")
+                    file.mkdirs()
+                    val symlink = File(file, "sub3").toPath()
+                    java.nio.file.Files.createSymbolicLink(symlink, dir.toPath())
+
+                    val git = File(file, ".git").toPath()
+                    java.nio.file.Files.createSymbolicLink(git, dir.toPath())
+                },
+                // Empty source path: don't pick up random directory stuff
+                extraArguments = arrayOf(
+                    "--pwd", temporaryFolder.root.path,
+                    ARG_SOURCE_PATH, ""
+                ),
+                checkCompilation = false, // needs androidx.annotations in classpath
+                checkDoclava1 = false,
+                stubs = arrayOf(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Foo {
+                    public Foo() { throw new RuntimeException("Stub!"); }
+                    /**
+                     * These are the docs for method1.
+                     * @param factor1 This value must never be {@code null}.
+                     * @param factor2 This value must never be {@code null}.
+                     * @return This value may be {@code null}.
+                     */
+                    @androidx.annotation.Nullable
+                    public java.lang.Double method1(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
+                    /**
+                     * These are the docs for method2. It can sometimes return null.
+                     * @param factor1 This value must never be {@code null}.
+                     * @param factor2 This value must never be {@code null}.
+                     */
+                    @androidx.annotation.Nullable
+                    public java.lang.Double method2(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
+                    /**
+                     * @param factor1 This value must never be {@code null}.
+                     * @param factor2 This value must never be {@code null}.
+                     * @return This value may be {@code null}.
+                     */
+                    @androidx.annotation.Nullable
+                    public java.lang.Double method3(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
+                    }
+                    """
+                )
+            )
+        } finally {
+            System.setProperty("user.dir", before)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt b/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt
index de29f8f..f80741a 100644
--- a/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt
+++ b/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt
@@ -153,12 +153,12 @@
     }
 
     @Test
-    fun `Check SystemService -- at least one permission must be defined with anyOf`() {
+    fun `Check SystemService such that at least one permission must be defined with anyOf`() {
         check(
             warnings = """
-                    src/test/pkg/MyTest2.java:5: error: None of the permissions foo.bar.PERMISSION1, foo.bar.PERMISSION2 are defined by manifest TESTROOT/manifest.xml. [RemovedField:10]
-                    src/test/pkg/MyTest2.java:5: lint: Method 'myMethod1' must be protected with a system permission. [RequiresPermission:125]
-                    """,
+                src/test/pkg/MyTest2.java:5: lint: None of the permissions foo.bar.PERMISSION1, foo.bar.PERMISSION2 are defined by manifest TESTROOT/manifest.xml. [RequiresPermission:125]
+                src/test/pkg/MyTest2.java:5: lint: Method 'myMethod1' must be protected with a system permission. [RequiresPermission:125]
+                """,
             compatibilityMode = false,
             includeSystemApiAnnotations = true,
             sourceFiles = *arrayOf(
@@ -186,7 +186,7 @@
     @Test
     fun `Check SystemService -- missing one permission with allOf`() {
         check(
-            warnings = "src/test/pkg/MyTest2.java:5: error: Permission 'foo.bar.PERMISSION2' is not defined by manifest TESTROOT/manifest.xml. [RemovedField:10]",
+            warnings = "src/test/pkg/MyTest2.java:5: lint: Permission 'foo.bar.PERMISSION2' is not defined by manifest TESTROOT/manifest.xml. [RequiresPermission:125]",
             compatibilityMode = false,
             includeSystemApiAnnotations = true,
             sourceFiles = *arrayOf(
@@ -262,8 +262,8 @@
     fun `Check SystemService -- missing manifest permissions`() {
         check(
             warnings = """
-                src/test/pkg/MyTest2.java:5: error: Permission 'Manifest.permission.MY_PERMISSION' is not defined by manifest TESTROOT/manifest.xml. [RemovedField:10]
-                src/test/pkg/MyTest2.java:5: error: Permission 'Manifest.permission.MY_PERMISSION2' is not defined by manifest TESTROOT/manifest.xml. [RemovedField:10]
+                src/test/pkg/MyTest2.java:5: lint: Permission 'Manifest.permission.MY_PERMISSION' is not defined by manifest TESTROOT/manifest.xml. [RequiresPermission:125]
+                src/test/pkg/MyTest2.java:5: lint: Permission 'Manifest.permission.MY_PERMISSION2' is not defined by manifest TESTROOT/manifest.xml. [RequiresPermission:125]
                 src/test/pkg/MyTest2.java:5: lint: Method 'test' must be protected with a system permission. [RequiresPermission:125]
                 """,
             compatibilityMode = false,
@@ -294,7 +294,7 @@
         check(
             warnings = """
                 TESTROOT/manifest.xml: error: Failed to parse TESTROOT/manifest.xml: The markup in the document preceding the root element must be well-formed. [ParseError:1]
-                src/test/pkg/MyTest2.java:6: error: None of the permissions foo.bar.PERMISSION1, foo.bar.PERMISSION2 are defined by manifest TESTROOT/manifest.xml. [RemovedField:10]
+                src/test/pkg/MyTest2.java:6: lint: None of the permissions foo.bar.PERMISSION1, foo.bar.PERMISSION2 are defined by manifest TESTROOT/manifest.xml. [RequiresPermission:125]
                 src/test/pkg/MyTest2.java:6: lint: Method 'test' must be protected with a system permission. [RequiresPermission:125]
                 """,
             compatibilityMode = false,
@@ -359,7 +359,7 @@
             ),
             manifest = """<?xml version="1.0" encoding="UTF-8"?>
                 <manifest/>
-                            """
+                """
         )
     }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/UnhideApisTest.kt b/src/test/java/com/android/tools/metalava/UnhideApisTest.kt
new file mode 100644
index 0000000..98f4d82
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/UnhideApisTest.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("ALL")
+
+package com.android.tools.metalava
+
+import org.junit.Test
+
+class UnhideApisTest : DriverTest() {
+    @Test
+    fun `Report hidden API access rather than opening up access`() {
+        check(
+            extraArguments = arrayOf(
+                ARG_HIDE,
+                "HiddenSuperclass",
+                ARG_HIDE,
+                "UnavailableSymbol",
+                ARG_HIDE,
+                "HiddenTypeParameter",
+                ARG_ERROR,
+                "ReferencesHidden"
+            ),
+            warnings = """
+            src/test/pkg/Foo.java:3: error: Class test.pkg.Hidden1 is not public but was referenced (as field type) from public field test.pkg.Foo.hidden1 [ReferencesHidden:158]
+            src/test/pkg/Foo.java:4: error: Class test.pkg.Hidden2 is hidden but was referenced (as field type) from public field test.pkg.Foo.hidden2 [ReferencesHidden:158]
+            src/test/pkg/Foo.java:5: error: Class test.pkg.Hidden1 is not public but was referenced (as parameter type) from public parameter hidden1 in test.pkg.Foo.method(test.pkg.Hidden1 hidden1, test.pkg.Hidden2 hidden2) [ReferencesHidden:158]
+            src/test/pkg/Foo.java:5: error: Class test.pkg.Hidden2 is hidden but was referenced (as parameter type) from public parameter hidden2 in test.pkg.Foo.method(test.pkg.Hidden1 hidden1, test.pkg.Hidden2 hidden2) [ReferencesHidden:158]
+            src/test/pkg/Foo.java:5: error: Class test.pkg.Hidden3 is hidden but was referenced (as exception) from public method test.pkg.Foo.method(test.pkg.Hidden1,test.pkg.Hidden2) [ReferencesHidden:158]
+            src/test/pkg/Foo.java:7: error: Class test.pkg.Hidden1 is not public but was referenced (as type parameter) from public method test.pkg.Foo.get(T) [ReferencesHidden:158]
+            src/test/pkg/Foo.java:7: error: Class test.pkg.Hidden2 is hidden but was referenced (as type parameter) from public method test.pkg.Foo.get(T) [ReferencesHidden:158]
+            src/test/pkg/Foo.java:8: error: Class test.pkg.Hidden1 is not public but was referenced (as return type) from public method test.pkg.Foo.getHidden1() [ReferencesHidden:158]
+            src/test/pkg/Foo.java:9: error: Class test.pkg.Hidden2 is hidden but was referenced (as return type) from public method test.pkg.Foo.getHidden2() [ReferencesHidden:158]
+            """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class Foo extends Hidden2 {
+                        public Hidden1 hidden1;
+                        public Hidden2 hidden2;
+                        public void method(Hidden1 hidden1, Hidden2 hidden2) throws Hidden3 {
+                        }
+                        public <S extends Hidden1, T extends Hidden2> S get(T t) { return null; }
+                        public Hidden1 getHidden1() { return null; }
+                        public Hidden2 getHidden2() { return null; }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    // Implicitly not part of the API by being package private
+                    class Hidden1 {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    /** @hide */
+                    public class Hidden2 {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    /** @hide */
+                    public class Hidden3 extends IOException {
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class Foo {
+                    ctor public Foo();
+                    method public <S extends test.pkg.Hidden1, T extends test.pkg.Hidden2> S get(T);
+                    method public test.pkg.Hidden1 getHidden1();
+                    method public test.pkg.Hidden2 getHidden2();
+                    method public void method(test.pkg.Hidden1, test.pkg.Hidden2) throws test.pkg.Hidden3;
+                    field public test.pkg.Hidden1 hidden1;
+                    field public test.pkg.Hidden2 hidden2;
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Including private interfaces from types`() {
+        check(
+            extraArguments = arrayOf(ARG_ERROR, "ReferencesHidden"),
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java("""package test.pkg1; interface Interface1 { }"""),
+                java("""package test.pkg1; abstract class Class1 { }"""),
+                java("""package test.pkg1; abstract class Class2 { }"""),
+                java("""package test.pkg1; abstract class Class3 { }"""),
+                java("""package test.pkg1; abstract class Class4 { }"""),
+                java("""package test.pkg1; abstract class Class5 { }"""),
+                java("""package test.pkg1; abstract class Class6 { }"""),
+                java("""package test.pkg1; abstract class Class7 { }"""),
+                java("""package test.pkg1; abstract class Class8 { }"""),
+                java("""package test.pkg1; abstract class Class9 { }"""),
+                java(
+                    """
+                    package test.pkg1;
+
+                    import java.util.List;
+                    import java.util.Map;
+                    public abstract class Usage implements List<Class1> {
+                       <T extends java.lang.Comparable<? super T>> void sort(java.util.List<T> list) {}
+                       public Class3 myClass1 = null;
+                       public List<? extends Class4> myClass2 = null;
+                       public Map<String, ? extends Class5> myClass3 = null;
+                       public <T extends Class6> void mySort(List<Class7> list, T element) {}
+                       public void ellipsisType(Class8... myargs);
+                       public void arrayType(Class9[] myargs);
+                    }
+                    """
+                )
+            ),
+
+            // TODO: Test annotations! (values, annotation classes, etc.)
+            warnings = """
+                    src/test/pkg1/Usage.java:7: error: Class test.pkg1.Class3 is not public but was referenced (as field type) from public field test.pkg1.Usage.myClass1 [ReferencesHidden:158]
+                    src/test/pkg1/Usage.java:8: error: Class test.pkg1.Class4 is not public but was referenced (as field type argument class) from public field test.pkg1.Usage.myClass2 [ReferencesHidden:158]
+                    src/test/pkg1/Usage.java:9: error: Class test.pkg1.Class5 is not public but was referenced (as field type argument class) from public field test.pkg1.Usage.myClass3 [ReferencesHidden:158]
+                    src/test/pkg1/Usage.java:10: error: Class test.pkg1.Class6 is not public but was referenced (as type parameter) from public method test.pkg1.Usage.mySort(java.util.List<test.pkg1.Class7>,T) [ReferencesHidden:158]
+                    src/test/pkg1/Usage.java:10: error: Class test.pkg1.Class7 is not public but was referenced (as type parameter) from public parameter list in test.pkg1.Usage.mySort(java.util.List<test.pkg1.Class7> list, T element) [ReferencesHidden:158]
+                    src/test/pkg1/Usage.java:10: error: Class test.pkg1.Class7 is not public but was referenced (as parameter type) from public parameter list in test.pkg1.Usage.mySort(java.util.List<test.pkg1.Class7> list, T element) [ReferencesHidden:158]
+                    src/test/pkg1/Usage.java:11: error: Class test.pkg1.Class8 is not public but was referenced (as parameter type) from public parameter myargs in test.pkg1.Usage.ellipsisType(test.pkg1.Class8... myargs) [ReferencesHidden:158]
+                    src/test/pkg1/Usage.java:12: error: Class test.pkg1.Class9 is not public but was referenced (as parameter type) from public parameter myargs in test.pkg1.Usage.arrayType(test.pkg1.Class9[] myargs) [ReferencesHidden:158]
+                    src/test/pkg1/Usage.java:12: warning: Parameter myargs references hidden type test.pkg1.Class9[]. [HiddenTypeParameter:121]
+                    src/test/pkg1/Usage.java:11: warning: Parameter myargs references hidden type test.pkg1.Class8.... [HiddenTypeParameter:121]
+                    src/test/pkg1/Usage.java:10: warning: Parameter list references hidden type class test.pkg1.Class7. [HiddenTypeParameter:121]
+                    src/test/pkg1/Usage.java:7: warning: Field Usage.myClass1 references hidden type test.pkg1.Class3. [HiddenTypeParameter:121]
+                    src/test/pkg1/Usage.java:8: warning: Field Usage.myClass2 references hidden type class test.pkg1.Class4. [HiddenTypeParameter:121]
+                    src/test/pkg1/Usage.java:9: warning: Field Usage.myClass3 references hidden type class test.pkg1.Class5. [HiddenTypeParameter:121]
+                    """,
+            api = """
+                    package test.pkg1 {
+                      public abstract class Usage implements java.util.List {
+                        ctor public Usage();
+                        method public void arrayType(test.pkg1.Class9[]);
+                        method public void ellipsisType(test.pkg1.Class8...);
+                        method public <T extends test.pkg1.Class6> void mySort(java.util.List<test.pkg1.Class7>, T);
+                        field public test.pkg1.Class3 myClass1;
+                        field public java.util.List<? extends test.pkg1.Class4> myClass2;
+                        field public java.util.Map<java.lang.String, ? extends test.pkg1.Class5> myClass3;
+                      }
+                    }
+                """
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt b/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt
index 8ea2750..c62331d 100644
--- a/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt
+++ b/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.tools.metalava.apilevels
 
+import com.android.tools.metalava.ARG_ANDROID_JAR_PATTERN
+import com.android.tools.metalava.ARG_GENERATE_API_LEVELS
 import com.android.tools.metalava.DriverTest
 import com.android.utils.XmlUtils
 import com.google.common.truth.Truth
@@ -46,11 +48,11 @@
 
         check(
             extraArguments = arrayOf(
-                "--generate-api-levels",
+                ARG_GENERATE_API_LEVELS,
                 outputPath,
-                "--android-jar-pattern",
+                ARG_ANDROID_JAR_PATTERN,
                 "${oldSdkJars.path}/android-%/android.jar",
-                "--android-jar-pattern",
+                ARG_ANDROID_JAR_PATTERN,
                 "${platformJars.path}/%/public/android.jar"
             ),
             checkDoclava1 = false,
diff --git a/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt b/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
index f3c367a..4d7d6db 100644
--- a/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
@@ -21,11 +21,11 @@
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Test
-import java.util.function.Predicate
+import java.io.File
 
 class TextBackedAnnotationItemTest {
     // Dummy for use in test where we don't need codebase functionality
-    private val dummyCodebase = object : DefaultCodebase() {
+    private val dummyCodebase = object : DefaultCodebase(File("").canonicalFile) {
         override fun supportsDocumentation(): Boolean = false
         override var description: String = ""
         override fun getPackages(): PackageList = unsupported()
@@ -33,7 +33,6 @@
         override fun findClass(className: String): ClassItem? = unsupported()
         override fun findPackage(pkgName: String): PackageItem? = unsupported()
         override fun trustedApi(): Boolean = false
-        override fun filter(filterEmit: Predicate<Item>, filterReference: Predicate<Item>): Codebase = unsupported()
         override var supportsStagedNullability: Boolean = false
     }
 
diff --git a/src/test/java/com/android/tools/metalava/model/psi/JavadocTest.kt b/src/test/java/com/android/tools/metalava/model/psi/JavadocTest.kt
new file mode 100644
index 0000000..8642575
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/model/psi/JavadocTest.kt
@@ -0,0 +1,922 @@
+/*
+ * 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.model.psi
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.metalava.DriverTest
+import org.intellij.lang.annotations.Language
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class JavadocTest : DriverTest() {
+    private fun checkStubs(
+        @Language("JAVA") source: String,
+        compatibilityMode: Boolean = true,
+        warnings: String? = "",
+        checkDoclava1: Boolean = false,
+        api: String? = null,
+        extraArguments: Array<String> = emptyArray(),
+        docStubs: Boolean = false,
+        showAnnotations: Array<String> = emptyArray(),
+        includeSourceRetentionAnnotations: Boolean = true,
+        skipEmitPackages: List<String> = listOf("java.lang", "java.util", "java.io"),
+        vararg sourceFiles: TestFile
+    ) {
+        check(
+            *sourceFiles,
+            showAnnotations = showAnnotations,
+            stubs = arrayOf(source),
+            compatibilityMode = compatibilityMode,
+            warnings = warnings,
+            checkDoclava1 = checkDoclava1,
+            checkCompilation = true,
+            api = api,
+            extraArguments = extraArguments,
+            docStubs = docStubs,
+            includeSourceRetentionAnnotations = includeSourceRetentionAnnotations,
+            skipEmitPackages = skipEmitPackages
+        )
+    }
+
+    @Test
+    fun `Test package to package info`() {
+        @Language("HTML")
+        val html = """
+            <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+            <!-- not a body tag: <body> -->
+            <html>
+            <body bgcolor="white">
+            My package docs<br>
+            <!-- comment -->
+            Sample code: /** code here */
+            Another line.<br>
+            </BODY>
+            </html>
+            """
+
+        @Suppress("DanglingJavadoc")
+        @Language("JAVA")
+        val java = """
+            /**
+             * My package docs<br>
+             * <!-- comment -->
+             * Sample code: /** code here &#42;/
+             * Another line.<br>
+             */
+            """
+
+        assertEquals(java.trimIndent() + "\n", packageHtmlToJavadoc(html.trimIndent()))
+    }
+
+    @Test
+    fun `Relative documentation links in stubs`() {
+        checkStubs(
+            docStubs = false,
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg1;
+                    import java.io.IOException;
+                    import test.pkg2.OtherClass;
+
+                    /**
+                     *  Blah blah {@link OtherClass} blah blah.
+                     *  Referencing <b>field</b> {@link OtherClass#foo},
+                     *  and referencing method {@link OtherClass#bar(int,
+                     *   boolean)}.
+                     *  And relative method reference {@link #baz()}.
+                     *  And relative field reference {@link #importance}.
+                     *  Here's an already fully qualified reference: {@link test.pkg2.OtherClass}.
+                     *  And here's one in the same package: {@link LocalClass}.
+                     *
+                     *  @deprecated For some reason
+                     *  @see OtherClass
+                     *  @see OtherClass#bar(int, boolean)
+                     */
+                    @SuppressWarnings("all")
+                    public class SomeClass {
+                       /**
+                       * My method.
+                       * @param focus The focus to find. One of {@link OtherClass#FOCUS_INPUT} or
+                       *         {@link OtherClass#FOCUS_ACCESSIBILITY}.
+                       * @throws IOException when blah blah blah
+                       * @throws {@link RuntimeException} when blah blah blah
+                       */
+                       public void baz(int focus) throws IOException;
+                       public boolean importance;
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg2;
+
+                    @SuppressWarnings("all")
+                    public class OtherClass {
+                        public static final int FOCUS_INPUT = 1;
+                        public static final int FOCUS_ACCESSIBILITY = 2;
+                        public int foo;
+                        public void bar(int baz, boolean bar);
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg1;
+
+                    @SuppressWarnings("all")
+                    public class LocalClass {
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg1;
+                    import test.pkg2.OtherClass;
+                    import java.io.IOException;
+                    /**
+                     *  Blah blah {@link OtherClass} blah blah.
+                     *  Referencing <b>field</b> {@link OtherClass#foo},
+                     *  and referencing method {@link OtherClass#bar(int,
+                     *   boolean)}.
+                     *  And relative method reference {@link #baz()}.
+                     *  And relative field reference {@link #importance}.
+                     *  Here's an already fully qualified reference: {@link test.pkg2.OtherClass}.
+                     *  And here's one in the same package: {@link LocalClass}.
+                     *
+                     *  @deprecated For some reason
+                     *  @see OtherClass
+                     *  @see OtherClass#bar(int, boolean)
+                     */
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    @Deprecated
+                    public class SomeClass {
+                    public SomeClass() { throw new RuntimeException("Stub!"); }
+                    /**
+                     * My method.
+                     * @param focus The focus to find. One of {@link OtherClass#FOCUS_INPUT} or
+                     *         {@link OtherClass#FOCUS_ACCESSIBILITY}.
+                     * @throws IOException when blah blah blah
+                     * @throws {@link RuntimeException} when blah blah blah
+                     */
+                    public void baz(int focus) throws java.io.IOException { throw new RuntimeException("Stub!"); }
+                    public boolean importance;
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Rewrite relative documentation links in doc-stubs`() {
+        checkStubs(
+            docStubs = true,
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg1;
+                    import java.io.IOException;
+                    import test.pkg2.OtherClass;
+
+                    /**
+                     *  Blah blah {@link OtherClass} blah blah.
+                     *  Referencing <b>field</b> {@link OtherClass#foo},
+                     *  and referencing method {@link OtherClass#bar(int,
+                     *   boolean)}.
+                     *  And relative method reference {@link #baz()}.
+                     *  And relative field reference {@link #importance}.
+                     *  Here's an already fully qualified reference: {@link test.pkg2.OtherClass}.
+                     *  And here's one in the same package: {@link LocalClass}.
+                     *
+                     *  @deprecated For some reason
+                     *  @see OtherClass
+                     *  @see OtherClass#bar(int, boolean)
+                     */
+                    @SuppressWarnings("all")
+                    public class SomeClass {
+                       /**
+                       * My method.
+                       * @param focus The focus to find. One of {@link OtherClass#FOCUS_INPUT} or
+                       *         {@link OtherClass#FOCUS_ACCESSIBILITY}.
+                       * @throws IOException when blah blah blah
+                       * @throws {@link RuntimeException} when blah blah blah
+                       */
+                       public void baz(int focus) throws IOException;
+                       public boolean importance;
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg2;
+
+                    @SuppressWarnings("all")
+                    public class OtherClass {
+                        public static final int FOCUS_INPUT = 1;
+                        public static final int FOCUS_ACCESSIBILITY = 2;
+                        public int foo;
+                        public void bar(int baz, boolean bar);
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg1;
+
+                    @SuppressWarnings("all")
+                    public class LocalClass {
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                package test.pkg1;
+                import test.pkg2.OtherClass;
+                import java.io.IOException;
+                /**
+                 *  Blah blah {@link test.pkg2.OtherClass OtherClass} blah blah.
+                 *  Referencing <b>field</b> {@link test.pkg2.OtherClass#foo OtherClass#foo},
+                 *  and referencing method {@link test.pkg2.OtherClass#bar(int,boolean) OtherClass#bar(int,
+                 *   boolean)}.
+                 *  And relative method reference {@link #baz()}.
+                 *  And relative field reference {@link #importance}.
+                 *  Here's an already fully qualified reference: {@link test.pkg2.OtherClass}.
+                 *  And here's one in the same package: {@link test.pkg1.LocalClass LocalClass}.
+                 *
+                 *  @deprecated For some reason
+                 *  @see test.pkg2.OtherClass
+                 *  @see test.pkg2.OtherClass#bar(int, boolean)
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @Deprecated
+                public class SomeClass {
+                public SomeClass() { throw new RuntimeException("Stub!"); }
+                /**
+                 * My method.
+                 * @param focus The focus to find. One of {@link test.pkg2.OtherClass#FOCUS_INPUT OtherClass#FOCUS_INPUT} or
+                 *         {@link test.pkg2.OtherClass#FOCUS_ACCESSIBILITY OtherClass#FOCUS_ACCESSIBILITY}.
+                 * @throws java.io.IOException when blah blah blah
+                 * @throws {@link java.lang.RuntimeException RuntimeException} when blah blah blah
+                 */
+                public void baz(int focus) throws java.io.IOException { throw new RuntimeException("Stub!"); }
+                public boolean importance;
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Rewrite relative documentation links in doc-stubs 2`() {
+        // Properly handle links to inherited methods
+        checkStubs(
+            docStubs = true,
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg1;
+                    import java.io.IOException;
+                    import test.pkg2.OtherClass;
+
+                    @SuppressWarnings("all")
+                    public class R {
+                        public static class attr {
+                            /**
+                             * Resource identifier to assign to this piece of named meta-data.
+                             * The resource identifier can later be retrieved from the meta data
+                             * Bundle through {@link android.os.Bundle#getInt Bundle.getInt}.
+                             * <p>May be a reference to another resource, in the form
+                             * "<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>" or a theme
+                             * attribute in the form
+                             * "<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>".
+                             */
+                            public static final int resource=0x01010025;
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android.os;
+
+                    @SuppressWarnings("all")
+                    public class Bundle extends BaseBundle {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android.os;
+
+                    @SuppressWarnings("all")
+                    public class BaseBundle {
+                        public int getInt(String key) {
+                            return getInt(key, 0);
+                        }
+
+                        public int getInt(String key, int defaultValue) {
+                            return defaultValue;
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                package test.pkg1;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class R {
+                public R() { throw new RuntimeException("Stub!"); }
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public static class attr {
+                public attr() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Resource identifier to assign to this piece of named meta-data.
+                 * The resource identifier can later be retrieved from the meta data
+                 * Bundle through {@link android.os.Bundle#getInt Bundle.getInt}.
+                 * <p>May be a reference to another resource, in the form
+                 * "<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>" or a theme
+                 * attribute in the form
+                 * "<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>".
+                 */
+                public static final int resource = 16842789; // 0x1010025
+                }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Rewrite relative documentation links in doc-stubs 3`() {
+        checkStubs(
+            docStubs = true,
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package android.accessibilityservice;
+
+                    import android.view.accessibility.AccessibilityEvent;
+
+                    /**
+                     * <p>
+                     * Window content may be retrieved with
+                     * {@link AccessibilityEvent#getSource() AccessibilityEvent.getSource()},
+                     * </p>
+                     */
+                    @SuppressWarnings("all")
+                    public abstract class AccessibilityService {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android.view.accessibility;
+
+                    @SuppressWarnings("all")
+                    public final class AccessibilityEvent extends AccessibilityRecord {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android.view.accessibility;
+
+                    @SuppressWarnings("all")
+                    public class AccessibilityRecord {
+                        public AccessibilityNodeInfo getSource() {
+                            return null;
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                package android.accessibilityservice;
+                import android.view.accessibility.AccessibilityEvent;
+                /**
+                 * <p>
+                 * Window content may be retrieved with
+                 * {@link android.view.accessibility.AccessibilityEvent#getSource() AccessibilityEvent#getSource()},
+                 * </p>
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class AccessibilityService {
+                public AccessibilityService() { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Rewrite relative documentation links in doc-stubs 4`() {
+        checkStubs(
+            docStubs = true,
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package android.content;
+
+                    import android.os.OperationCanceledException;
+
+                    @SuppressWarnings("all")
+                    public abstract class AsyncTaskLoader {
+                        /**
+                         * Called if the task was canceled before it was completed.  Gives the class a chance
+                         * to clean up post-cancellation and to properly dispose of the result.
+                         *
+                         * @param data The value that was returned by {@link #loadInBackground}, or null
+                         * if the task threw {@link OperationCanceledException}.
+                         */
+                        public void onCanceled(D data) {
+                        }
+
+                        /**
+                         * Called on a worker thread to perform the actual load and to return
+                         * the result of the load operation.
+                         *
+                         * Implementations should not deliver the result directly, but should return them
+                         * from this method, which will eventually end up calling {@link #deliverResult} on
+                         * the UI thread.  If implementations need to process the results on the UI thread
+                         * they may override {@link #deliverResult} and do so there.
+                         *
+                         * When the load is canceled, this method may either return normally or throw
+                         * {@link OperationCanceledException}.  In either case, the Loader will
+                         * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
+                         * result object, if any.
+                         *
+                         * @return The result of the load operation.
+                         *
+                         * @throws OperationCanceledException if the load is canceled during execution.
+                         *
+                         * @see #onCanceled
+                         */
+                        public abstract Object loadInBackground();
+
+                        /**
+                         * Sends the result of the load to the registered listener. Should only be called by subclasses.
+                         *
+                         * Must be called from the process's main thread.
+                         *
+                         * @param data the result of the load
+                         */
+                        public void deliverResult(Object data) {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android.os;
+
+
+                    /**
+                     * An exception type that is thrown when an operation in progress is canceled.
+                     */
+                    @SuppressWarnings("all")
+                    public class OperationCanceledException extends RuntimeException {
+                        public OperationCanceledException() {
+                            this(null);
+                        }
+
+                        public OperationCanceledException(String message) {
+                            super(message != null ? message : "The operation has been canceled.");
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                package android.content;
+                import android.os.OperationCanceledException;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class AsyncTaskLoader {
+                public AsyncTaskLoader() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Called if the task was canceled before it was completed.  Gives the class a chance
+                 * to clean up post-cancellation and to properly dispose of the result.
+                 *
+                 * @param data The value that was returned by {@link #loadInBackground}, or null
+                 * if the task threw {@link android.os.OperationCanceledException OperationCanceledException}.
+                 */
+                public void onCanceled(D data) { throw new RuntimeException("Stub!"); }
+                /**
+                 * Called on a worker thread to perform the actual load and to return
+                 * the result of the load operation.
+                 *
+                 * Implementations should not deliver the result directly, but should return them
+                 * from this method, which will eventually end up calling {@link #deliverResult} on
+                 * the UI thread.  If implementations need to process the results on the UI thread
+                 * they may override {@link #deliverResult} and do so there.
+                 *
+                 * When the load is canceled, this method may either return normally or throw
+                 * {@link android.os.OperationCanceledException OperationCanceledException}.  In either case, the Loader will
+                 * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
+                 * result object, if any.
+                 *
+                 * @return The result of the load operation.
+                 *
+                 * @throws android.os.OperationCanceledException if the load is canceled during execution.
+                 *
+                 * @see #onCanceled
+                 */
+                public abstract java.lang.Object loadInBackground();
+                /**
+                 * Sends the result of the load to the registered listener. Should only be called by subclasses.
+                 *
+                 * Must be called from the process's main thread.
+                 *
+                 * @param data the result of the load
+                 */
+                public void deliverResult(java.lang.Object data) { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Rewrite relative documentation links in doc-stubs 5`() {
+        // Properly handle links to inherited methods
+        checkStubs(
+            docStubs = true,
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package org.xmlpull.v1;
+
+                    /**
+                     * Example docs.
+                     * <pre>
+                     * import org.xmlpull.v1.<a href="XmlPullParserException.html">XmlPullParserException</a>;
+                     *         xpp.<a href="#setInput">setInput</a>( new StringReader ( "&lt;foo>Hello World!&lt;/foo>" ) );
+                     * </pre>
+                     * see #setInput
+                     */
+                    @SuppressWarnings("all")
+                    public interface XmlPullParser {
+                        void setInput();
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                package org.xmlpull.v1;
+                /**
+                 * Example docs.
+                 * <pre>
+                 * import org.xmlpull.v1.<a href="XmlPullParserException.html">XmlPullParserException</a>;
+                 *         xpp.<a href="#setInput">setInput</a>( new StringReader ( "&lt;foo>Hello World!&lt;/foo>" ) );
+                 * </pre>
+                 * see #setInput
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public interface XmlPullParser {
+                public void setInput();
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Check references to inherited field constants`() {
+        checkStubs(
+            docStubs = true,
+            compatibilityMode = false,
+            checkDoclava1 = false,
+            warnings = "",
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg1;
+                    import test.pkg2.MyChild;
+
+                    /**
+                     * Reference to {@link MyChild#CONSTANT1},
+                     * {@link MyChild#CONSTANT2}, and
+                     * {@link MyChild#myMethod}.
+                     * <p>
+                     * Absolute reference:
+                     * {@link test.pkg2.MyChild#CONSTANT1 MyChild.CONSTANT1}
+                     * <p>
+                     * Inner class reference:
+                     * {@link Test.TestInner#CONSTANT3}, again
+                     * {@link TestInner#CONSTANT3}
+                     *
+                     * @see test.pkg2.MyChild#myMethod
+                     */
+                    @SuppressWarnings("all")
+                    public class Test {
+                        public static class TestInner {
+                            public static final String CONSTANT3 = "Hello";
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg1;
+                    @SuppressWarnings("all")
+                    interface MyConstants {
+                        long CONSTANT1 = 12345;
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg1;
+                    import java.io.Closeable;
+                    @SuppressWarnings("all")
+                    class MyParent implements MyConstants, Closeable {
+                        public static final long CONSTANT2 = 67890;
+                        public void myMethod() {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg2;
+
+                    import test.pkg1.MyParent;
+                    @SuppressWarnings("all")
+                    public class MyChild extends MyParent implements MyConstants {
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg1;
+                import test.pkg2.MyChild;
+                /**
+                 * Reference to {@link test.pkg2.MyChild#CONSTANT1 MyChild#CONSTANT1},
+                 * {@link test.pkg2.MyChild#CONSTANT2 MyChild#CONSTANT2}, and
+                 * {@link test.pkg2.MyChild#myMethod MyChild#myMethod}.
+                 * <p>
+                 * Absolute reference:
+                 * {@link test.pkg2.MyChild#CONSTANT1 MyChild.CONSTANT1}
+                 * <p>
+                 * Inner class reference:
+                 * {@link test.pkg1.Test.TestInner#CONSTANT3 Test.TestInner#CONSTANT3}, again
+                 * {@link test.pkg1.Test.TestInner#CONSTANT3 TestInner#CONSTANT3}
+                 *
+                 * @see test.pkg2.MyChild#myMethod
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Test {
+                public Test() { throw new RuntimeException("Stub!"); }
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public static class TestInner {
+                public TestInner() { throw new RuntimeException("Stub!"); }
+                public static final java.lang.String CONSTANT3 = "Hello";
+                }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Handle @attr references`() {
+        checkStubs(
+            docStubs = true,
+            compatibilityMode = false,
+            checkDoclava1 = false,
+            warnings = "",
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg1;
+
+                    @SuppressWarnings("all")
+                    public class Test {
+                        /**
+                         * Returns the drawable that will be drawn between each item in the list.
+                         *
+                         * @return the current drawable drawn between list elements
+                         * This value may be {@code null}.
+                         * @attr ref R.styleable#ListView_divider
+                         */
+                        public Object getFoo() {
+                            return null;
+                        }
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg1;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Test {
+                public Test() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Returns the drawable that will be drawn between each item in the list.
+                 *
+                 * @return the current drawable drawn between list elements
+                 * This value may be {@code null}.
+                 * @attr ref android.R.styleable#ListView_divider
+                 */
+                public java.lang.Object getFoo() { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Rewrite parameter list`() {
+        checkStubs(
+            docStubs = true,
+            compatibilityMode = false,
+            checkDoclava1 = false,
+            warnings = "",
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg1;
+                    import test.pkg2.OtherClass1;
+                    import test.pkg2.OtherClass2;
+
+                    /**
+                     * Reference to {@link OtherClass1#myMethod(OtherClass2, int name, OtherClass2[])},
+                     */
+                    @SuppressWarnings("all")
+                    public class Test<E extends OtherClass2> {
+                        /**
+                         * Reference to {@link OtherClass1#myMethod(E, int, OtherClass2 [])},
+                         */
+                        public void test() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg2;
+
+                    @SuppressWarnings("all")
+                    class OtherClass1 {
+                        public void myMethod(OtherClass2 parameter1, int parameter2, OtherClass2[] parameter3) {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg2;
+
+                    @SuppressWarnings("all")
+                    public class OtherClass2 {
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg1;
+                import test.pkg2.OtherClass2;
+                /**
+                 * Reference to {@link test.pkg2.OtherClass1#myMethod(test.pkg2.OtherClass2,int name,test.pkg2.OtherClass2[]) OtherClass1#myMethod(OtherClass2, int name, OtherClass2[])},
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Test<E extends test.pkg2.OtherClass2> {
+                public Test() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Reference to {@link test.pkg2.OtherClass1#myMethod(E,int,test.pkg2.OtherClass2[]) OtherClass1#myMethod(E, int, OtherClass2 [])},
+                 */
+                public void test() { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Rewrite parameter list 2`() {
+        checkStubs(
+            docStubs = true,
+            compatibilityMode = false,
+            checkDoclava1 = false,
+            warnings = "",
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg1;
+                    import java.nio.ByteBuffer;
+
+                    @SuppressWarnings("all")
+                    public class Test {
+                        /**
+                         * Blah blah
+                         * <blockquote><pre>
+                         * {@link #wrap(ByteBuffer [], int, int, ByteBuffer)
+                         *     engine.wrap(new ByteBuffer [] { src }, 0, 1, dst);}
+                         * </pre></blockquote>
+                         */
+                        public void test() { }
+
+                        public abstract void wrap(ByteBuffer [] srcs, int offset,
+                            int length, ByteBuffer dst);
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg1;
+                import java.nio.ByteBuffer;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Test {
+                public Test() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Blah blah
+                 * <blockquote><pre>
+                 * {@link #wrap(java.nio.ByteBuffer[],int,int,java.nio.ByteBuffer)
+                 *     engine.wrap(new ByteBuffer [] { src }, 0, 1, dst);}
+                 * </pre></blockquote>
+                 */
+                public void test() { throw new RuntimeException("Stub!"); }
+                public abstract void wrap(java.nio.ByteBuffer[] srcs, int offset, int length, java.nio.ByteBuffer dst);
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Warn about unresolved`() {
+        @Suppress("ConstantConditionIf")
+        checkStubs(
+            docStubs = true,
+            compatibilityMode = false,
+            checkDoclava1 = false,
+            warnings =
+            if (REPORT_UNRESOLVED_SYMBOLS) {
+                """
+                src/test/pkg1/Test.java:6: lint: Unresolved documentation reference: SomethingMissing [UnresolvedLink:101]
+                src/test/pkg1/Test.java:6: lint: Unresolved documentation reference: OtherMissing [UnresolvedLink:101]
+            """
+            } else {
+                ""
+            },
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg1;
+                    import java.nio.ByteBuffer;
+
+                    @SuppressWarnings("all")
+                    public class Test {
+                        /**
+                         * Reference to {@link SomethingMissing} and
+                         * {@link String#randomMethod}.
+                         *
+                         * @see OtherMissing
+                         */
+                        public void test() { }
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg1;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Test {
+                public Test() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Reference to {@link SomethingMissing} and
+                 * {@link java.lang.String#randomMethod String#randomMethod}.
+                 *
+                 * @see OtherMissing
+                 */
+                public void test() { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/model/psi/PsiBasedCodebaseTest.kt b/src/test/java/com/android/tools/metalava/model/psi/PsiBasedCodebaseTest.kt
new file mode 100644
index 0000000..7db8c52
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/model/psi/PsiBasedCodebaseTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.model.psi
+
+import com.android.tools.lint.checks.infrastructure.TestFiles.base64gzip
+import com.android.tools.metalava.DriverTest
+import org.junit.Test
+
+class PsiBasedCodebaseTest : DriverTest() {
+    @Test
+    fun `Regression test for issue 112931426`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg2;
+                    public class Foo {
+                        public test.pkg.ForeignClass getFoo(test.pkg.ForeignClass.ExtraCallback callback) {
+                            return null;
+                        }
+                    }
+                    """
+                )
+            ),
+            classpath = arrayOf(
+                /* The following source file, compiled, and root folder jar'ed and stored as base64 gzip:
+                    package test.pkg;
+
+                    public class ForeignClass {
+                        public class ExtraCallback extends Something.Callback {
+                            public void foo() {}
+                        }
+
+                        public static class Something {
+                            public static class Callback {
+                                public void foo() {}
+                            }
+                        }
+                    }
+                 */
+                base64gzip(
+                    "test.jar", "" +
+                        "H4sIAAAAAAAAAAvwZmYRYeDg4GCYUiXhy4AEOBlYGHxdQxx1Pf3c9P+dYmBg" +
+                        "ZgjwZucASTFBlQTg1CwCxHDNvo5+nm6uwSF6vm6ffc+c9vHW1bvI662rde7M" +
+                        "+c1BBleMHzwt0vPy1fH0vVi6ioUz4oXkEekoCa2MH+Kqas+XaFk8Fxd9Iq46" +
+                        "jeFq9qeij0WMYFdIfLSxdQHa4Qp1BRcDA9BlDWiuYAXiktTiEn3cSjhhSgqy" +
+                        "0/UR/kFXpoeszC2/KDUzPc85J7G4WCU4Pze1JCMzL13FOTEnJykxOVsvGSTR" +
+                        "G+DrfdhBoHZy3ZLO0NsZV2dz2F7hevYg8GOQBocr/9R75oW9t/PKVT3/xs9j" +
+                        "4f/HUD/FonOriEgJf/6zs3vMrc/8Pv5ausFY5scKhYKNj5OmB+wPcHs6vcXr" +
+                        "fYXTCr43c1Vy+qdMvN5dqXI5WjBzHYuyNLet4EqtH5sizsqHPEvqf9D7NsbI" +
+                        "mHNhZWqPD3tUyswzHL2NF6yEPvZckH9qdPrw8Ughvlyt0CSv0PLUd3XZ5ysX" +
+                        "Vmdz/ruhdymCL/RjfPXdUitR/0WdWlkt9+qfC0W16l45pPLQ12Rq4buk+QU/" +
+                        "jjM4PQ9n7XvwwbL7eUNXqsTrM059RzZeq2e0nZ47fWOcmK2JxOx4prRomZ8v" +
+                        "tD97nvx4+cV6yczMkgtaG8+WKjw4484ufUd3k2/FfFAsnq2qKldgZGD4xYic" +
+                        "ltDDXpVw2EOCvBUW5MdqJN+2h/UHvVhYZcDe3zXdwZGjasWXfZ3ed8p2T1R5" +
+                        "+/+TSsUH9h+Lj3iKfFUoYZczzr8/e+bd7/3XzRnELfguON4/2tjt3GSvHbap" +
+                        "LkRNvcX82Amja4tWpTxN8viQtLBUa5PqwZ1Bblevt5x7UuK34fEjR6FdnUaf" +
+                        "yvZ6pVbqB509o6BptPD5ohDfv763bBYq9UyKCiv9suXM4sxAr6mzy278SG/3" +
+                        "djLQvpaqtnzVx6//T50TT1J2qvgQKyPot+2L0hcp5yWtJxfvLlHcPMvgZpLU" +
+                        "f7uKH+tFlCP+1J9V5ItrM3yglZWd8PwN65czna2xbstVbVt6Hk5nqL6R/363" +
+                        "7rcZjGxzInvMzEssih/WM4HCV1RV2JEXGLab8IavJs7wda0oKUpES9aBp/0O" +
+                        "BQjYBnMt3XrCWfBoeM8MBZWkR0GH2zKSnO4k3Ij/0DX1UvatpD+64seOtMo/" +
+                        "cJdc3toh1aP3zub8HOPJv7/9+8T1gCNaTShi24LFGQtSEnoncOXPWVDkobHV" +
+                        "WbKld/fDBYXLal7y7Jmm3fRghqJ63/MWX6edv7jT9ntfiH7lJbQp9/hk8ceB" +
+                        "/r+mL3petPRCWO3Dstb3Lj+3rHDf1HLmpfOL7mkFd+e94Of8tvO5p73a1Dv3" +
+                        "Zq8P/2l2VFpt3dSk6N5vG6yy3nx/OlP45JP9fbJ3uqoTlTun8DJXMku33K3n" +
+                        "kLihEvKc2TpL5adIfsBM2cQ6Mc6XkTstpjdu+MtuXPbjcsk/+1NPFz21vORQ" +
+                        "4zhr7cnIycW9e0NX2zdmnb8gl9xz1OyoxIcvTLencnKZbXKQUX1sp9j+Z8pq" +
+                        "uZcu79VdjZZtUD+r2drmlDN9w4l9Z7LVIlQO/w39lqp2xTKq/ScjKKr4XzX/" +
+                        "CwFGUyMTvqiSxhVVkMjJDdjod9hAoPb6El5ezVzmpcpOBgzMkhKOOoJTJyhJ" +
+                        "dO2yPsiVF8uuqvdo+4wJEj8Y7LgKOXlcV+na36/+/ubzz9e/HwsfYD1mJ9Mi" +
+                        "V2b7plJCcNflJM23k40sFXeYuk3bKBaqqs3/u+Xe2dW6rjpb/Fy3KZSsmxL4" +
+                        "bdaDOXPOceTF2Hg52Qe63Vs7baWOvMuqSM7JSsJVal6hn+0vPjUSTZwTEXnz" +
+                        "ReVE98mLpmiZ+x71brLXDUtVU19Vxn998vPij0pMJQ+F3aT7hdm2ql869ORi" +
+                        "rdX0maoyOVvk1m+t2XLz4/pcmUVH94fxPzrZn3AnTcxUJmP+3uZ7FhmKCW9M" +
+                        "FjXLJX/IPsD3y7fuwoMJtg4nJp5efTa8ak9+Yx24ppi7O7NRABiwK8B5gJFJ" +
+                        "hAG1xoLVZaDqDhWgVH7oWpErIBEUbbY4qj6QCVwMuCsqBDiMqLZwa+FE0fIM" +
+                        "tRpDuBWkDbmY1UPRBiocSKvW0M1GLmJUUczuZCKy2EY3EjkraKIY+ZKZhJIK" +
+                        "3VjkhCCNYuwUVry5KsCblQ2kjAMIzYBuegvmAQBYPJKrOQkAAA=="
+                )
+            ),
+            api = """
+                package test.pkg2 {
+                  public class Foo {
+                    ctor public Foo();
+                    method public test.pkg.ForeignClass getFoo(test.pkg.ForeignClass.ExtraCallback);
+                  }
+                }
+                """
+        )
+    }
+}
\ No newline at end of file
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 c850b24..f958693 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
@@ -16,12 +16,13 @@
 
 package com.android.tools.metalava.model.text
 
+import com.android.tools.metalava.doclava1.ApiFile
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
 class TextTypeItemTest {
     @Test
-    fun testTypeString() {
+    fun `test typeString()`() {
         val full =
             "@android.support.annotation.Nullable java.util.List<@android.support.annotation.Nullable java.lang.String>"
         assertThat(TextTypeItem.toTypeString(full, false, false, false)).isEqualTo(
@@ -60,4 +61,117 @@
             )
         ).isEqualTo("java.util.List[]")
     }
+
+    @Test
+    fun `check erasure`() {
+        // When a type variable is on a member and the type variable is defined on the surrounding
+        // class, look up the bound on the class type parameter:
+        val codebase = ApiFile.parseApi(
+            "test", """
+            package androidx.navigation {
+              public final class NavDestination {
+                ctor public NavDestination();
+              }
+              public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
+                ctor public NavDestinationBuilder(int id);
+                method public D build();
+              }
+            }
+        """, false, false
+        )
+        val cls = codebase.findClass("androidx.navigation.NavDestinationBuilder")
+        val method = cls?.findMethod("build", "") as TextMethodItem
+        assertThat(method).isNotNull()
+        assertThat(
+            TextTypeParameterItem.bounds(
+                "D",
+                method
+            ).toString()
+        ).isEqualTo("[androidx.navigation.NavDestination]")
+
+        assertThat(
+            TextTypeItem.toTypeString(
+                "D[]",
+                false,
+                false,
+                erased = true,
+                context = method
+            )
+        ).isEqualTo("androidx.navigation.NavDestination[]") // it doesn't know any better
+
+        // TODO: Test that in an enum, "T" becomes "java.lang.Enum"; elsewhere it's "java.lang.Object", etc.
+    }
+
+    @Test
+    fun `check erasure from object`() {
+        // When a type variable is on a member and the type variable is defined on the surrounding
+        // class, look up the bound on the class type parameter:
+        val codebase = ApiFile.parseApi(
+            "test", """
+            package test.pkg {
+              public final class TestClass<D> {
+                method public D build();
+              }
+            }
+        """, false, false
+        )
+        val cls = codebase.findClass("test.pkg.TestClass")
+        val method = cls?.findMethod("build", "") as TextMethodItem
+        assertThat(method).isNotNull()
+
+        @Suppress("ConstantConditionIf")
+        if (ASSUME_TYPE_VARS_EXTEND_OBJECT) {
+            assertThat(
+                TextTypeItem.toTypeString(
+                    "D[]",
+                    false,
+                    false,
+                    erased = true,
+                    context = method
+                )
+            ).isEqualTo("java.lang.Object[]")
+        }
+    }
+
+    @Test
+    fun `check erasure from enums`() {
+        // When a type variable is on a member and the type variable is defined on the surrounding
+        // class, look up the bound on the class type parameter:
+        val codebase = ApiFile.parseApi(
+            "test", """
+            package test.pkg {
+              public class EnumMap<K extends java.lang.Enum<K>, V> extends java.util.AbstractMap implements java.lang.Cloneable java.io.Serializable {
+                method public java.util.EnumMap<K, V> clone();
+                method public java.util.Set<java.util.Map.Entry<K, V>> entrySet();
+              }
+            }
+        """, false, false
+        )
+        val cls = codebase.findClass("test.pkg.EnumMap")
+        val method = cls?.findMethod("clone", "") as TextMethodItem
+        assertThat(method).isNotNull()
+
+        assertThat(
+            TextTypeItem.toTypeString(
+                "K",
+                false,
+                false,
+                erased = true,
+                context = method
+            )
+        ).isEqualTo("java.lang.Enum")
+
+        @Suppress("ConstantConditionIf")
+        if (ASSUME_TYPE_VARS_EXTEND_OBJECT) {
+            assertThat(
+                TextTypeItem.toTypeString(
+                    "V",
+                    false,
+                    false,
+                    erased = true,
+                    context = method
+                )
+            ).isEqualTo("java.lang.Object")
+        }
+    }
 }
\ No newline at end of file
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 6b1b17d..31d6e31 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
@@ -16,19 +16,42 @@
 
 package com.android.tools.metalava.model.text
 
-import com.google.common.truth.Truth
+import com.android.tools.metalava.doclava1.ApiFile
+import com.android.tools.metalava.model.text.TextTypeParameterItem.Companion.bounds
+
+import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
 class TextTypeParameterItemTest {
     @Test
     fun testTypeParameterNames() {
-        Truth.assertThat(TextTypeParameterItem.bounds(null).toString()).isEqualTo("[]")
-        Truth.assertThat(TextTypeParameterItem.bounds("").toString()).isEqualTo("[]")
-        Truth.assertThat(TextTypeParameterItem.bounds("X").toString()).isEqualTo("[]")
-        Truth.assertThat(TextTypeParameterItem.bounds("DEF extends T").toString()).isEqualTo("[T]")
-        Truth.assertThat(TextTypeParameterItem.bounds("T extends java.lang.Comparable<? super T>").toString())
+        assertThat(bounds(null).toString()).isEqualTo("[]")
+        assertThat(bounds("").toString()).isEqualTo("[]")
+        assertThat(bounds("X").toString()).isEqualTo("[]")
+        assertThat(bounds("DEF extends T").toString()).isEqualTo("[T]")
+        assertThat(bounds("T extends java.lang.Comparable<? super T>").toString())
             .isEqualTo("[java.lang.Comparable]")
-        Truth.assertThat(TextTypeParameterItem.bounds("T extends java.util.List<Number> & java.util.RandomAccess").toString())
+        assertThat(bounds("T extends java.util.List<Number> & java.util.RandomAccess").toString())
             .isEqualTo("[java.util.List, java.util.RandomAccess]")
+
+        // When a type variable is on a member and the type variable is defined on the surrounding
+        // class, look up the bound on the class type parameter:
+        val codebase = ApiFile.parseApi(
+            "test", """
+            package androidx.navigation {
+              public final class NavDestination {
+                ctor public NavDestination();
+              }
+              public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
+                ctor public NavDestinationBuilder(int id);
+                method public D build();
+              }
+            }
+        """, false, false
+        )
+        val cls = codebase.findClass("androidx.navigation.NavDestinationBuilder")
+        val method = cls?.findMethod("build", "") as TextMethodItem
+        assertThat(method).isNotNull()
+        assertThat(bounds("D", method).toString()).isEqualTo("[androidx.navigation.NavDestination]")
     }
 }
\ No newline at end of file