aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp2
-rw-r--r--aidl_checkapi.cpp1
-rw-r--r--aidl_language.cpp22
-rw-r--r--aidl_language.h2
-rw-r--r--aidl_typenames.cpp30
-rw-r--r--aidl_typenames.h2
-rw-r--r--aidl_unittest.cpp39
-rw-r--r--generate_java.cpp111
-rw-r--r--tests/android/aidl/tests/immutable/Bar.aidl22
-rw-r--r--tests/android/aidl/tests/immutable/Foo.aidl28
-rw-r--r--tests/android/aidl/tests/immutable/IBaz.aidl23
-rw-r--r--tests/java_app/src/android/aidl/tests/ImmutableAnnotationTests.java61
12 files changed, 312 insertions, 31 deletions
diff --git a/Android.bp b/Android.bp
index 06f8636d..580e5a12 100644
--- a/Android.bp
+++ b/Android.bp
@@ -332,11 +332,13 @@ android_test_helper_app {
srcs: [
"tests/android/aidl/tests/*.aidl",
"tests/android/aidl/tests/generic/*.aidl",
+ "tests/android/aidl/tests/immutable/*.aidl",
"tests/android/aidl/tests/map/*.aidl",
"tests/android/aidl/tests/extension/*.aidl",
"tests/java_app/src/android/aidl/tests/ExtensionTests.java",
"tests/java_app/src/android/aidl/tests/GenericTests.java",
"tests/java_app/src/android/aidl/tests/MapTests.java",
+ "tests/java_app/src/android/aidl/tests/ImmutableAnnotationTests.java",
"tests/java_app/src/android/aidl/tests/NullableTests.java",
"tests/java_app/src/android/aidl/tests/SimpleParcelable.java",
"tests/java_app/src/android/aidl/tests/TestFailException.java",
diff --git a/aidl_checkapi.cpp b/aidl_checkapi.cpp
index 5a4daa65..9e73f31d 100644
--- a/aidl_checkapi.cpp
+++ b/aidl_checkapi.cpp
@@ -52,6 +52,7 @@ static set<AidlAnnotation> get_strict_annotations(const AidlAnnotatable& node) {
static const set<AidlAnnotation::Type> kIgnoreAnnotations{
AidlAnnotation::Type::NULLABLE,
AidlAnnotation::Type::JAVA_DEBUG,
+ AidlAnnotation::Type::IMMUTABLE,
};
set<AidlAnnotation> annotations;
for (const AidlAnnotation& annotation : node.GetAnnotations()) {
diff --git a/aidl_language.cpp b/aidl_language.cpp
index 5ecfb601..d99f9efa 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -125,6 +125,7 @@ const std::vector<AidlAnnotation::Schema>& AidlAnnotation::AllSchemas() {
{AidlAnnotation::Type::BACKING, "Backing", {{"type", "String"}}},
{AidlAnnotation::Type::JAVA_PASSTHROUGH, "JavaPassthrough", {{"annotation", "String"}}},
{AidlAnnotation::Type::JAVA_DEBUG, "JavaDebug", {}},
+ {AidlAnnotation::Type::IMMUTABLE, "Immutable", {}},
};
return kSchemas;
}
@@ -269,6 +270,10 @@ bool AidlAnnotatable::IsVintfStability() const {
return GetAnnotation(annotations_, AidlAnnotation::Type::VINTF_STABILITY);
}
+bool AidlAnnotatable::IsImmutable() const {
+ return GetAnnotation(annotations_, AidlAnnotation::Type::IMMUTABLE);
+}
+
const AidlAnnotation* AidlAnnotatable::UnsupportedAppUsage() const {
return GetAnnotation(annotations_, AidlAnnotation::Type::UNSUPPORTED_APP_USAGE);
}
@@ -775,9 +780,9 @@ bool AidlParameterizable<std::string>::CheckValid() const {
}
std::set<AidlAnnotation::Type> AidlParcelable::GetSupportedAnnotations() const {
- return {AidlAnnotation::Type::VINTF_STABILITY, AidlAnnotation::Type::UNSUPPORTED_APP_USAGE,
+ return {AidlAnnotation::Type::VINTF_STABILITY, AidlAnnotation::Type::UNSUPPORTED_APP_USAGE,
AidlAnnotation::Type::JAVA_STABLE_PARCELABLE, AidlAnnotation::Type::HIDE,
- AidlAnnotation::Type::JAVA_PASSTHROUGH};
+ AidlAnnotation::Type::JAVA_PASSTHROUGH, AidlAnnotation::Type::IMMUTABLE};
}
bool AidlParcelable::CheckValid(const AidlTypenames& typenames) const {
@@ -817,9 +822,12 @@ void AidlStructuredParcelable::Dump(CodeWriter* writer) const {
}
std::set<AidlAnnotation::Type> AidlStructuredParcelable::GetSupportedAnnotations() const {
- return {AidlAnnotation::Type::VINTF_STABILITY, AidlAnnotation::Type::UNSUPPORTED_APP_USAGE,
- AidlAnnotation::Type::HIDE, AidlAnnotation::Type::JAVA_PASSTHROUGH,
- AidlAnnotation::Type::JAVA_DEBUG};
+ return {AidlAnnotation::Type::VINTF_STABILITY,
+ AidlAnnotation::Type::UNSUPPORTED_APP_USAGE,
+ AidlAnnotation::Type::HIDE,
+ AidlAnnotation::Type::JAVA_PASSTHROUGH,
+ AidlAnnotation::Type::JAVA_DEBUG,
+ AidlAnnotation::Type::IMMUTABLE};
}
bool AidlStructuredParcelable::CheckValid(const AidlTypenames& typenames) const {
@@ -830,7 +838,11 @@ bool AidlStructuredParcelable::CheckValid(const AidlTypenames& typenames) const
for (const auto& v : GetFields()) {
success = success && v->CheckValid(typenames);
+ if (IsImmutable()) {
+ success = success && typenames.CanBeImmutable(v->GetType());
+ }
}
+
return success;
}
diff --git a/aidl_language.h b/aidl_language.h
index ea9fd38b..512636de 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -166,6 +166,7 @@ class AidlAnnotation : public AidlNode {
UTF8_IN_CPP,
JAVA_PASSTHROUGH,
JAVA_DEBUG,
+ IMMUTABLE,
};
static std::string TypeToString(Type type);
@@ -229,6 +230,7 @@ class AidlAnnotatable : public AidlNode {
bool IsNullable() const;
bool IsUtf8InCpp() const;
bool IsVintfStability() const;
+ bool IsImmutable() const;
bool IsStableApiParcelable(Options::Language lang) const;
bool IsHide() const;
bool IsJavaDebug() const;
diff --git a/aidl_typenames.cpp b/aidl_typenames.cpp
index a25ad6b3..46977829 100644
--- a/aidl_typenames.cpp
+++ b/aidl_typenames.cpp
@@ -217,7 +217,32 @@ AidlTypenames::ResolvedTypename AidlTypenames::ResolveTypename(const string& typ
}
}
-// Only T[], List, Map, ParcelFileDescriptor and Parcelable can be an out parameter.
+// Only immutable Parcelable, primitive type, and String, and List, Map, array of the types can be
+// immutable.
+bool AidlTypenames::CanBeImmutable(const AidlTypeSpecifier& type) const {
+ const string& name = type.GetName();
+ if (type.IsGeneric()) {
+ if (type.GetName() == "List" || type.GetName() == "Map") {
+ const auto& types = type.GetTypeParameters();
+ return std::all_of(types.begin(), types.end(),
+ [this](const auto& t) { return CanBeImmutable(*t); });
+ }
+ AIDL_ERROR(type) << "For a generic type, an immutable parcelable can contain only List or Map.";
+ return false;
+ }
+ if (IsPrimitiveTypename(name) || name == "String") {
+ return true;
+ }
+ const AidlDefinedType* t = TryGetDefinedType(type.GetName());
+ if (t == nullptr) {
+ AIDL_ERROR(type) << "An immutable parcelable can contain only immutable Parcelable, primitive "
+ "type, and String.";
+ return false;
+ }
+ return t->IsImmutable();
+}
+
+// Only T[], List, Map, ParcelFileDescriptor and mutable Parcelable can be an out parameter.
bool AidlTypenames::CanBeOutParameter(const AidlTypeSpecifier& type) const {
const string& name = type.GetName();
if (IsBuiltinTypename(name) || GetEnumDeclaration(type)) {
@@ -226,7 +251,8 @@ bool AidlTypenames::CanBeOutParameter(const AidlTypeSpecifier& type) const {
}
const AidlDefinedType* t = TryGetDefinedType(type.GetName());
CHECK(t != nullptr) << "Unrecognized type: '" << type.GetName() << "'";
- return t->AsParcelable() != nullptr;
+ // An 'out' field is passed as an argument, so it doesn't make sense if it is immutable.
+ return t->AsParcelable() != nullptr && !t->IsImmutable();
}
const AidlEnumDeclaration* AidlTypenames::GetEnumDeclaration(const AidlTypeSpecifier& type) const {
diff --git a/aidl_typenames.h b/aidl_typenames.h
index 82001d23..2bcc4097 100644
--- a/aidl_typenames.h
+++ b/aidl_typenames.h
@@ -68,6 +68,8 @@ class AidlTypenames final {
};
ResolvedTypename ResolveTypename(const string& type_name) const;
bool CanBeOutParameter(const AidlTypeSpecifier& type) const;
+ bool CanBeImmutable(const AidlTypeSpecifier& type) const;
+
bool IsIgnorableImport(const string& import) const;
// Returns the AidlEnumDeclaration of the given type, or nullptr if the type
// is not an AidlEnumDeclaration;
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index beedbf6b..6de0a1fc 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -124,8 +124,8 @@ public class Rect implements android.os.Parcelable
{
int _aidl_start_pos = _aidl_parcel.dataPosition();
int _aidl_parcelable_size = _aidl_parcel.readInt();
- if (_aidl_parcelable_size < 0) return;
try {
+ if (_aidl_parcelable_size < 0) return;
x = _aidl_parcel.readInt();
if (_aidl_parcel.dataPosition() - _aidl_start_pos >= _aidl_parcelable_size) return;
y = _aidl_parcel.readInt();
@@ -325,7 +325,8 @@ TEST_P(AidlTest, RejectUnsupportedParcelableAnnotations) {
const string method = "package a; @nullable parcelable IFoo cpp_header \"IFoo.h\";";
const string expected_stderr =
"ERROR: a/Foo.aidl:1.32-37: 'nullable' is not a supported annotation for this node. "
- "It must be one of: Hide, JavaOnlyStableParcelable, UnsupportedAppUsage, VintfStability, JavaPassthrough\n";
+ "It must be one of: Hide, JavaOnlyStableParcelable, UnsupportedAppUsage, VintfStability, "
+ "JavaPassthrough, Immutable\n";
CaptureStderr();
EXPECT_EQ(nullptr, Parse("a/Foo.aidl", method, typenames_, GetLanguage(), &error));
EXPECT_EQ(expected_stderr, GetCapturedStderr());
@@ -337,7 +338,8 @@ TEST_P(AidlTest, RejectUnsupportedParcelableDefineAnnotations) {
const string method = "package a; @nullable parcelable Foo { String a; String b; }";
const string expected_stderr =
"ERROR: a/Foo.aidl:1.32-36: 'nullable' is not a supported annotation for this node. "
- "It must be one of: Hide, UnsupportedAppUsage, VintfStability, JavaPassthrough, JavaDebug\n";
+ "It must be one of: Hide, UnsupportedAppUsage, VintfStability, JavaPassthrough, JavaDebug, "
+ "Immutable\n";
CaptureStderr();
EXPECT_EQ(nullptr, Parse("a/Foo.aidl", method, typenames_, GetLanguage(), &error));
EXPECT_EQ(expected_stderr, GetCapturedStderr());
@@ -2279,5 +2281,36 @@ TEST_P(AidlTest, UnsupportedBackingAnnotationParam) {
EXPECT_EQ(AidlError::BAD_TYPE, error);
}
+TEST_P(AidlTest, SupportImmutableAnnotation) {
+ io_delegate_.SetFileContents(
+ "Foo.aidl",
+ "@Immutable parcelable Foo { int a; Bar b; List<Bar> c; Map<String, Baz> d; Bar[] e; }");
+ io_delegate_.SetFileContents("Bar.aidl", "@Immutable parcelable Bar { String a; }");
+ io_delegate_.SetFileContents("Baz.aidl", "@Immutable @JavaOnlyStableParcelable parcelable Baz;");
+ Options options = Options::From("aidl --lang=java -I . Foo.aidl");
+ EXPECT_EQ(0, ::android::aidl::compile_aidl(options, io_delegate_));
+}
+
+TEST_P(AidlTest, RejectMutableParcelableFromImmutableParcelable) {
+ io_delegate_.SetFileContents("Foo.aidl", "@Immutable parcelable Foo { Bar bar; }");
+ io_delegate_.SetFileContents("Bar.aidl", "parcelable Bar { String a; }");
+ Options options = Options::From("aidl --lang=java Foo.aidl -I .");
+ EXPECT_NE(0, ::android::aidl::compile_aidl(options, io_delegate_));
+}
+
+TEST_P(AidlTest, ImmtuableParcelableCannotBeInOut) {
+ io_delegate_.SetFileContents("Foo.aidl", "@Immutable parcelable Foo { int a; }");
+ io_delegate_.SetFileContents("IBar.aidl", "interface IBar { void my(inout Foo); }");
+ Options options = Options::From("aidl --lang=java IBar.aidl -I .");
+ EXPECT_NE(0, ::android::aidl::compile_aidl(options, io_delegate_));
+}
+
+TEST_P(AidlTest, ImmtuableParcelableCannotBeOut) {
+ io_delegate_.SetFileContents("Foo.aidl", "@Immutable parcelable Foo { int a; }");
+ io_delegate_.SetFileContents("IBar.aidl", "interface IBar { void my(out Foo); }");
+ Options options = Options::From("aidl --lang=java IBar.aidl -I .");
+ EXPECT_NE(0, ::android::aidl::compile_aidl(options, io_delegate_));
+}
+
} // namespace aidl
} // namespace android
diff --git a/generate_java.cpp b/generate_java.cpp
index 3bb5ba8d..4da0c4fe 100644
--- a/generate_java.cpp
+++ b/generate_java.cpp
@@ -110,11 +110,12 @@ std::unique_ptr<android::aidl::java::Class> generate_parcel_class(
out << a << "\n";
}
out << "public ";
- if (variable->GetType().GetName() == "ParcelableHolder") {
+
+ if (variable->GetType().GetName() == "ParcelableHolder" || parcel->IsImmutable()) {
out << "final ";
}
out << JavaSignatureOf(variable->GetType(), typenames) << " " << variable->GetName();
- if (variable->GetDefaultValue()) {
+ if (!parcel->IsImmutable() && variable->GetDefaultValue()) {
out << " = " << variable->ValueString(ConstantValueDecorator);
} else if (variable->GetType().GetName() == "ParcelableHolder") {
out << std::boolalpha;
@@ -145,9 +146,13 @@ std::unique_ptr<android::aidl::java::Class> generate_parcel_class(
out << " @Override\n";
out << " public " << parcel->GetName()
<< " createFromParcel(android.os.Parcel _aidl_source) {\n";
- out << " " << parcel->GetName() << " _aidl_out = new " << parcel->GetName() << "();\n";
- out << " _aidl_out.readFromParcel(_aidl_source);\n";
- out << " return _aidl_out;\n";
+ if (parcel->IsImmutable()) {
+ out << " return new " << parcel->GetName() << "(_aidl_source);\n";
+ } else {
+ out << " " << parcel->GetName() << " _aidl_out = new " << parcel->GetName() << "();\n";
+ out << " _aidl_out.readFromParcel(_aidl_source);\n";
+ out << " return _aidl_out;\n";
+ }
out << " }\n";
out << " @Override\n";
out << " public " << parcel->GetName() << "[] newArray(int _aidl_size) {\n";
@@ -198,20 +203,63 @@ std::unique_ptr<android::aidl::java::Class> generate_parcel_class(
parcel_class->elements.push_back(write_method);
- auto read_method = std::make_shared<Method>();
- read_method->modifiers = PUBLIC | FINAL;
- read_method->returnType = "void";
- read_method->name = "readFromParcel";
- read_method->parameters.push_back(parcel_variable);
- read_method->statements = std::make_shared<StatementBlock>();
+ if (parcel->IsImmutable()) {
+ auto constructor = std::make_shared<Method>();
+ constructor->modifiers = PUBLIC;
+ constructor->name = parcel->GetName();
+ constructor->statements = std::make_shared<StatementBlock>();
+ for (const auto& field : parcel->GetFields()) {
+ constructor->parameters.push_back(std::make_shared<Variable>(
+ JavaSignatureOf(field->GetType(), typenames), field->GetName()));
+ out.str("");
+
+ out << "this." << field->GetName() << " = ";
+ if (field->GetType().GetName() == "List") {
+ out << "java.util.Collections.unmodifiableList(" << field->GetName() << ");\n";
+ } else if (field->GetType().GetName() == "Map") {
+ out << "java.util.Collections.unmodifiableMap(" << field->GetName() << ");\n";
+ } else {
+ out << field->GetName() << ";\n";
+ }
+ constructor->statements->Add(std::make_shared<LiteralStatement>(out.str()));
+ }
+ parcel_class->elements.push_back(constructor);
+ }
+ // For an immutable parcelable, generate a constructor with a parcel object,
+ // Otherwise, generate readFromParcel method.
+ auto read_or_create_method = std::make_shared<Method>();
+ if (parcel->IsImmutable()) {
+ auto constructor = std::make_shared<Method>();
+ read_or_create_method->modifiers = PUBLIC;
+ read_or_create_method->name = parcel->GetName();
+ read_or_create_method->parameters.push_back(parcel_variable);
+ read_or_create_method->statements = std::make_shared<StatementBlock>();
+ } else {
+ read_or_create_method->modifiers = PUBLIC | FINAL;
+ read_or_create_method->returnType = "void";
+ read_or_create_method->name = "readFromParcel";
+ read_or_create_method->parameters.push_back(parcel_variable);
+ read_or_create_method->statements = std::make_shared<StatementBlock>();
+ }
out.str("");
+ if (parcel->IsImmutable()) {
+ for (const auto& field : parcel->GetFields()) {
+ out << JavaSignatureOf(field->GetType(), typenames) << " " << field->GetName();
+ if (field->GetDefaultValue()) {
+ out << " = " << field->ValueString(ConstantValueDecorator);
+ } else {
+ out << " = null";
+ }
+ out << ";\n";
+ }
+ }
out << "int _aidl_start_pos = _aidl_parcel.dataPosition();\n"
<< "int _aidl_parcelable_size = _aidl_parcel.readInt();\n"
- << "if (_aidl_parcelable_size < 0) return;\n"
- << "try {\n";
+ << "try {\n"
+ << " if (_aidl_parcelable_size < 0) return;\n";
- read_method->statements->Add(std::make_shared<LiteralStatement>(out.str()));
+ read_or_create_method->statements->Add(std::make_shared<LiteralStatement>(out.str()));
out.str("");
out << " if (_aidl_parcel.dataPosition() - _aidl_start_pos >= _aidl_parcelable_size) return;\n";
@@ -221,6 +269,8 @@ std::unique_ptr<android::aidl::java::Class> generate_parcel_class(
// at most once.
bool is_classloader_created = false;
for (const auto& field : parcel->GetFields()) {
+ const auto field_variable_name =
+ (parcel->IsImmutable() ? "_aidl_temp_" : "") + field->GetName();
string code;
CodeWriterPtr writer = CodeWriter::ForString(&code);
CodeGeneratorContext context{
@@ -228,25 +278,44 @@ std::unique_ptr<android::aidl::java::Class> generate_parcel_class(
.typenames = typenames,
.type = field->GetType(),
.parcel = parcel_variable->name,
- .var = field->GetName(),
+ .var = field_variable_name,
.is_classloader_created = &is_classloader_created,
};
context.writer.Indent();
+ if (parcel->IsImmutable()) {
+ context.writer.Write("%s %s;\n", JavaSignatureOf(field->GetType(), typenames).c_str(),
+ field_variable_name.c_str());
+ }
CreateFromParcelFor(context);
+ if (parcel->IsImmutable()) {
+ context.writer.Write("%s = %s;\n", field->GetName().c_str(), field_variable_name.c_str());
+ }
writer->Close();
- read_method->statements->Add(std::make_shared<LiteralStatement>(code));
+ read_or_create_method->statements->Add(std::make_shared<LiteralStatement>(code));
if (!sizeCheck) sizeCheck = std::make_shared<LiteralStatement>(out.str());
- read_method->statements->Add(sizeCheck);
+ read_or_create_method->statements->Add(sizeCheck);
}
out.str("");
out << "} finally {\n"
- << " _aidl_parcel.setDataPosition(_aidl_start_pos + _aidl_parcelable_size);\n"
- << "}\n";
+ << " _aidl_parcel.setDataPosition(_aidl_start_pos + _aidl_parcelable_size);\n";
+ if (parcel->IsImmutable()) {
+ for (const auto& field : parcel->GetFields()) {
+ out << " this." << field->GetName() << " = ";
+ if (field->GetType().GetName() == "List") {
+ out << "java.util.Collections.unmodifiableList(" << field->GetName() << ");\n";
+ } else if (field->GetType().GetName() == "Map") {
+ out << "java.util.Collections.unmodifiableMap(" << field->GetName() << ");\n";
+ } else {
+ out << field->GetName() << ";\n";
+ }
+ }
+ }
+ out << "}\n";
- read_method->statements->Add(std::make_shared<LiteralStatement>(out.str()));
+ read_or_create_method->statements->Add(std::make_shared<LiteralStatement>(out.str()));
- parcel_class->elements.push_back(read_method);
+ parcel_class->elements.push_back(read_or_create_method);
if (parcel->IsJavaDebug()) {
out.str("");
diff --git a/tests/android/aidl/tests/immutable/Bar.aidl b/tests/android/aidl/tests/immutable/Bar.aidl
new file mode 100644
index 00000000..ac04ad02
--- /dev/null
+++ b/tests/android/aidl/tests/immutable/Bar.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.immutable;
+
+@Immutable
+parcelable Bar {
+ String s;
+} \ No newline at end of file
diff --git a/tests/android/aidl/tests/immutable/Foo.aidl b/tests/android/aidl/tests/immutable/Foo.aidl
new file mode 100644
index 00000000..ca8ba016
--- /dev/null
+++ b/tests/android/aidl/tests/immutable/Foo.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.immutable;
+
+import android.aidl.tests.immutable.Bar;
+
+@Immutable
+parcelable Foo {
+ int a;
+ Bar b;
+ List<Bar> c;
+ Map<String, Bar> d;
+ Bar[] e;
+} \ No newline at end of file
diff --git a/tests/android/aidl/tests/immutable/IBaz.aidl b/tests/android/aidl/tests/immutable/IBaz.aidl
new file mode 100644
index 00000000..69538e2d
--- /dev/null
+++ b/tests/android/aidl/tests/immutable/IBaz.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.immutable;
+
+import android.aidl.tests.immutable.Foo;
+
+interface IBaz {
+ Foo RepeatFoo(in Foo a);
+} \ No newline at end of file
diff --git a/tests/java_app/src/android/aidl/tests/ImmutableAnnotationTests.java b/tests/java_app/src/android/aidl/tests/ImmutableAnnotationTests.java
new file mode 100644
index 00000000..1db6b268
--- /dev/null
+++ b/tests/java_app/src/android/aidl/tests/ImmutableAnnotationTests.java
@@ -0,0 +1,61 @@
+/*
+ * 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 static org.junit.Assert.fail;
+
+import android.aidl.tests.immutable.Bar;
+import android.aidl.tests.immutable.Foo;
+import android.os.RemoteException;
+import java.lang.UnsupportedOperationException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ImmutableAnnotationTests {
+ @Test
+ public void testEveryFieldIsFinal() throws RemoteException {
+ for (Field f : Foo.class.getDeclaredFields()) {
+ if (!Modifier.isFinal(f.getModifiers())) {
+ fail(f.getName() + " should be final.");
+ }
+ }
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testListIsUnmodifiable() throws RemoteException {
+ Foo foo =
+ new Foo(7, new Bar("my"), new ArrayList<Bar>(), new HashMap<String, Bar>(), new Bar[5]);
+ foo.c.add(new Bar("hi"));
+ // It is supposed to fail.
+ fail("A List in an immutable parcelable isn't modifiable.");
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testMapIsUnmodifiable() throws RemoteException {
+ Foo foo =
+ new Foo(7, new Bar("my"), new ArrayList<Bar>(), new HashMap<String, Bar>(), new Bar[5]);
+ foo.d.put("key", new Bar("value"));
+ // It is supposed to fail.
+ fail("A Map in an immutable parcelable isn't modifiable.");
+ }
+}