Merge bb81a6ddda9763480f2918e7f3735709775e0450 on remote branch

Change-Id: Ifa7fb0ea3c54aac31798dc2d573033b15c1abc92
diff --git a/java/android/compat/annotation/Android.bp b/java/android/compat/annotation/Android.bp
index 97c5544..fb88748 100644
--- a/java/android/compat/annotation/Android.bp
+++ b/java/android/compat/annotation/Android.bp
@@ -21,6 +21,7 @@
         "ChangeId.java",
         "Disabled.java",
         "EnabledAfter.java",
+        "LoggingOnly.java",
     ],
     // Allow core_current to use the annotations.
     sdk_version: "current",
@@ -32,6 +33,7 @@
         "ChangeId.java",
         "Disabled.java",
         "EnabledAfter.java",
+        "LoggingOnly.java",
         "UnsupportedAppUsage.java",
     ],
 }
diff --git a/java/android/compat/annotation/LoggingOnly.java b/java/android/compat/annotation/LoggingOnly.java
new file mode 100644
index 0000000..ec6a95f
--- /dev/null
+++ b/java/android/compat/annotation/LoggingOnly.java
@@ -0,0 +1,37 @@
+/*
+ * 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 android.compat.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Used to indicate that a compatibility {@link ChangeId} change is used for logging only purpose.
+ * Toggling this chnge will have no effect.
+ *
+ * <p>This annotation should only be applied to change ID constants that are also annotated with
+ * {@link ChangeId}. In any other context, this annotation will have no effect.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({FIELD})
+public @interface LoggingOnly {
+}
\ No newline at end of file
diff --git a/java/android/processor/compat/changeid/Change.java b/java/android/processor/compat/changeid/Change.java
index 6805e8b..a3ea7b9 100644
--- a/java/android/processor/compat/changeid/Change.java
+++ b/java/android/processor/compat/changeid/Change.java
@@ -26,6 +26,7 @@
     final Long id;
     final String name;
     final boolean disabled;
+    final boolean loggingOnly;
     final Integer enabledAfter;
     final String description;
     /**
@@ -46,12 +47,13 @@
     final String sourcePosition;
 
     @VisibleForTesting
-    public Change(Long id, String name, boolean disabled, Integer enabledAfter,
+    public Change(Long id, String name, boolean disabled, boolean loggingOnly, Integer enabledAfter,
             String description, String javaPackage, String className, String qualifiedClass,
             String sourcePosition) {
         this.id = id;
         this.name = name;
         this.disabled = disabled;
+        this.loggingOnly = loggingOnly;
         this.enabledAfter = enabledAfter;
         this.description = description;
         this.javaPackage = javaPackage;
@@ -64,6 +66,7 @@
         Long id;
         String name;
         boolean disabled;
+        boolean loggingOnly;
         Integer enabledAfter;
         String description;
         String javaPackage;
@@ -89,6 +92,11 @@
             return this;
         }
 
+        public Builder loggingOnly() {
+            this.loggingOnly = true;
+            return this;
+        }
+
         public Builder enabledAfter(int sdkVersion) {
             this.enabledAfter = sdkVersion;
             return this;
@@ -120,7 +128,8 @@
         }
 
         public Change build() {
-            return new Change(id, name, disabled, enabledAfter, description, javaPackage, javaClass,
+            return new Change(id, name, disabled, loggingOnly, enabledAfter, description,
+                    javaPackage, javaClass,
                     qualifiedClass, sourcePosition);
         }
 
diff --git a/java/android/processor/compat/changeid/ChangeIdProcessor.java b/java/android/processor/compat/changeid/ChangeIdProcessor.java
index c330c35..fd43183 100644
--- a/java/android/processor/compat/changeid/ChangeIdProcessor.java
+++ b/java/android/processor/compat/changeid/ChangeIdProcessor.java
@@ -49,6 +49,7 @@
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.QualifiedNameable;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.TypeKind;
@@ -73,6 +74,7 @@
             "android.compat.annotation.ChangeId";
 
     private static final String DISABLED_CLASS_NAME = "android.compat.annotation.Disabled";
+    private static final String LOGGING_CLASS_NAME = "android.compat.annotation.LoggingOnly";
     private static final String ENABLED_AFTER_CLASS_NAME = "android.compat.annotation.EnabledAfter";
     private static final String TARGET_SDK_VERSION = "targetSdkVersion";
 
@@ -232,31 +234,6 @@
         return true;
     }
 
-    private static <E extends Element> E getEnclosingElementByKind(Element element, ElementKind kind) {
-        while (element != null && element.getKind() != kind) {
-            element = element.getEnclosingElement();
-        }
-        return (E) element;
-    }
-
-    private String getQualifiedClass(Element element){
-        TypeElement t = getEnclosingElementByKind(element, ElementKind.CLASS);
-        return t.getQualifiedName().toString();
-    }
-
-    /**
-     * Returns the qualified name of a class within its package. For a regular class, this will be
-     * "ClassName"; for an inner class it will be "ClassName.Inner".
-     */
-    private String getClassName(TypeElement t) {
-        List<String> classes = new ArrayList<>();
-        while (t != null) {
-            classes.add(t.getSimpleName().toString());
-            t = getEnclosingElementByKind(t.getEnclosingElement(), ElementKind.CLASS);
-        }
-        return Joiner.on(".").join(Lists.reverse(classes));
-    }
-
     private String getSourcePosition(Element e, AnnotationMirror a) {
         JavacElements javacElem = (JavacElements) processingEnv.getElementUtils();
         Pair<JCTree, JCTree.JCCompilationUnit> pair = javacElem.getTreeAndTopLevel(e, a, null);
@@ -276,6 +253,8 @@
                     ((TypeElement) m.getAnnotationType().asElement()).getQualifiedName().toString();
             if (type.equals(DISABLED_CLASS_NAME)) {
                 builder.disabled();
+            } else if (type.equals(LOGGING_CLASS_NAME)) {
+                builder.loggingOnly();
             } else if (type.equals(ENABLED_AFTER_CLASS_NAME)) {
                 for (Map.Entry<?, ?> entry : m.getElementValues().entrySet()) {
                     String key = ((ExecutableElement) entry.getKey()).getSimpleName().toString();
@@ -294,15 +273,18 @@
             comment = JAVADOC_SANITIZER.matcher(comment).replaceAll("");
             builder.description(comment.replaceAll("\\n"," ").trim());
         }
-        TypeElement cls = getEnclosingElementByKind(e, ElementKind.CLASS);
-        PackageElement pkg = getEnclosingElementByKind(cls, ElementKind.PACKAGE);
-        Change change = builder.javaClass(getClassName(cls))
-                .javaPackage(pkg.getQualifiedName().toString())
-                .qualifedClass(cls.getQualifiedName().toString())
+
+        // TODO(satayev): move common processors code to android.processor.compat.
+        String packageName = processingEnv.getElementUtils().getPackageOf(e).toString();
+        String enclosingElementName = ((QualifiedNameable) e.getEnclosingElement()).getQualifiedName().toString();
+        String className = enclosingElementName.substring(packageName.length() + 1);
+
+        Change change = builder.javaClass(className)
+                .javaPackage(packageName)
+                .qualifedClass(enclosingElementName)
                 .sourcePosition(getSourcePosition(e, changeId))
                 .build();
 
-
         if (change.disabled && change.enabledAfter != null) {
             messager.printMessage(
                     ERROR,
@@ -310,6 +292,15 @@
                     e);
         }
 
+        if (change.loggingOnly && (change.disabled || change.enabledAfter != null)) {
+            messager.printMessage(
+                    ERROR,
+                    "ChangeId cannot be annotated with both @LoggingOnly and @EnabledAfter or "
+                            + "@Disabled.",
+                    e);
+        }
+
         return change;
     }
+
 }
diff --git a/java/android/processor/compat/changeid/XmlWriter.java b/java/android/processor/compat/changeid/XmlWriter.java
index 72346b4..484c0ba 100644
--- a/java/android/processor/compat/changeid/XmlWriter.java
+++ b/java/android/processor/compat/changeid/XmlWriter.java
@@ -41,7 +41,7 @@
  *     <compat-change id="111" name="change-name1">
  *         <meta-data definedIn="java.package.ClassName" sourcePosition="java/package/ClassName.java:10" />
  *     </compat-change>
- *     <compat-change disabled="true" id="222" name="change-name2" description="my change">
+ *     <compat-change disabled="true" id="222" loggingOnly= "true" name="change-name2" description="my change">
  *         <meta-data .../>
  *     </compat-change>
  *     <compat-change enableAfterTargetSdk="28" id="333" name="change-name3">
@@ -64,6 +64,7 @@
     private static final String XML_NAME_ATTR = "name";
     private static final String XML_ID_ATTR = "id";
     private static final String XML_DISABLED_ATTR = "disabled";
+    private static final String XML_LOGGING_ATTR = "loggingOnly";
     private static final String XML_ENABLED_AFTER_ATTR = "enableAfterTargetSdk";
     private static final String XML_DESCRIPTION_ATTR = "description";
     private static final String XML_METADATA_ELEMENT = "meta-data";
@@ -88,6 +89,9 @@
         if (change.disabled) {
             newElement.setAttribute(XML_DISABLED_ATTR, "true");
         }
+        if (change.loggingOnly) {
+            newElement.setAttribute(XML_LOGGING_ATTR, "true");
+        }
         if (change.enabledAfter != null) {
             newElement.setAttribute(XML_ENABLED_AFTER_ATTR, change.enabledAfter.toString());
         }
diff --git a/javatest/android/processor/compat/changeid/ChangeIdProcessorTest.java b/javatest/android/processor/compat/changeid/ChangeIdProcessorTest.java
index a0ad192..a94a874 100644
--- a/javatest/android/processor/compat/changeid/ChangeIdProcessorTest.java
+++ b/javatest/android/processor/compat/changeid/ChangeIdProcessorTest.java
@@ -58,6 +58,16 @@
                     "@Target({FIELD})",
                     "public @interface Disabled {",
                     "}"),
+            JavaFileObjects.forSourceLines("android.compat.annotation.LoggingOnly",
+                    "package android.compat.annotation;",
+                    "import static java.lang.annotation.ElementType.FIELD;",
+                    "import static java.lang.annotation.RetentionPolicy.SOURCE;",
+                    "import java.lang.annotation.Retention;",
+                    "import java.lang.annotation.Target;",
+                    "@Retention(SOURCE)",
+                    "@Target({FIELD})",
+                    "public @interface LoggingOnly {",
+                    "}"),
             JavaFileObjects.forSourceLines("android.compat.annotation.EnabledAfter",
                     "package android.compat.annotation;",
                     "import static java.lang.annotation.ElementType.FIELD;",
@@ -201,6 +211,71 @@
     }
 
     @Test
+    public void testCompatConfigXmlOutput_interface() {
+        JavaFileObject[] source = {
+                JavaFileObjects.forSourceLines(
+                        "libcore.util.Compat",
+                        "package libcore.util;",
+                        "import android.compat.annotation.ChangeId;",
+                        "import android.compat.annotation.EnabledAfter;",
+                        "import android.compat.annotation.Disabled;",
+                        "public interface Compat {",
+                        "    /**",
+                        "     * description of",
+                        "     * MY_CHANGE_ID",
+                        "     */",
+                        "    @ChangeId",
+                        "    static final long MY_CHANGE_ID = 123456789l;",
+                        "}"),
+        };
+        String expectedFile = HEADER + "<config>" +
+                "<compat-change description=\"description of MY_CHANGE_ID\" "
+                + "id=\"123456789\" name=\"MY_CHANGE_ID\"><meta-data definedIn=\"libcore.util"
+                + ".Compat\" sourcePosition=\"libcore/util/Compat.java:10\"/>"
+                + "</compat-change></config>";
+        Compilation compilation =
+                Compiler.javac()
+                        .withProcessors(new ChangeIdProcessor())
+                        .compile(ObjectArrays.concat(mAnnotations,source, JavaFileObject.class));
+        CompilationSubject.assertThat(compilation).succeeded();
+        CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util",
+                "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile);
+    }
+
+    @Test
+    public void testCompatConfigXmlOutput_enum() {
+        JavaFileObject[] source = {
+                JavaFileObjects.forSourceLines(
+                        "libcore.util.Compat",
+                        "package libcore.util;",
+                        "import android.compat.annotation.ChangeId;",
+                        "import android.compat.annotation.EnabledAfter;",
+                        "import android.compat.annotation.Disabled;",
+                        "public enum Compat {",
+                        "    ENUM_CONSTANT;",
+                        "    /**",
+                        "     * description of",
+                        "     * MY_CHANGE_ID",
+                        "     */",
+                        "    @ChangeId",
+                        "    private static final long MY_CHANGE_ID = 123456789l;",
+                        "}"),
+        };
+        String expectedFile = HEADER + "<config>" +
+                "<compat-change description=\"description of MY_CHANGE_ID\" "
+                + "id=\"123456789\" name=\"MY_CHANGE_ID\"><meta-data definedIn=\"libcore.util"
+                + ".Compat\" sourcePosition=\"libcore/util/Compat.java:11\"/>"
+                + "</compat-change></config>";
+        Compilation compilation =
+                Compiler.javac()
+                        .withProcessors(new ChangeIdProcessor())
+                        .compile(ObjectArrays.concat(mAnnotations,source, JavaFileObject.class));
+        CompilationSubject.assertThat(compilation).succeeded();
+        CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util",
+                "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile);
+    }
+
+    @Test
     public void testBothDisabledAndEnabledAfter() {
         JavaFileObject[] source = {
                 JavaFileObjects.forSourceLines(
@@ -216,10 +291,6 @@
                         "    static final long MY_CHANGE_ID = 123456789l;",
                         "}")
         };
-        String expectedFile = HEADER + "<config>" +
-                "<compat-change disabled=\"true\" enableAfterTargetSdk=\"29\" id=\"123456789\" "
-                + "name=\"MY_CHANGE_ID\"/>" +
-                "</config>";
         Compilation compilation =
                 Compiler.javac()
                         .withProcessors(new ChangeIdProcessor())
@@ -228,6 +299,84 @@
                 "ChangeId cannot be annotated with both @Disabled and @EnabledAfter.");
     }
 
+
+    @Test
+    public void testBothLoggingOnlyAndEnabledAfter() {
+        JavaFileObject[] source = {
+                JavaFileObjects.forSourceLines(
+                        "libcore.util.Compat",
+                        "package libcore.util;",
+                        "import android.compat.annotation.ChangeId;",
+                        "import android.compat.annotation.EnabledAfter;",
+                        "import android.compat.annotation.LoggingOnly;",
+                        "public class Compat {",
+                        "    @EnabledAfter(targetSdkVersion=29)",
+                        "    @LoggingOnly",
+                        "    @ChangeId",
+                        "    static final long MY_CHANGE_ID = 123456789l;",
+                        "}")
+        };
+        Compilation compilation =
+                Compiler.javac()
+                        .withProcessors(new ChangeIdProcessor())
+                        .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
+        CompilationSubject.assertThat(compilation).hadErrorContaining(
+                "ChangeId cannot be annotated with both @LoggingOnly and @EnabledAfter or "
+                        + "@Disabled.");
+    }
+
+    @Test
+    public void testBothLoggingOnlyAndDisabled() {
+        JavaFileObject[] source = {
+                JavaFileObjects.forSourceLines(
+                        "libcore.util.Compat",
+                        "package libcore.util;",
+                        "import android.compat.annotation.ChangeId;",
+                        "import android.compat.annotation.LoggingOnly;",
+                        "import android.compat.annotation.Disabled;",
+                        "public class Compat {",
+                        "    @LoggingOnly",
+                        "    @Disabled",
+                        "    @ChangeId",
+                        "    static final long MY_CHANGE_ID = 123456789l;",
+                        "}")
+        };
+        Compilation compilation =
+                Compiler.javac()
+                        .withProcessors(new ChangeIdProcessor())
+                        .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
+        CompilationSubject.assertThat(compilation).hadErrorContaining(
+                "ChangeId cannot be annotated with both @LoggingOnly and @EnabledAfter or "
+                        + "@Disabled.");
+    }
+
+    @Test
+    public void testLoggingOnly() {
+        JavaFileObject[] source = {
+                JavaFileObjects.forSourceLines(
+                        "libcore.util.Compat",
+                        "package libcore.util;",
+                        "import android.compat.annotation.ChangeId;",
+                        "import android.compat.annotation.LoggingOnly;",
+                        "public class Compat {",
+                        "    @LoggingOnly",
+                        "    @ChangeId",
+                        "    static final long MY_CHANGE_ID = 123456789l;",
+                        "}")
+        };
+        String expectedFile = HEADER + "<config>" +
+                "<compat-change id=\"123456789\" loggingOnly=\"true\" name=\"MY_CHANGE_ID\">" +
+                "<meta-data definedIn=\"libcore.util.Compat\" " +
+                "sourcePosition=\"libcore/util/Compat.java:6\"/>" +
+                "</compat-change></config>";
+        Compilation compilation =
+                Compiler.javac()
+                        .withProcessors(new ChangeIdProcessor())
+                        .compile(ObjectArrays.concat(mAnnotations, source, JavaFileObject.class));
+        CompilationSubject.assertThat(compilation).succeeded();
+        CompilationSubject.assertThat(compilation).generatedFile(CLASS_OUTPUT, "libcore.util",
+                "Compat_compat_config.xml").contentsAsString(UTF_8).isEqualTo(expectedFile);
+    }
     @Test
     public void testIgnoredParams() {
         JavaFileObject[] source = {
diff --git a/javatest/android/processor/compat/changeid/XmlWriterTest.java b/javatest/android/processor/compat/changeid/XmlWriterTest.java
index f04d037..a6843d9 100644
--- a/javatest/android/processor/compat/changeid/XmlWriterTest.java
+++ b/javatest/android/processor/compat/changeid/XmlWriterTest.java
@@ -84,11 +84,17 @@
                 .disabled()
                 .enabledAfter(29)
                 .build();
+        Change loggingOnly = new Change.Builder()
+                .id(555L)
+                .name("change-name5")
+                .loggingOnly()
+                .build();
 
         writer.addChange(c);
         writer.addChange(disabled);
         writer.addChange(sdkRestricted);
         writer.addChange(both);
+        writer.addChange(loggingOnly);
         writer.write(mOutputStream);
 
         String expected = HEADER + "<config>"
@@ -98,6 +104,7 @@
                 + "name=\"change-name3\"/>"
                 + "<compat-change disabled=\"true\" enableAfterTargetSdk=\"29\" id=\"444\" "
                 + "name=\"change-name4\"/>"
+                + "<compat-change id=\"555\" loggingOnly=\"true\" name=\"change-name5\"/>"
                 + "</config>";
 
         assertThat(mOutputStream.toString(), startsWith(expected));