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));