describeContents() returns CONTENTS_FILE_DESCRIPTOR

Parcelable.describeContents() returns CONTENTS_FILE_DESCRIPTOR when the
contents hold a file descriptor.

Bug: 170677046
Test: aidl_unittests / aidl_integration_test
Change-Id: Idb06123def25c78f7e4ed5317e61ddee8ca39d5d
diff --git a/Android.bp b/Android.bp
index e86d167..90fe3ff 100644
--- a/Android.bp
+++ b/Android.bp
@@ -267,6 +267,7 @@
         "tests/android/aidl/tests/ICppJavaTests.aidl",
         "tests/android/aidl/tests/SimpleParcelable.aidl",
         "tests/android/aidl/tests/Union.aidl",
+        "tests/android/aidl/tests/UnionWithFd.aidl",
         "tests/android/aidl/tests/extension/*.aidl",
     ],
     path: "tests",
diff --git a/aidl_language.cpp b/aidl_language.cpp
index 3bf8c47..63a7d4f 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -405,14 +405,16 @@
       comments_(comments),
       split_name_(Split(unresolved_name, ".")) {}
 
-AidlTypeSpecifier AidlTypeSpecifier::ArrayBase() const {
+const AidlTypeSpecifier& AidlTypeSpecifier::ArrayBase() const {
   AIDL_FATAL_IF(!is_array_, this);
   // Declaring array of generic type cannot happen, it is grammar error.
   AIDL_FATAL_IF(IsGeneric(), this);
 
-  AidlTypeSpecifier array_base = *this;
-  array_base.is_array_ = false;
-  return array_base;
+  if (!array_base_) {
+    array_base_.reset(new AidlTypeSpecifier(*this));
+    array_base_->is_array_ = false;
+  }
+  return *array_base_;
 }
 
 bool AidlTypeSpecifier::IsHidden() const {
diff --git a/aidl_language.h b/aidl_language.h
index e8bf546..4b56a5f 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -267,7 +267,7 @@
   virtual ~AidlTypeSpecifier() = default;
 
   // Copy of this type which is not an array.
-  AidlTypeSpecifier ArrayBase() const;
+  const AidlTypeSpecifier& ArrayBase() const;
 
   // Returns the full-qualified name of the base type.
   // int -> int
@@ -319,6 +319,7 @@
   bool is_array_;
   string comments_;
   vector<string> split_name_;
+  mutable shared_ptr<AidlTypeSpecifier> array_base_;
 };
 
 // Returns the universal value unaltered.
diff --git a/aidl_typenames.cpp b/aidl_typenames.cpp
index 5e5cdbd..16afbf6 100644
--- a/aidl_typenames.cpp
+++ b/aidl_typenames.cpp
@@ -299,6 +299,15 @@
   return nullptr;
 }
 
+const AidlParcelable* AidlTypenames::GetParcelable(const AidlTypeSpecifier& type) const {
+  if (auto defined_type = TryGetDefinedType(type.GetName()); defined_type != nullptr) {
+    if (auto parcelable = defined_type->AsParcelable(); parcelable != nullptr) {
+      return parcelable;
+    }
+  }
+  return nullptr;
+}
+
 void AidlTypenames::IterateTypes(const std::function<void(const AidlDefinedType&)>& body) const {
   for (const auto& kv : defined_types_) {
     body(*kv.second);
diff --git a/aidl_typenames.h b/aidl_typenames.h
index 469de4b..7ae3690 100644
--- a/aidl_typenames.h
+++ b/aidl_typenames.h
@@ -33,6 +33,7 @@
 class AidlDefinedType;
 class AidlEnumDeclaration;
 class AidlInterface;
+class AidlParcelable;
 class AidlTypeSpecifier;
 class AidlDocument;
 
@@ -79,6 +80,9 @@
   // Returns the AidlInterface of the given type, or nullptr if the type
   // is not an AidlInterface;
   const AidlInterface* GetInterface(const AidlTypeSpecifier& type) const;
+  // Returns the AidlParcelable of the given type, or nullptr if the type
+  // is not an AidlParcelable;
+  const AidlParcelable* GetParcelable(const AidlTypeSpecifier& type) const;
   // Iterates over all defined and then preprocessed types
   void IterateTypes(const std::function<void(const AidlDefinedType&)>& body) const;
 
diff --git a/aidl_unittest.cpp b/aidl_unittest.cpp
index 9dc2af0..16356ae 100644
--- a/aidl_unittest.cpp
+++ b/aidl_unittest.cpp
@@ -76,7 +76,7 @@
 p/Foo.aidl :
 )";
 
-const char kExpectedJavaParcelableOutputContests[] =
+const char kExpectedJavaParcelableOutputContents[] =
     R"(/*
  * This file is auto-generated.  DO NOT MODIFY.
  */
@@ -93,6 +93,8 @@
   public int y = 0;
 
   public android.os.ParcelFileDescriptor fd;
+
+  public java.util.List<android.os.ParcelFileDescriptor> fds;
   public static final android.os.Parcelable.Creator<Rect> CREATOR = new android.os.Parcelable.Creator<Rect>() {
     @Override
     public Rect createFromParcel(android.os.Parcel _aidl_source) {
@@ -140,6 +142,7 @@
         fd = null;
       }
       if (_aidl_parcel.dataPosition() - _aidl_start_pos >= _aidl_parcelable_size) return;
+      if (_aidl_parcel.dataPosition() - _aidl_start_pos >= _aidl_parcelable_size) return;
     } finally {
       if (_aidl_start_pos > (Integer.MAX_VALUE - _aidl_parcelable_size)) {
         throw new android.os.BadParcelableException("Overflow in the size of parcelable");
@@ -147,9 +150,12 @@
       _aidl_parcel.setDataPosition(_aidl_start_pos + _aidl_parcelable_size);
     }
   }
-  @Override public int describeContents()
-  {
-    return 0;
+  @Override
+  public int describeContents() {
+    int _mask = 0;
+    if (fd != null) _mask |= fd.describeContents();
+    if (fds != null) for (ParcelFileDescriptor _v0: fds) if (_v0 != null) _mask |= _v0.describeContents();
+    return _mask;
   }
 }
 )";
@@ -765,6 +771,7 @@
       "+ \"y\")\n"
       "  int y;\n"
       "  ParcelFileDescriptor fd;\n"
+      "  List<ParcelFileDescriptor> fds;\n"
       "}");
 
   vector<string> args{"aidl", "Rect.aidl"};
@@ -773,7 +780,7 @@
 
   string output;
   EXPECT_TRUE(io_delegate_.GetWrittenContents("Rect.java", &output));
-  EXPECT_EQ(kExpectedJavaParcelableOutputContests, output);
+  EXPECT_EQ(kExpectedJavaParcelableOutputContents, output);
 }
 
 TEST_F(AidlTest, CppHeaderIncludes) {
@@ -2699,6 +2706,7 @@
 
 #include <a/ByteEnum.h>
 #include <binder/Parcel.h>
+#include <binder/ParcelFileDescriptor.h>
 #include <binder/Status.h>
 #include <cstdint>
 #include <type_traits>
@@ -2732,6 +2740,7 @@
   enum Tag : int32_t {
     ns = 0,  // int[] ns;
     e,  // a.ByteEnum e;
+    pfd,  // ParcelFileDescriptor pfd;
   };
 
   template<typename _Tp>
@@ -2789,7 +2798,7 @@
     return DESCIPTOR;
   }
 private:
-  std::variant<::std::vector<int32_t>, ::a::ByteEnum> _value;
+  std::variant<::std::vector<int32_t>, ::a::ByteEnum, ::android::os::ParcelFileDescriptor> _value;
 };  // class Foo
 
 }  // namespace a
@@ -2814,6 +2823,11 @@
     if ((_aidl_ret_status = _aidl_parcel->readByte(reinterpret_cast<int8_t *>(&_aidl_value))) != ::android::OK) return _aidl_ret_status;
     set<e>(std::move(_aidl_value));
     return ::android::OK; }
+  case pfd: {
+    ::android::os::ParcelFileDescriptor _aidl_value;
+    if ((_aidl_ret_status = _aidl_parcel->readParcelable(&_aidl_value)) != ::android::OK) return _aidl_ret_status;
+    set<pfd>(std::move(_aidl_value));
+    return ::android::OK; }
   }
   return ::android::BAD_VALUE;
 }
@@ -2824,6 +2838,7 @@
   switch (getTag()) {
   case ns: return _aidl_parcel->writeInt32Vector(get<ns>());
   case e: return _aidl_parcel->writeByte(static_cast<int8_t>(get<e>()));
+  case pfd: return _aidl_parcel->writeParcelable(get<pfd>());
   }
   abort();
 }
@@ -2857,6 +2872,7 @@
   enum Tag : int32_t {
     ns = 0,  // int[] ns;
     e,  // a.ByteEnum e;
+    pfd,  // ParcelFileDescriptor pfd;
   };
 
   template<typename _Tp>
@@ -2911,7 +2927,7 @@
   binder_status_t writeToParcel(AParcel* _parcel) const;
   static const ::ndk::parcelable_stability_t _aidl_stability = ::ndk::STABILITY_LOCAL;
 private:
-  std::variant<std::vector<int32_t>, ::aidl::a::ByteEnum> _value;
+  std::variant<std::vector<int32_t>, ::aidl::a::ByteEnum, ::ndk::ScopedFileDescriptor> _value;
 };
 }  // namespace a
 }  // namespace aidl
@@ -2940,6 +2956,11 @@
     if ((_aidl_ret_status = AParcel_readByte(_parcel, reinterpret_cast<int8_t*>(&_aidl_value))) != STATUS_OK) return _aidl_ret_status;
     set<e>(std::move(_aidl_value));
     return STATUS_OK; }
+  case pfd: {
+    ::ndk::ScopedFileDescriptor _aidl_value;
+    if ((_aidl_ret_status = ::ndk::AParcel_readRequiredParcelFileDescriptor(_parcel, &_aidl_value)) != STATUS_OK) return _aidl_ret_status;
+    set<pfd>(std::move(_aidl_value));
+    return STATUS_OK; }
   }
   return STATUS_BAD_VALUE;
 }
@@ -2949,6 +2970,7 @@
   switch (getTag()) {
   case ns: return ::ndk::AParcel_writeVector(_parcel, get<ns>());
   case e: return AParcel_writeByte(_parcel, static_cast<int8_t>(get<e>()));
+  case pfd: return ::ndk::AParcel_writeRequiredParcelFileDescriptor(_parcel, get<pfd>());
   }
   abort();
 }
@@ -2967,6 +2989,7 @@
   // tags for union fields
   public final static int ns = 0;  // int[] ns;
   public final static int e = 1;  // a.ByteEnum e;
+  public final static int pfd = 2;  // ParcelFileDescriptor pfd;
 
   private int _tag;
   private Object _value;
@@ -3018,6 +3041,21 @@
     _set(e, _value);
   }
 
+  // ParcelFileDescriptor pfd;
+
+  public static Foo pfd(android.os.ParcelFileDescriptor _value) {
+    return new Foo(pfd, _value);
+  }
+
+  public android.os.ParcelFileDescriptor getPfd() {
+    _assertTag(pfd);
+    return (android.os.ParcelFileDescriptor) _value;
+  }
+
+  public void setPfd(android.os.ParcelFileDescriptor _value) {
+    _set(pfd, _value);
+  }
+
   public static final android.os.Parcelable.Creator<Foo> CREATOR = new android.os.Parcelable.Creator<Foo>() {
     @Override
     public Foo createFromParcel(android.os.Parcel _aidl_source) {
@@ -3039,6 +3077,15 @@
     case e:
       _aidl_parcel.writeByte(getE());
       break;
+    case pfd:
+      if ((getPfd()!=null)) {
+        _aidl_parcel.writeInt(1);
+        getPfd().writeToParcel(_aidl_parcel, 0);
+      }
+      else {
+        _aidl_parcel.writeInt(0);
+      }
+      break;
     }
   }
 
@@ -3056,13 +3103,29 @@
       _aidl_value = _aidl_parcel.readByte();
       _set(_aidl_tag, _aidl_value);
       return; }
+    case pfd: {
+      android.os.ParcelFileDescriptor _aidl_value;
+      if ((0!=_aidl_parcel.readInt())) {
+        _aidl_value = android.os.ParcelFileDescriptor.CREATOR.createFromParcel(_aidl_parcel);
+      }
+      else {
+        _aidl_value = null;
+      }
+      _set(_aidl_tag, _aidl_value);
+      return; }
     }
     throw new RuntimeException("union: out of range: " + _aidl_tag);
   }
 
   @Override
   public int describeContents() {
-    return 0;
+    int _mask = 0;
+    switch (getTag()) {
+    case pfd:
+      if (getPfd() != null) _mask |= getPfd().describeContents();
+      break;
+    }
+    return _mask;
   }
 
   private void _assertTag(int tag) {
@@ -3075,6 +3138,7 @@
     switch (_tag) {
     case ns: return "ns";
     case e: return "e";
+    case pfd: return "pfd";
     }
     throw new IllegalStateException("unknown field: " + _tag);
   }
@@ -3094,6 +3158,7 @@
 union Foo {
   int[] ns = {42};
   ByteEnum  e;
+  ParcelFileDescriptor pfd;
 }
 )");
     io_delegate_.SetFileContents("a/ByteEnum.aidl", R"(
diff --git a/code_writer.h b/code_writer.h
index 469cd5f..4feb63b 100644
--- a/code_writer.h
+++ b/code_writer.h
@@ -16,11 +16,13 @@
 
 #pragma once
 
+#include <stdio.h>
+
+#include <functional>
 #include <memory>
 #include <ostream>
 #include <string>
-
-#include <stdio.h>
+#include <utility>
 
 namespace android {
 namespace aidl {
@@ -37,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 aeeea81..ed57049 100644
--- a/generate_java.cpp
+++ b/generate_java.cpp
@@ -23,8 +23,10 @@
 #include <algorithm>
 #include <map>
 #include <memory>
+#include <optional>
 #include <sstream>
 
+#include <android-base/format.h>
 #include <android-base/stringprintf.h>
 
 #include "aidl_to_java.h"
@@ -36,6 +38,7 @@
 using ::android::base::StartsWith;
 using std::string;
 using std::unique_ptr;
+using std::vector;
 
 namespace {
 // join two non-empty strings according to `camelCase` naming.
@@ -51,6 +54,87 @@
 inline string setter_name(const AidlVariableDeclaration& variable) {
   return camelcase_join("set", variable.GetName(), variable);
 }
+
+// Some types contribute to Parcelable.describeContents().
+// e.g. FileDescriptor, Parcelables, List<Parcelables> ...
+std::optional<string> DescribeContents(const AidlTypenames& types, const AidlTypeSpecifier& type,
+                                       const string& value, int nest_level = 0) {
+  if (type.IsArray() || type.GetName() == "List") {
+    const auto& base_type = type.IsArray() ? type.ArrayBase() : *type.GetTypeParameters()[0];
+    auto base_var = "_v" + std::to_string(nest_level);
+    auto base_describer = DescribeContents(types, base_type, base_var, nest_level + 1);
+    if (base_describer) {
+      return fmt::format(
+          "if ({value} != null) for ({base_type} {base_var}: {value}) {base_describer}",
+          fmt::arg("value", value), fmt::arg("base_type", base_type.GetName()),
+          fmt::arg("base_var", base_var), fmt::arg("base_describer", *base_describer));
+    }
+    return std::nullopt;
+  }
+
+  if (type.GetName() == "Map") {
+    const auto& base_type = type.GetTypeParameters()[1];
+    auto base_var = "_v" + std::to_string(nest_level);
+    auto base_describer = DescribeContents(types, *base_type, base_var, nest_level + 1);
+    if (base_describer) {
+      return fmt::format(
+          "if ({value} != null) for ({base_type} {base_var}: {value}.values()) {base_describer}",
+          fmt::arg("value", value), fmt::arg("base_type", base_type->GetName()),
+          fmt::arg("base_var", base_var), fmt::arg("base_describer", *base_describer));
+    }
+    return std::nullopt;
+  }
+
+  if (type.GetName() == "FileDescriptor") {
+    return fmt::format(
+        "_mask |= ({} != null) ? android.os.Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;\n", value);
+  }
+
+  if (type.GetName() == "ParcelFileDescriptor" || type.GetName() == "ParcelableHolder" ||
+      types.GetParcelable(type) != nullptr) {
+    return fmt::format("if ({} != null) _mask |= {}.describeContents();\n", value, value);
+  }
+
+  return std::nullopt;
+}
+
+void GenerateParcelableDescribeContents(CodeWriter& out, const AidlStructuredParcelable& decl,
+                                        const AidlTypenames& types) {
+  out << "@Override\n";
+  out << "public int describeContents() {\n";
+  out.Indent();
+  out << "int _mask = 0;\n";
+  for (const auto& f : decl.GetFields()) {
+    if (auto describer = DescribeContents(types, f->GetType(), f->GetName()); describer) {
+      out << *describer;
+    }
+  }
+  out << "return _mask;\n";
+  out.Dedent();
+  out << "}\n";
+}
+
+void GenerateParcelableDescribeContents(CodeWriter& out, const AidlUnionDecl& decl,
+                                        const AidlTypenames& types) {
+  out << "@Override\n";
+  out << "public int describeContents() {\n";
+  out.Indent();
+  out << "int _mask = 0;\n";
+  out << "switch (getTag()) {\n";
+  for (const auto& f : decl.GetFields()) {
+    if (auto describer = DescribeContents(types, f->GetType(), getter_name(*f) + "()"); describer) {
+      out << fmt::format("case {}:\n", f->GetName());
+      out.Indent();
+      out << *describer;
+      out << "break;\n";
+      out.Dedent();
+    }
+  }
+  out << "}\n";
+  out << "return _mask;\n";
+  out.Dedent();
+  out << "}\n";
+}
 }  // namespace
 
 namespace android {
@@ -410,13 +494,9 @@
     parcel_class->elements.push_back(std::make_shared<LiteralClassElement>(out.str()));
   }
 
-  auto describe_contents_method = std::make_shared<Method>();
-  describe_contents_method->modifiers = PUBLIC | OVERRIDE;
-  describe_contents_method->returnType = "int";
-  describe_contents_method->name = "describeContents";
-  describe_contents_method->statements = std::make_shared<StatementBlock>();
-  describe_contents_method->statements->Add(std::make_shared<LiteralStatement>("return 0;\n"));
-  parcel_class->elements.push_back(describe_contents_method);
+  auto describe_contents_method =
+      CodeWriter::RunWith(GenerateParcelableDescribeContents, *parcel, typenames);
+  parcel_class->elements.push_back(std::make_shared<LiteralClassElement>(describe_contents_method));
 
   return parcel_class;
 }
@@ -574,15 +654,18 @@
 
   out << "@Override\n";
   out << "public final void writeToParcel(android.os.Parcel _aidl_parcel, int _aidl_flag) {\n";
-  out << "  " + write_to_parcel(tag_type_specifier, tag_name, "_aidl_parcel");
-  out << "  switch (" + tag_name + ") {\n";
+  out.Indent();
+  out << write_to_parcel(tag_type_specifier, tag_name, "_aidl_parcel");
+  out << "switch (" + tag_name + ") {\n";
   for (const auto& variable : decl->GetFields()) {
-    out << "  case " + variable->GetName() + ":\n";
-    out << "    " +
-               write_to_parcel(variable->GetType(), getter_name(*variable) + "()", "_aidl_parcel");
-    out << "    break;\n";
+    out << "case " + variable->GetName() + ":\n";
+    out.Indent();
+    out << write_to_parcel(variable->GetType(), getter_name(*variable) + "()", "_aidl_parcel");
+    out << "break;\n";
+    out.Dedent();
   }
-  out << "  }\n";
+  out << "}\n";
+  out.Dedent();
   out << "}\n\n";
 
   // keep this across different fields in order to create the classloader
@@ -606,26 +689,28 @@
 
   // Not override, but as a user-defined parcelable, this method should be public
   out << "public void readFromParcel(android.os.Parcel _aidl_parcel) {\n";
-  out << "  " + tag_type + " _aidl_tag;\n";
-  out << "  " + read_from_parcel(tag_type_specifier, "_aidl_tag", "_aidl_parcel");
-  out << "  switch (_aidl_tag) {\n";
+  out.Indent();
+  out << tag_type + " _aidl_tag;\n";
+  out << read_from_parcel(tag_type_specifier, "_aidl_tag", "_aidl_parcel");
+  out << "switch (_aidl_tag) {\n";
   for (const auto& variable : decl->GetFields()) {
     auto var_name = variable->GetName();
     auto var_type = JavaSignatureOf(variable->GetType(), typenames);
-    out << "  case " + var_name + ": {\n";
-    out << "    " + var_type + " _aidl_value;\n";
-    out << "    " + read_from_parcel(variable->GetType(), "_aidl_value", "_aidl_parcel");
-    out << "    _set(_aidl_tag, _aidl_value);\n";
-    out << "    return; }\n";
+    out << "case " + var_name + ": {\n";
+    out.Indent();
+    out << var_type + " _aidl_value;\n";
+    out << read_from_parcel(variable->GetType(), "_aidl_value", "_aidl_parcel");
+    out << "_set(_aidl_tag, _aidl_value);\n";
+    out << "return; }\n";
+    out.Dedent();
   }
-  out << "  }\n";
-  out << "  throw new RuntimeException(\"union: out of range: \" + _aidl_tag);\n";
+  out << "}\n";
+  out << "throw new RuntimeException(\"union: out of range: \" + _aidl_tag);\n";
+  out.Dedent();
   out << "}\n\n";
 
-  out << "@Override\n";
-  out << "public int describeContents() {\n";
-  out << "  return 0;\n";
-  out << "}\n\n";
+  GenerateParcelableDescribeContents(out, *decl, typenames);
+  out << "\n";
 
   // helper: _assertTag
   out << "private void _assertTag(" + tag_type + " tag) {\n";
diff --git a/tests/android/aidl/tests/UnionWithFd.aidl b/tests/android/aidl/tests/UnionWithFd.aidl
new file mode 100644
index 0000000..5f59695
--- /dev/null
+++ b/tests/android/aidl/tests/UnionWithFd.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;
+
+union UnionWithFd {
+    int num;
+    ParcelFileDescriptor pfd;
+}
diff --git a/tests/java/src/android/aidl/tests/UnionTests.java b/tests/java/src/android/aidl/tests/UnionTests.java
index 45f9686..c95032b 100644
--- a/tests/java/src/android/aidl/tests/UnionTests.java
+++ b/tests/java/src/android/aidl/tests/UnionTests.java
@@ -19,9 +19,16 @@
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import android.aidl.tests.Union;
+import android.aidl.tests.UnionWithFd;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
 import java.util.Arrays;
 import java.util.List;
 import org.junit.Test;
@@ -67,4 +74,27 @@
 
     parcel.recycle();
   }
+
+  @Test
+  public void unionDescribeContents() {
+    UnionWithFd u = UnionWithFd.num(0);
+    assertTrue((u.describeContents() & Parcelable.CONTENTS_FILE_DESCRIPTOR) == 0);
+
+    final Parcel parcel = Parcel.obtain();
+    try {
+      u.setPfd(ParcelFileDescriptor.open(new File("/system"), ParcelFileDescriptor.MODE_READ_ONLY));
+    } catch (FileNotFoundException e) {
+      throw new RuntimeException("can't open /system", e);
+    }
+    assertTrue((u.describeContents() & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0);
+
+    u.writeToParcel(parcel, 0);
+
+    UnionWithFd v = UnionWithFd.num(0);
+    parcel.setDataPosition(0);
+    v.readFromParcel(parcel);
+    assertTrue((v.describeContents() & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0);
+
+    parcel.recycle();
+  }
 }