Merge "Snap for 6982797 from 513926fc07f451c58e0c346a9c214018eca858fd to androidx-transition-release" into androidx-transition-release
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+  license_type: NOTICE
+}
diff --git a/build.gradle b/build.gradle
index 3096e0e..0a0ce4d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,76 +16,42 @@
 
 import javax.tools.ToolProvider
 
-apply plugin: 'java'
-apply plugin: 'maven'
+plugins {
+    id("java")
+    id("maven-publish")
+}
 
 group = 'com.android'
 version = '1.0.6'
 
-def currentJvmVersion = org.gradle.api.JavaVersion.current()
-if (currentJvmVersion.getMajorVersion() != "8") {
-  throw new Exception("Unsupported java version '" + currentJvmVersion.toString() + "'. Please install java 8.\n" +
-"\n" +
-"If you have already installed java 8, you can instruct Gradle to use it by setting the environment variable JAVA_HOME equal to its file path.")
-}
-
-/*
- * With the build server you are given two env variables:
- * 1. The OUT_DIR is a temporary directory you can use to put things during the build.
- * 2. The DIST_DIR is where you want to save things from the build.
- *
- * The build server will copy the contents of DIST_DIR to somewhere and make it available.
- */
-if (System.env.DIST_DIR != null && System.env.OUT_DIR != null) {
-    buildDir = file("${System.env.OUT_DIR}/gradle/external/jdiff/build").getCanonicalFile()
-    ext.distDir = file(System.env.DIST_DIR).getCanonicalFile()
-
-    // The distDir is conveniently named after the build ID.
-    version = "${version}.${ext.distDir.name}"
+if (System.env.OUT_DIR != null) {
+    buildDir = file("${System.env.OUT_DIR}/gradle/external/doclava/build").getCanonicalFile()
 } else {
-    buildDir = file('../../out/host/gradle/external/jdiff/build')
-    ext.distDir = file('../../out/dist')
-
-    // Local builds are not public release candidates.
-    version = "${version}-SNAPSHOT"
+    buildDir = file('../../out/host/gradle/external/doclava/build')
 }
 
-/*
- * If prebuilts are available, use them. Else, if this is unbundled build use jcenter().
- * Finally, if none of that is true, attempt to compile against the full source trees.
- */
-File m2repo = file('../../prebuilts/androidx/external')
-boolean unbundleBuild = (new File("unbundled-build")).exists()
-
-if (m2repo.exists() || unbundleBuild) {
-    repositories {
-        maven { url m2repo.absolutePath }
-        if (unbundleBuild) {
-            jcenter()
-        }
-    }
-
-    dependencies {
-        compile 'org.antlr:antlr:3.5.2'
-        compile 'com.google.jsilver:jsilver:1.0.0'
-        compile 'org.ccil.cowan.tagsoup:tagsoup:1.2.1'
-        // Transitive dependency required by jsilver.
-        compile 'com.google.guava:guava:15.0'
-    }
-} else {
-    dependencies {
-        compile project(path: ':antlr', configuration: 'antlrRuntime')
-        compile project(':jsilver')
-        compile project(':tagsoup')
-    }
+repositories {
+    maven { url file('../../prebuilts/androidx/external').absolutePath }
 }
 
-
 dependencies {
-    testCompile 'junit:junit:4.12'
+    implementation("org.antlr:antlr:3.5.2")
+    implementation("com.google.jsilver:jsilver:1.0.0")
+    implementation("org.ccil.cowan.tagsoup:tagsoup:1.2.1")
 
     // tools.jar required for com.sun.javadoc
-    compile files(((URLClassLoader) ToolProvider.getSystemToolClassLoader()).getURLs())
+    def toolsJar
+    if (JavaVersion.current().getMajorVersion() == "8") {
+        toolsJar = ((URLClassLoader) ToolProvider.getSystemToolClassLoader()).getURLs()
+    } else if (System.env.JAVA_TOOLS_JAR != null) {
+        toolsJar = System.env.JAVA_TOOLS_JAR
+    } else {
+        throw new Exception("If you are not using Java 8, JAVA_TOOLS_JAR env variable " +
+                "needs to be set to build Doclava")
+    }
+    implementation(files(toolsJar))
+
+    testImplementation("junit:junit:4.12")
 }
 
 sourceSets {
@@ -99,22 +65,12 @@
     }
 }
 
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: uri("${buildDir}/repo"))
-        }
-    }
+tasks.withType(JavaCompile) {
+    // Suppress build warnings that we're not interested in: b/154755010
+    options.warnings = false
 }
 
-task dist(type: Zip, dependsOn: uploadArchives)  {
-    group = BasePlugin.BUILD_GROUP
-    description 'Builds distribution artifacts.'
-
-    from uploadArchives.artifacts
-    destinationDir distDir
-
-    doLast {
-        logger.lifecycle "Compressed maven artifacts to ${archivePath}"
-    }
+tasks.withType(Jar) { task ->
+    task.reproducibleFileOrder = true
+    task.preserveFileTimestamps = false
 }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 84939b4..92352a0 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-3.3-bin.zip
+distributionUrl=../../../../tools/external/gradle/gradle-6.5.1-bin.zip
diff --git a/res/assets/templates-sdk/compatchanges.cs b/res/assets/templates-sdk/compatchanges.cs
new file mode 100644
index 0000000..001b937
--- /dev/null
+++ b/res/assets/templates-sdk/compatchanges.cs
@@ -0,0 +1,47 @@
+<?cs # THIS CREATES THE COMPAT CONFIG DOCS FROM compatconfig.xml ?>
+<?cs include:"macros.cs" ?>
+<?cs include:"macros_override.cs" ?>
+
+<?cs include:"doctype.cs" ?>
+<html<?cs if:devsite ?> devsite<?cs /if ?>>
+<?cs include:"head_tag.cs" ?>
+<?cs include:"body_tag.cs" ?>
+<div itemscope itemtype="http://developers.google.com/ReferenceObject">
+<?cs include:"header.cs" ?>
+<?cs # Includes api-info-block DIV at top of page. Standard Devsite uses right nav. ?>
+<?cs if:dac ?><?cs include:"page_info.cs" ?><?cs /if ?>
+<?cs # This DIV spans the entire document to provide scope for some scripts ?>
+<div id="jd-content" >
+
+<?cs each:change=change ?>
+  <h3 class="api-name" id="<?cs var:change.name ?>"><?cs var:change.name ?></h3>
+  <div>Value: <?cs var:change.id ?></div>
+  <div>
+  <?cs if:change.loggingOnly ?>
+        Used for logging only.
+  <?cs else ?>
+        <?cs if:change.disabled ?>
+            Disabled for all apps.
+        <?cs else ?>
+            Enabled for
+            <?cs if:change.enableAfterTargetSdk ?>
+                apps with a <code>targetSdkVersion</code> of greater than
+                <?cs var:change.enableAfterTargetSdk ?>.
+            <?cs else ?>
+                all apps.
+            <?cs /if ?>
+        <?cs /if ?>
+  <?cs /if ?>
+  </div>
+
+  <?cs call:description(change) ?>
+<?cs /each ?>
+
+</div>
+<?cs if:!devsite ?>
+<?cs include:"footer.cs" ?>
+<?cs include:"trailer.cs" ?>
+<?cs /if ?>
+</div><!-- end devsite ReferenceObject -->
+</body>
+</html>
diff --git a/src/com/google/doclava/CompatInfo.java b/src/com/google/doclava/CompatInfo.java
new file mode 100644
index 0000000..167f6ab
--- /dev/null
+++ b/src/com/google/doclava/CompatInfo.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2020 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.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+public class CompatInfo {
+
+  public static class CompatChange {
+    public final String name;
+    public final long id;
+    public final String description;
+    public final String definedInClass;
+    public final String sourceFile;
+    public final int sourceLine;
+    public final boolean disabled;
+    public final boolean loggingOnly;
+    public final int enableAfterTargetSdk;
+
+
+    CompatChange(String name, long id, String description, String definedInClass,
+            String sourceFile, int sourceLine, boolean disabled, boolean loggingOnly,
+            int enableAfterTargetSdk) {
+      this.name = name;
+      this.id = id;
+      this.description = description;
+      this.definedInClass = definedInClass;
+      this.sourceFile = sourceFile;
+      this.sourceLine = sourceLine;
+      this.disabled = disabled;
+      this.loggingOnly = loggingOnly;
+      this.enableAfterTargetSdk = enableAfterTargetSdk;
+    }
+
+    static class Builder {
+      private String mName;
+      private long mId;
+      private String mDescription;
+      private String mDefinedInClass;
+      private String mSourceFile;
+      private int mSourceLine;
+      private boolean mDisabled;
+      private boolean mLoggingOnly;
+      private int mEnableAfterTargetSdk;
+
+      CompatChange build() {
+        return new CompatChange(
+            mName, mId, mDescription, mDefinedInClass, mSourceFile, mSourceLine,
+                mDisabled, mLoggingOnly, mEnableAfterTargetSdk);
+      }
+
+      Builder name(String name) {
+        mName = name;
+        return this;
+      }
+
+      Builder id(long id) {
+        mId = id;
+        return this;
+      }
+
+      Builder description(String description) {
+        mDescription = description;
+        return this;
+      }
+
+      Builder definedInClass(String definedInClass) {
+        mDefinedInClass = definedInClass;
+        return this;
+      }
+
+      Builder sourcePosition(String sourcePosition) throws SAXException {
+        if (sourcePosition != null) {
+          int colonPos = sourcePosition.indexOf(":");
+          if (colonPos == -1) {
+            throw new SAXException("Invalid source position: " + sourcePosition);
+          }
+          mSourceFile = sourcePosition.substring(0, colonPos);
+          try {
+            mSourceLine = Integer.parseInt(sourcePosition.substring(colonPos + 1));
+          } catch (NumberFormatException nfe) {
+            throw new SAXException("Invalid source position: " + sourcePosition, nfe);
+          }
+        }
+        return this;
+      }
+
+      boolean parseBool(String value) {
+        if (value == null) {
+          return false;
+        }
+        boolean result = Boolean.parseBoolean(value);
+        return result;
+      }
+
+      Builder disabled(String disabled) {
+        mDisabled = parseBool(disabled);
+        return this;
+      }
+
+      Builder loggingOnly(String loggingOnly) {
+        mLoggingOnly = parseBool(loggingOnly);
+        return this;
+      }
+
+      Builder enableAfterTargetSdk(String enableAfter) throws SAXException {
+        if (enableAfter == null) {
+          mEnableAfterTargetSdk = 0;
+        } else {
+          try {
+            mEnableAfterTargetSdk = Integer.parseInt(enableAfter);
+          } catch (NumberFormatException nfe) {
+            throw new SAXException("Invalid SDK version int: " + enableAfter, nfe);
+          }
+        }
+        return this;
+      }
+    }
+
+  }
+
+  private class CompatConfigXmlParser extends DefaultHandler {
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes attributes)
+        throws SAXException {
+      if (qName.equals("compat-change")) {
+        mCurrentChange = new CompatChange.Builder();
+        String idStr = attributes.getValue("id");
+        if (idStr == null) {
+          throw new SAXException("<compat-change> element has no id");
+        }
+        try {
+          mCurrentChange.id(Long.parseLong(idStr));
+        } catch (NumberFormatException nfe) {
+          throw new SAXException("<compat-change> id is not a valid long", nfe);
+        }
+        mCurrentChange.name(attributes.getValue("name"))
+                .description(attributes.getValue("description"))
+                .enableAfterTargetSdk(attributes.getValue("enableAfterTargetSdk"))
+                .disabled(attributes.getValue("disabled"))
+                .loggingOnly(attributes.getValue("loggingOnly"));
+
+      } else if (qName.equals("meta-data")) {
+        if (mCurrentChange == null) {
+          throw new SAXException("<meta-data> tag with no enclosing <compat-change>");
+        }
+        mCurrentChange.definedInClass(attributes.getValue("definedIn"))
+            .sourcePosition(attributes.getValue("sourcePosition"));
+      }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) {
+      if (qName.equals("compat-change")) {
+        mChanges.add(mCurrentChange.build());
+        mCurrentChange = null;
+      }
+    }
+  }
+
+  public static CompatInfo readCompatConfig(String source) {
+    CompatInfo config = new CompatInfo();
+    try {
+      InputStream in = new FileInputStream(new File(source));
+
+      XMLReader xmlreader = XMLReaderFactory.createXMLReader();
+      xmlreader.setContentHandler(config.mXmlParser);
+      xmlreader.setErrorHandler(config.mXmlParser);
+      xmlreader.parse(new InputSource(in));
+      in.close();
+      return config;
+    } catch (SAXException e) {
+      throw new RuntimeException("Failed to parse " + source, e);
+    } catch (IOException e) {
+      throw new RuntimeException("Failed to read " + source, e);
+    }
+  }
+
+  private final CompatConfigXmlParser mXmlParser = new CompatConfigXmlParser();
+  private CompatChange.Builder mCurrentChange;
+  private List<CompatChange> mChanges = new ArrayList<>();
+
+  public List<CompatChange> getChanges() {
+    return mChanges;
+  }
+
+  public void makeHDF(Data hdf) {
+    // We construct a Comment for each compat change to re-use the default docs generation support
+    // for comments.
+    mChanges.sort(Comparator.comparing(a -> a.name));
+    for (int i = 0; i < mChanges.size(); ++i) {
+      CompatInfo.CompatChange change = mChanges.get(i);
+      // we will get null ClassInfo here if the defining class is not in the SDK.
+      ContainerInfo definedInContainer = Converter.obtainClass(change.definedInClass);
+      if (definedInContainer == null) {
+        // This happens when the class defining the @ChangeId constant is not included in
+        // the sources that the SDK docs are generated from. Using package "android" as the
+        // container works, but means we lose the context of the original javadoc comment.
+        // This means that if the javadoc comment refers to classes imported by it's
+        // containing source file, we cannot resolve those imports here.
+        // TODO see if we could somehow plumb the import list from the original source file,
+        // via compat_config.xml, so we can resolve links properly here?
+        definedInContainer = Converter.obtainPackage("android");
+      }
+      if (change.description == null) {
+        throw new RuntimeException("No desriprion found for @ChangeId " + change.name);
+      }
+      Comment comment = new Comment(change.description, definedInContainer, new SourcePositionInfo(
+          change.sourceFile, change.sourceLine, 1));
+      String path = "change." + i;
+      hdf.setValue(path + ".id", Long.toString(change.id));
+      hdf.setValue(path + ".name", change.name);
+      if (change.enableAfterTargetSdk != 0) {
+        hdf.setValue(path + ".enableAfterTargetSdk",
+            Integer.toString(change.enableAfterTargetSdk));
+      }
+      if (change.loggingOnly) {
+        hdf.setValue(path + ".loggingOnly", Boolean.toString(true));
+      }
+      if (change.disabled) {
+        hdf.setValue(path + ".disabled", Boolean.toString(true));
+      }
+      TagInfo.makeHDF(hdf, path + ".descr", comment.tags());
+    }
+  }
+}
diff --git a/src/com/google/doclava/Converter.java b/src/com/google/doclava/Converter.java
index a6f803b..cf14237 100644
--- a/src/com/google/doclava/Converter.java
+++ b/src/com/google/doclava/Converter.java
@@ -501,23 +501,9 @@
         return result;
       } else {
         ConstructorDoc m = (ConstructorDoc) o;
-        // Workaround for a JavaDoc behavior change introduced in OpenJDK 8 that breaks
-        // links in documentation and the content of API files like current.txt.
-        // http://b/18051133.
-        String name = m.name();
-        ClassDoc containingClass = m.containingClass();
-        if (containingClass.containingClass() != null) {
-          // This should detect the new behavior and be bypassed otherwise.
-          if (!name.contains(".")) {
-            // Constructors of inner classes do not contain the name of the enclosing class
-            // with OpenJDK 8. This simulates the old behavior:
-            name = containingClass.name();
-          }
-        }
-        // End of workaround.
         MethodInfo result =
             new MethodInfo(m.getRawCommentText(), new ArrayList<TypeInfo>(Arrays.asList(Converter.convertTypes(m.typeParameters()))),
-                name, m.signature(), Converter.obtainClass(m.containingClass()), Converter
+                m.name(), m.signature(), Converter.obtainClass(m.containingClass()), Converter
                 .obtainClass(m.containingClass()), m.isPublic(), m.isProtected(), m
                 .isPackagePrivate(), m.isPrivate(), m.isFinal(), m.isStatic(), m.isSynthetic(),
                 false, m.isSynchronized(), m.isNative(), false/*isDefault*/, false, "constructor", m.flatSignature(),
diff --git a/src/com/google/doclava/Doclava.java b/src/com/google/doclava/Doclava.java
index f24718a..049e93e 100644
--- a/src/com/google/doclava/Doclava.java
+++ b/src/com/google/doclava/Doclava.java
@@ -127,6 +127,7 @@
   public static Linter linter = new EmptyLinter();
   public static boolean android = false;
   public static String manifestFile = null;
+  public static String compatConfig = null;
   public static Map<String, String> manifestPermissions = new HashMap<>();
 
   public static JSilver jSilver = null;
@@ -403,6 +404,8 @@
         android = true;
       } else if (a[0].equals("-manifest")) {
         manifestFile = a[1];
+      } else if (a[0].equals("-compatconfig")) {
+        compatConfig = a[1];
       }
     }
 
@@ -538,6 +541,7 @@
         writeClassLists();
         writeClasses();
         writeHierarchy();
+        writeCompatConfig();
         // writeKeywords();
 
         // Write yaml tree.
@@ -962,6 +966,9 @@
     if (option.equals("-manifest")) {
       return 2;
     }
+    if (option.equals("-compatconfig")) {
+      return 2;
+    }
     return 0;
   }
   public static boolean validOptions(String[][] options, DocErrorReporter r) {
@@ -2171,8 +2178,8 @@
   public static String getDocumentationStringForAnnotation(String annotationName) {
     if (!documentAnnotations) return null;
     if (annotationDocumentationMap == null) {
-      // parse the file for map
       annotationDocumentationMap = new HashMap<String, String>();
+      // parse the file for map
       try {
         BufferedReader in = new BufferedReader(
             new FileReader(documentAnnotationsPath));
@@ -2195,4 +2202,17 @@
     return annotationDocumentationMap.get(annotationName);
   }
 
+  public static void writeCompatConfig() {
+    if (compatConfig == null) {
+      return;
+    }
+    CompatInfo config = CompatInfo.readCompatConfig(compatConfig);
+    Data data = makeHDF();
+    config.makeHDF(data);
+    setPageTitle(data, "Compatibility changes");
+    // TODO - should we write the output to some other path?
+    String outfile = "compatchanges.html";
+    ClearPage.write(data, "compatchanges.cs", outfile);
+  }
+
 }
diff --git a/src/com/google/doclava/SeeTagInfo.java b/src/com/google/doclava/SeeTagInfo.java
index eddc595..fee8ce2 100644
--- a/src/com/google/doclava/SeeTagInfo.java
+++ b/src/com/google/doclava/SeeTagInfo.java
@@ -35,16 +35,8 @@
 
   protected LinkReference linkReference() {
     if (mLink == null) {
-      // If this is a @see reference in frameworks/base, suppress errors about broken references.
-      // Outside of frameworks/base, and the generated android/R file, all such
-      // errors have been fixed, see b/80570421.
-      boolean suppressableSeeReference =
-          "@see".equals(name()) &&
-              (position().file.contains("frameworks/base/")
-                  || position().file.endsWith("android/R.java"));
-      mLink =
-          LinkReference.parse(text(), mBase, position(), !suppressableSeeReference
-              && (mBase != null ? mBase.checkLevel() : true));
+      mLink =  LinkReference.parse(text(), mBase, position(),
+          mBase != null ? mBase.checkLevel() : true);
     }
     return mLink;
   }