Fix UnsupportedAppUsage property serialization

The publicAlternatives field of the UnsupportedAppUsage annotation may
contain commas, which would interfere with the csv serialization into
metadata.csv. This change ensures that all fields containing commas are
encased by '|' characters (due to it being used as a quote character in
frameworks/base/tools/hiddenapi/merge_csv.py), and all other ocurrences
of '|' are escaped.

Bug: 130721457
Test: atest AnnotationPropertyWriterTest
Change-Id: I5bc0c68b460ea23b9408ecc4c997938d310005fe
diff --git a/tools/class2greylist/src/com/android/class2greylist/AnnotationPropertyWriter.java b/tools/class2greylist/src/com/android/class2greylist/AnnotationPropertyWriter.java
index aacd963..6656d3f 100644
--- a/tools/class2greylist/src/com/android/class2greylist/AnnotationPropertyWriter.java
+++ b/tools/class2greylist/src/com/android/class2greylist/AnnotationPropertyWriter.java
@@ -3,6 +3,7 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.OutputStream;
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Comparator;
@@ -25,6 +26,12 @@
         mColumns = new HashSet<>();
     }
 
+    public AnnotationPropertyWriter(OutputStream output) {
+        mOutput = new PrintStream(output);
+        mContents = new ArrayList<>();
+        mColumns = new HashSet<>();
+    }
+
     public void consume(String apiSignature, Map<String, String> annotationProperties,
             Set<String> parsedFlags) {
         // Clone properties map.
@@ -38,6 +45,13 @@
         mContents.add(contents);
     }
 
+    private static String escapeCsvColumn(String column) {
+        // Using '|' as a quote character, as in frameworks/base/tools/hiddenapi/merge_csv.py
+        // Escape '|' characters in the column, then wrap the column in '|' characters.
+        column = column.replace("|", "||");
+        return "|" + column + "|";
+    }
+
     public void close() {
         // Sort columns by name and print header row.
         List<String> columns = new ArrayList<>(mColumns);
@@ -47,6 +61,7 @@
         // Sort contents according to columns and print.
         for (Map<String, String> row : mContents) {
             mOutput.println(columns.stream().map(column -> row.getOrDefault(column, ""))
+                    .map(column -> escapeCsvColumn(column))
                     .collect(Collectors.joining(",")));
         }
 
diff --git a/tools/class2greylist/test/src/com/android/class2greylist/AnnotationPropertyWriterTest.java b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationPropertyWriterTest.java
new file mode 100644
index 0000000..a6c7770
--- /dev/null
+++ b/tools/class2greylist/test/src/com/android/class2greylist/AnnotationPropertyWriterTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.class2greylist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+public class AnnotationPropertyWriterTest {
+
+    private ByteArrayOutputStream mByteArrayOutputStream;
+    private AnnotationPropertyWriter mAnnotationPropertyWriter;
+
+    @Before
+    public void setup() {
+        mByteArrayOutputStream = new ByteArrayOutputStream();
+        mAnnotationPropertyWriter = new AnnotationPropertyWriter(mByteArrayOutputStream);
+    }
+
+    @Test
+    public void testExportPropertiesNoEscaping() {
+        String signature = "foo";
+        Map<String, String> annotationProperties = ImmutableMap.of(
+                "prop", "val"
+        );
+        Set<String> parsedFlags = new HashSet<String>();
+        mAnnotationPropertyWriter.consume(signature, annotationProperties, parsedFlags);
+        mAnnotationPropertyWriter.close();
+
+        String output = mByteArrayOutputStream.toString();
+        String expected = "prop,signature\n"
+                + "|val|,|foo|\n";
+        assertThat(output).isEqualTo(expected);
+    }
+
+    @Test
+    public void testExportPropertiesEscapeQuotes() {
+        String signature = "foo";
+        Map<String, String> annotationProperties = ImmutableMap.of(
+                "prop", "val1 | val2 | val3"
+        );
+        Set<String> parsedFlags = new HashSet<String>();
+        mAnnotationPropertyWriter.consume(signature, annotationProperties, parsedFlags);
+        mAnnotationPropertyWriter.close();
+
+        String output = mByteArrayOutputStream.toString();
+        String expected = "prop,signature\n"
+                + "|val1 || val2 || val3|,|foo|\n";
+        assertThat(output).isEqualTo(expected);
+    }
+}