Snap for 7080740 from 5eb4fe3f2cb140f0cfd4be36388663458845dcab to mainline-cellbroadcast-release

Change-Id: I063c82110f84ceb46cd95b37dfcc7156619a841c
diff --git a/aidl_checkapi.cpp b/aidl_checkapi.cpp
index f0228d9..b2a884c 100644
--- a/aidl_checkapi.cpp
+++ b/aidl_checkapi.cpp
@@ -48,6 +48,7 @@
   // - a new implementation might start accepting null values (add @nullable)
   static const set<std::string> kIgnoreAnnotations{
       "nullable",
+      "JavaDerive",
   };
   set<AidlAnnotation> annotations;
   for (const AidlAnnotation& annotation : node.GetAnnotations()) {
diff --git a/aidl_language.cpp b/aidl_language.cpp
index 5beaea1..be1221d 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -131,6 +131,7 @@
 static const string kJavaStableParcelable("JavaOnlyStableParcelable");
 static const string kHide("Hide");
 static const string kBacking("Backing");
+static const string kJavaDerive("JavaDerive");
 
 static const std::map<string, std::map<std::string, std::string>> kAnnotationParameters{
     {kNullable, {}},
@@ -144,7 +145,8 @@
       {"trackingBug", "long"}}},
     {kJavaStableParcelable, {}},
     {kHide, {}},
-    {kBacking, {{"type", "String"}}}};
+    {kBacking, {{"type", "String"}}},
+    {kJavaDerive, {{"toString", "boolean"}}}};
 
 AidlAnnotation* AidlAnnotation::Parse(
     const AidlLocation& location, const string& name,
@@ -308,6 +310,10 @@
   return HasAnnotation(annotations_, kHide);
 }
 
+const AidlAnnotation* AidlAnnotatable::JavaDerive() const {
+  return GetAnnotation(annotations_, kJavaDerive);
+}
+
 void AidlAnnotatable::DumpAnnotations(CodeWriter* writer) const {
   if (annotations_.empty()) return;
 
diff --git a/aidl_language.h b/aidl_language.h
index 7a40427..920970f 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -244,6 +244,7 @@
   bool IsVintfStability() const;
   bool IsStableApiParcelable(Options::Language lang) const;
   bool IsHide() const;
+  const AidlAnnotation* JavaDerive() const;
 
   void DumpAnnotations(CodeWriter* writer) const;
 
diff --git a/aidl_to_java.cpp b/aidl_to_java.cpp
index 8910365..f4706a2 100644
--- a/aidl_to_java.cpp
+++ b/aidl_to_java.cpp
@@ -762,6 +762,50 @@
   return true;
 }
 
+void ToStringFor(const CodeGeneratorContext& c) {
+  if (c.type.IsArray()) {
+    // Arrays can be null
+    c.writer << c.var << " == null ? \"null\" : ";
+    c.writer << "java.util.Arrays.toString(" << c.var << ")";
+    return;
+  }
+
+  const std::string name = c.type.GetName();
+
+  if (AidlTypenames::IsPrimitiveTypename(name)) {
+    c.writer << c.var;
+    return;
+  }
+
+  const AidlDefinedType* t = c.typenames.TryGetDefinedType(name);
+  if (t != nullptr && t->AsEnumDeclaration()) {
+    c.writer << c.var;
+    return;
+  }
+
+  // FileDescriptor doesn't have a good toString() impl.
+  if (name == "FileDescriptor") {
+    c.writer << c.var << " == null ? \"null\" : ";
+    c.writer << c.var << ".getInt$()";
+    return;
+  }
+
+  // Rest of the built-in types have reasonable toString() impls.
+  if (AidlTypenames::IsBuiltinTypename(name)) {
+    c.writer << "java.util.Objects.toString(" << c.var << ")";
+    return;
+  }
+
+  // For user-defined types, we also use toString() that we are generating here, but just make sure
+  // that they are actually user-defined types.
+  AIDL_FATAL_IF(t == nullptr, c.type) << "Unknown type";
+  if (t->AsInterface() != nullptr || t->AsParcelable() != nullptr) {
+    c.writer << "java.util.Objects.toString(" << c.var << ")";
+    return;
+  }
+  CHECK(true) << "Unhandled typename: " << name << endl;
+}
+
 }  // namespace java
 }  // namespace aidl
 }  // namespace android
diff --git a/aidl_to_java.h b/aidl_to_java.h
index d33b25b..2ebe2c3 100644
--- a/aidl_to_java.h
+++ b/aidl_to_java.h
@@ -99,6 +99,10 @@
 // array or a List.
 bool ReadFromParcelFor(const CodeGeneratorContext& c);
 
+// Writes an expression that returns the string representation of a field
+// in a parcelable
+void ToStringFor(const CodeGeneratorContext& c);
+
 }  // namespace java
 }  // namespace aidl
 }  // namespace android
diff --git a/aidl_typenames.cpp b/aidl_typenames.cpp
index ad89c1c..51c26d7 100644
--- a/aidl_typenames.cpp
+++ b/aidl_typenames.cpp
@@ -82,9 +82,9 @@
 }
 
 bool AidlTypenames::IsIgnorableImport(const string& import) const {
-  static set<string> ignore_import = {"android.os.IInterface",   "android.os.IBinder",
-                                      "android.os.Parcelable",   "android.os.Parcel",
-                                      "android.content.Context", "java.lang.String"};
+  static set<string> ignore_import = {
+      "android.os.IInterface",   "android.os.IBinder", "android.os.Parcelable", "android.os.Parcel",
+      "android.content.Context", "java.lang.String",   "java.lang.CharSequence"};
   // these known built-in types don't need to be imported
   const bool in_ignore_import = ignore_import.find(import) != ignore_import.end();
   // an already defined type doesn't need to be imported again unless it is from
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index 6827980..fea3419 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -21,6 +21,7 @@
 
 #include <android-base/stringprintf.h>
 #include <gtest/gtest.h>
+#include <gmock/gmock.h>
 
 #include "aidl.h"
 #include "aidl_checkapi.h"
@@ -397,6 +398,24 @@
   EXPECT_NE(nullptr, Parse("a/IFoo.aidl", oneway_method, typenames_, Options::Language::JAVA));
 }
 
+TEST_F(AidlTest, ParsesJavaDeriveAnnotation) {
+  io_delegate_.SetFileContents("a/IFoo.aidl", R"(package a;
+    @JavaDerive(toString=true) parcelable IFoo { int a; float b; })");
+  Options java_options = Options::From("aidl --lang=java -o out a/IFoo.aidl");
+  EXPECT_EQ(0, ::android::aidl::compile_aidl(java_options, io_delegate_));
+
+  string java_out;
+  EXPECT_TRUE(io_delegate_.GetWrittenContents("out/a/IFoo.java", &java_out));
+  EXPECT_THAT(java_out, testing::HasSubstr("public String toString() {"));
+
+  // Other backends shouldn't be bothered
+  Options cpp_options = Options::From("aidl --lang=cpp -o out -h out a/IFoo.aidl");
+  EXPECT_EQ(0, ::android::aidl::compile_aidl(cpp_options, io_delegate_));
+
+  Options ndk_options = Options::From("aidl --lang=ndk -o out -h out a/IFoo.aidl");
+  EXPECT_EQ(0, ::android::aidl::compile_aidl(ndk_options, io_delegate_));
+}
+
 TEST_F(AidlTest, WritesComments) {
   string foo_interface =
       "package a; /* foo */ interface IFoo {"
diff --git a/code_writer.h b/code_writer.h
index d890d52..08516bf 100644
--- a/code_writer.h
+++ b/code_writer.h
@@ -39,6 +39,14 @@
   // The buffer gets updated only after Close() is called or the CodeWriter
   // is deleted -- much like a real file.
   static CodeWriterPtr ForString(std::string* buf);
+  // Run a Code Generater (which accepts CodeWriter& as a first parameter)
+  // and return a result as a string.
+  template <typename... Args>
+  static std::string RunWith(void (*gen)(CodeWriter&, Args...), Args&&... args) {
+    std::string code;
+    (*gen)(*ForString(&code), std::forward<Args>(args)...);
+    return code;
+  }
   // Write a formatted string to this writer in the usual printf sense.
   // Returns false on error.
   virtual bool Write(const char* format, ...) __attribute__((format(printf, 2, 3)));
diff --git a/generate_java.cpp b/generate_java.cpp
index 5d572a4..f120ac5 100644
--- a/generate_java.cpp
+++ b/generate_java.cpp
@@ -33,6 +33,49 @@
 using ::android::aidl::java::Variable;
 using std::string;
 
+namespace {
+using android::aidl::java::CodeGeneratorContext;
+using android::aidl::java::ConstantValueDecorator;
+
+void GenerateToString(CodeWriter& out, const AidlStructuredParcelable& parcel,
+                      const AidlTypenames& typenames) {
+  out << "@Override\n";
+  out << "public String toString() {\n";
+  out.Indent();
+  out << "java.util.StringJoiner _aidl_sj = new java.util.StringJoiner(";
+  out << "\", \", \"{\", \"}\");\n";
+  for (const auto& field : parcel.GetFields()) {
+    CodeGeneratorContext ctx{
+        .writer = out,
+        .typenames = typenames,
+        .type = field->GetType(),
+        .var = field->GetName(),
+    };
+    out << "_aidl_sj.add(\"" << field->GetName() << ": \" + (";
+    ToStringFor(ctx);
+    out << "));\n";
+  }
+  out << "return \"" << parcel.GetCanonicalName() << "\" + _aidl_sj.toString()  ;\n";
+  out.Dedent();
+  out << "}\n";
+}
+
+template <typename ParcelableType>
+void GenerateDerivedMethods(CodeWriter& out, const ParcelableType& parcel,
+                            const AidlTypenames& typenames) {
+  if (auto java_derive = parcel.JavaDerive(); java_derive) {
+    auto synthetic_methods = java_derive->AnnotationParams(ConstantValueDecorator);
+    for (const auto& [method_name, generate] : synthetic_methods) {
+      if (generate == "true") {
+        if (method_name == "toString") {
+          GenerateToString(out, parcel, typenames);
+        }
+      }
+    }
+  }
+}
+}  // namespace
+
 namespace android {
 namespace aidl {
 namespace java {
@@ -227,6 +270,9 @@
 
   parcel_class->elements.push_back(read_method);
 
+  auto method = CodeWriter::RunWith(GenerateDerivedMethods, *parcel, typenames);
+  parcel_class->elements.push_back(std::make_shared<LiteralClassElement>(method));
+
   auto describe_contents_method = std::make_shared<Method>();
   describe_contents_method->modifiers = PUBLIC | OVERRIDE;
   describe_contents_method->returnType = "int";
diff --git a/tests/android/aidl/tests/OtherParcelableForToString.aidl b/tests/android/aidl/tests/OtherParcelableForToString.aidl
new file mode 100644
index 0000000..f042ab5
--- /dev/null
+++ b/tests/android/aidl/tests/OtherParcelableForToString.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.aidl.tests;
+
+@JavaDerive(toString=true)
+parcelable OtherParcelableForToString {
+    String field;
+}
diff --git a/tests/android/aidl/tests/ParcelableForToString.aidl b/tests/android/aidl/tests/ParcelableForToString.aidl
new file mode 100644
index 0000000..2ad414c
--- /dev/null
+++ b/tests/android/aidl/tests/ParcelableForToString.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.aidl.tests;
+
+import android.aidl.tests.OtherParcelableForToString;
+import android.aidl.tests.IntEnum;
+
+@JavaDerive(toString=true)
+parcelable ParcelableForToString {
+    int intValue;
+    int[] intArray;
+    long longValue;
+    long[] longArray;
+    double doubleValue;
+    double[] doubleArray;
+    float floatValue;
+    float[] floatArray;
+    byte byteValue;
+    byte[] byteArray;
+    boolean booleanValue;
+    boolean[] booleanArray;
+    String stringValue;
+    String[] stringArray;
+    List<String> stringList;
+    OtherParcelableForToString parcelableValue;
+    OtherParcelableForToString[] parcelableArray;
+    IntEnum enumValue;
+    IntEnum[] enumArray;
+    String[] nullArray;
+    List<String> nullList;
+}
diff --git a/tests/android/aidl/tests/StructuredParcelable.aidl b/tests/android/aidl/tests/StructuredParcelable.aidl
index 28046e2..5e316ee 100644
--- a/tests/android/aidl/tests/StructuredParcelable.aidl
+++ b/tests/android/aidl/tests/StructuredParcelable.aidl
@@ -21,6 +21,7 @@
 import android.aidl.tests.LongEnum;
 import android.aidl.tests.ConstantExpressionEnum;
 
+@JavaDerive(toString=true)
 parcelable StructuredParcelable {
     int[] shouldContainThreeFs;
     int f;
diff --git a/tests/java_app/src/android/aidl/tests/TestServiceClient.java b/tests/java_app/src/android/aidl/tests/TestServiceClient.java
index 01df091..3415280 100644
--- a/tests/java_app/src/android/aidl/tests/TestServiceClient.java
+++ b/tests/java_app/src/android/aidl/tests/TestServiceClient.java
@@ -920,6 +920,65 @@
             "parcelable.const_exprs_10 should be 1 but is " + parcelable.const_exprs_10);
       }
 
+      final String expected = "android.aidl.tests.StructuredParcelable{" +
+          "shouldContainThreeFs: [17, 17, 17], " +
+          "f: 17, " +
+          "shouldBeJerry: Jerry, " +
+          "shouldBeByteBar: 2, " +
+          "shouldBeIntBar: 2000, " +
+          "shouldBeLongBar: 200000000000, " +
+          "shouldContainTwoByteFoos: [1, 1], " +
+          "shouldContainTwoIntFoos: [1000, 1000], " +
+          "shouldContainTwoLongFoos: [100000000000, 100000000000], " +
+          "stringDefaultsToFoo: foo, " +
+          "byteDefaultsToFour: 4, " +
+          "intDefaultsToFive: 5, " +
+          "longDefaultsToNegativeSeven: -7, " +
+          "booleanDefaultsToTrue: true, " +
+          "charDefaultsToC: C, " +
+          "floatDefaultsToPi: 3.14, " +
+          "doubleWithDefault: -3.14E17, " +
+          "arrayDefaultsTo123: [1, 2, 3], " +
+          "arrayDefaultsToEmpty: [], " +
+          "boolDefault: false, " +
+          "byteDefault: 0, " +
+          "intDefault: 0, " +
+          "longDefault: 0, " +
+          "floatDefault: 0.0, " +
+          "doubleDefault: 0.0, " +
+          "checkDoubleFromFloat: 3.14, " +
+          "checkStringArray1: [a, b], " +
+          "checkStringArray2: [a, b], " +
+          "int32_min: -2147483648, " +
+          "int32_max: 2147483647, " +
+          "int64_max: 9223372036854775807, " +
+          "hexInt32_neg_1: -1, " +
+          "ibinder: null, " +
+          "int32_1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, " +
+          "1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, " +
+          "1, 1, 1, 1], " +
+          "int64_1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], " +
+          "hexInt32_pos_1: 1, " +
+          "hexInt64_pos_1: 1, " +
+          "const_exprs_1: 1, " +
+          "const_exprs_2: 1, " +
+          "const_exprs_3: 1, " +
+          "const_exprs_4: 1, " +
+          "const_exprs_5: 1, " +
+          "const_exprs_6: 1, " +
+          "const_exprs_7: 1, " +
+          "const_exprs_8: 1, " +
+          "const_exprs_9: 1, " +
+          "const_exprs_10: 1, " +
+          "addString1: hello world!, " +
+          "addString2: The quick brown fox jumps over the lazy dog." +
+          "}";
+      if (!expected.equals(parcelable.toString())) {
+        mLog.logAndThrow(
+            "parcelable.toString() should be \"" + expected + "\" " +
+            "but is \"" + parcelable.toString() + "\"");
+      }
+
       mLog.log("Successfully verified the StructuredParcelable");
     }
 
@@ -953,6 +1012,64 @@
       }
     }
 
+    private void checkToString() throws TestFailException {
+      ParcelableForToString p = new ParcelableForToString();
+      p.intValue = 10;
+      p.intArray = new int[]{20, 30};
+      p.longValue = 100L;
+      p.longArray = new long[]{200L, 300L};
+      p.doubleValue = 3.14d;
+      p.doubleArray = new double[]{1.1d, 1.2d};
+      p.floatValue = 3.14f;
+      p.floatArray = new float[]{1.1f, 1.2f};
+      p.byteValue = 3;
+      p.byteArray = new byte[]{5, 6};
+      p.booleanValue = true;
+      p.booleanArray = new boolean[]{true, false};
+      p.stringValue = "this is a string";
+      p.stringArray = new String[]{"hello", "world"};
+      p.stringList = Arrays.asList(new String[]{"alice", "bob"});
+      OtherParcelableForToString op = new OtherParcelableForToString();
+      op.field = "other";
+      p.parcelableValue = op;
+      p.parcelableArray = new OtherParcelableForToString[]{op, op};
+      p.enumValue = IntEnum.FOO;
+      p.enumArray = new int[]{IntEnum.FOO, IntEnum.BAR};
+      p.nullArray = null;
+      p.nullList = null;
+
+      final String expected = "android.aidl.tests.ParcelableForToString{" +
+          "intValue: 10, " +
+          "intArray: [20, 30], " +
+          "longValue: 100, " +
+          "longArray: [200, 300], " +
+          "doubleValue: 3.14, " +
+          "doubleArray: [1.1, 1.2], " +
+          "floatValue: 3.14, " +
+          "floatArray: [1.1, 1.2], " +
+          "byteValue: 3, " +
+          "byteArray: [5, 6], " +
+          "booleanValue: true, " +
+          "booleanArray: [true, false], " +
+          "stringValue: this is a string, " +
+          "stringArray: [hello, world], " +
+          "stringList: [alice, bob], " +
+          "parcelableValue: android.aidl.tests.OtherParcelableForToString{field: other}, " +
+          "parcelableArray: [" +
+          "android.aidl.tests.OtherParcelableForToString{field: other}, " +
+          "android.aidl.tests.OtherParcelableForToString{field: other}], " +
+          "enumValue: 1000, " +
+          "enumArray: [1000, 2000], " +
+          "nullArray: null, " +
+          "nullList: null" +
+          "}";
+      if (!expected.equals(p.toString())) {
+        mLog.logAndThrow(
+            "parcelable.toString() should be \"" + expected + "\" " +
+            "but is \"" + p.toString() + "\"");
+      }
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -975,6 +1092,7 @@
           new MapTests(mLog).runTests();
           new GenericTests(mLog).runTests();
           checkDefaultImpl(service);
+          checkToString();
 
           mLog.log(mSuccessSentinel);
         } catch (TestFailException e) {