diff options
-rw-r--r-- | Android.bp | 2 | ||||
-rw-r--r-- | aidl_checkapi.cpp | 1 | ||||
-rw-r--r-- | aidl_language.cpp | 22 | ||||
-rw-r--r-- | aidl_language.h | 2 | ||||
-rw-r--r-- | aidl_typenames.cpp | 30 | ||||
-rw-r--r-- | aidl_typenames.h | 2 | ||||
-rw-r--r-- | aidl_unittest.cpp | 39 | ||||
-rw-r--r-- | generate_java.cpp | 111 | ||||
-rw-r--r-- | tests/android/aidl/tests/immutable/Bar.aidl | 22 | ||||
-rw-r--r-- | tests/android/aidl/tests/immutable/Foo.aidl | 28 | ||||
-rw-r--r-- | tests/android/aidl/tests/immutable/IBaz.aidl | 23 | ||||
-rw-r--r-- | tests/java_app/src/android/aidl/tests/ImmutableAnnotationTests.java | 61 |
12 files changed, 312 insertions, 31 deletions
@@ -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."); + } +} |