1 /*
2  * Copyright (C) 2018, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * limitations under the License.
13  */
14 
15 #include "aidl_to_ndk.h"
16 #include "aidl_language.h"
17 #include "aidl_to_cpp_common.h"
18 #include "logging.h"
19 #include "os.h"
20 
21 #include <android-base/stringprintf.h>
22 #include <android-base/strings.h>
23 
24 #include <functional>
25 
26 using ::android::base::Join;
27 
28 namespace android {
29 namespace aidl {
30 namespace ndk {
31 
NdkHeaderFile(const AidlDefinedType & defined_type,cpp::ClassNames name,bool use_os_sep)32 std::string NdkHeaderFile(const AidlDefinedType& defined_type, cpp::ClassNames name,
33                           bool use_os_sep) {
34   char seperator = (use_os_sep) ? OS_PATH_SEPARATOR : '/';
35   return std::string("aidl") + seperator + cpp::HeaderFile(defined_type, name, use_os_sep);
36 }
37 
38 // This represents a type in AIDL (e.g. 'String' which can be referenced in multiple ways)
39 struct TypeInfo {
40   struct Aspect {
41     // name of the type in C++ output
42     std::string cpp_name;
43     // whether to prefer 'value type' over 'const&'
44     bool value_is_cheap;
45 
46     std::function<void(const CodeGeneratorContext& c)> read_func;
47     std::function<void(const CodeGeneratorContext& c)> write_func;
48   };
49 
50   // e.g. 'String'
51   Aspect raw;
52 
53   // e.g. 'String[]'
54   std::shared_ptr<Aspect> array;
55 
56   // note: Nullable types do not exist in Java. For most Java types, the type is split into a
57   // nullable and non-nullable variant. This is because C++ types are more usually non-nullable, but
58   // everything in Java is non-nullable. This does mean that some Java interfaces may have to have
59   // '@nullable' added to them in order to function as expected w/ the NDK. It also means that some
60   // transactions will be allowed in Java which are not allowed in C++. However, in Java, if a null
61   // is ignored, it will just result in a NullPointerException and be delivered to the other side.
62   // C++ does not have this same capacity (in Android), and so instead, we distinguish nullability
63   // in the type system.
64 
65   // e.g. '@nullable String'
66   std::shared_ptr<Aspect> nullable;
67 
68   // e.g. '@nullable String[]'
69   std::shared_ptr<Aspect> nullable_array;
70 };
71 
ConstantValueDecorator(const AidlTypeSpecifier & type,const std::string & raw_value)72 std::string ConstantValueDecorator(const AidlTypeSpecifier& type, const std::string& raw_value) {
73   if (type.IsArray()) {
74     return raw_value;
75   }
76 
77   if (type.GetName() == "long" && !type.IsArray()) {
78     return raw_value + "L";
79   }
80 
81   if (auto defined_type = type.GetDefinedType(); defined_type) {
82     auto enum_type = defined_type->AsEnumDeclaration();
83     AIDL_FATAL_IF(!enum_type, type) << "Invalid type for \"" << raw_value << "\"";
84     return NdkFullClassName(*enum_type, cpp::ClassNames::RAW) +
85            "::" + raw_value.substr(raw_value.find_last_of('.') + 1);
86   }
87 
88   return raw_value;
89 };
90 
StandardRead(const std::string & name)91 static std::function<void(const CodeGeneratorContext& c)> StandardRead(const std::string& name) {
92   return [name](const CodeGeneratorContext& c) {
93     c.writer << name << "(" << c.parcel << ", " << c.var << ")";
94   };
95 }
StandardWrite(const std::string & name)96 static std::function<void(const CodeGeneratorContext& c)> StandardWrite(const std::string& name) {
97   return [name](const CodeGeneratorContext& c) {
98     c.writer << name << "(" << c.parcel << ", " << c.var << ")";
99   };
100 }
101 
PrimitiveType(const std::string & cpp_name,const std::string & pretty_name,const std::optional<std::string> & cpp_name_for_array_opt=std::nullopt)102 TypeInfo PrimitiveType(const std::string& cpp_name, const std::string& pretty_name,
103                        const std::optional<std::string>& cpp_name_for_array_opt = std::nullopt) {
104   std::string cpp_name_for_array = cpp_name_for_array_opt.value_or(cpp_name);
105   return TypeInfo{
106       .raw =
107           TypeInfo::Aspect{
108               .cpp_name = cpp_name,
109               .value_is_cheap = true,
110               .read_func = StandardRead("AParcel_read" + pretty_name),
111               .write_func = StandardWrite("AParcel_write" + pretty_name),
112           },
113       .array = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
114           .cpp_name = "std::vector<" + cpp_name_for_array + ">",
115           .value_is_cheap = false,
116           .read_func = StandardRead("::ndk::AParcel_readVector"),
117           .write_func = StandardWrite("::ndk::AParcel_writeVector"),
118       }),
119       .nullable = nullptr,
120       .nullable_array = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
121           .cpp_name = "std::optional<std::vector<" + cpp_name_for_array + ">>",
122           .value_is_cheap = false,
123           .read_func = StandardRead("::ndk::AParcel_readVector"),
124           .write_func = StandardWrite("::ndk::AParcel_writeVector"),
125       }),
126   };
127 }
128 
InterfaceTypeInfo(const AidlInterface & type)129 TypeInfo InterfaceTypeInfo(const AidlInterface& type) {
130   const std::string clazz = NdkFullClassName(type, cpp::ClassNames::INTERFACE);
131 
132   return TypeInfo{
133       .raw =
134           TypeInfo::Aspect{
135               .cpp_name = "std::shared_ptr<" + clazz + ">",
136               .value_is_cheap = false,
137               .read_func = StandardRead(clazz + "::readFromParcel"),
138               .write_func = StandardWrite(clazz + "::writeToParcel"),
139           },
140       .array = nullptr,
141       .nullable = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
142           .cpp_name = "std::shared_ptr<" + clazz + ">",
143           .value_is_cheap = false,
144           .read_func = StandardRead(clazz + "::readFromParcel"),
145           .write_func = StandardWrite(clazz + "::writeToParcel"),
146       }),
147       .nullable_array = nullptr,
148   };
149 }
150 
ParcelableTypeInfo(const AidlParcelable & type,const AidlTypeSpecifier & typeSpec,const AidlTypenames & types)151 TypeInfo ParcelableTypeInfo(const AidlParcelable& type, const AidlTypeSpecifier& typeSpec,
152                             const AidlTypenames& types) {
153   std::string clazz = NdkFullClassName(type, cpp::ClassNames::RAW);
154   std::string template_params = "";
155   if (typeSpec.IsGeneric()) {
156     std::vector<std::string> type_params;
157     for (const auto& parameter : typeSpec.GetTypeParameters()) {
158       type_params.push_back(NdkNameOf(types, *parameter, StorageMode::STACK));
159     }
160     clazz += base::StringPrintf("<%s>", base::Join(type_params, ", ").c_str());
161   }
162   return TypeInfo{
163       .raw =
164           TypeInfo::Aspect{
165               .cpp_name = clazz,
166               .value_is_cheap = false,
167               .read_func = StandardRead("::ndk::AParcel_readParcelable"),
168               .write_func = StandardWrite("::ndk::AParcel_writeParcelable"),
169           },
170       .array = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
171           .cpp_name = "std::vector<" + clazz + ">",
172           .value_is_cheap = false,
173           .read_func = StandardRead("::ndk::AParcel_readVector"),
174           .write_func = StandardWrite("::ndk::AParcel_writeVector"),
175       }),
176       .nullable = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
177           .cpp_name = "std::optional<" + clazz + ">",
178           .value_is_cheap = false,
179           .read_func = StandardRead("::ndk::AParcel_readNullableParcelable"),
180           .write_func = StandardWrite("::ndk::AParcel_writeNullableParcelable"),
181       }),
182       .nullable_array = nullptr,
183   };
184 }
185 
EnumDeclarationTypeInfo(const AidlEnumDeclaration & enum_decl)186 TypeInfo EnumDeclarationTypeInfo(const AidlEnumDeclaration& enum_decl) {
187   const std::string clazz = NdkFullClassName(enum_decl, cpp::ClassNames::RAW);
188 
189   static map<std::string, std::string> kAParcelTypeNameMap = {
190       {"byte", "Byte"},
191       {"int", "Int32"},
192       {"long", "Int64"},
193   };
194   auto aparcel_name_it = kAParcelTypeNameMap.find(enum_decl.GetBackingType().GetName());
195   AIDL_FATAL_IF(aparcel_name_it == kAParcelTypeNameMap.end(), enum_decl);
196   const std::string aparcel_name = aparcel_name_it->second;
197 
198   const std::string backing_type_name =
199       NdkNameOf(AidlTypenames(), enum_decl.GetBackingType(), StorageMode::STACK);
200 
201   return TypeInfo{
202       .raw = TypeInfo::Aspect{
203           .cpp_name = clazz,
204           .value_is_cheap = true,
205           .read_func =
206               [aparcel_name, backing_type_name](const CodeGeneratorContext& c) {
207                 c.writer << "AParcel_read" << aparcel_name << "(" << c.parcel
208                          << ", reinterpret_cast<" << backing_type_name << "*>(" << c.var << "))";
209               },
210           .write_func =
211               [aparcel_name, backing_type_name](const CodeGeneratorContext& c) {
212                 c.writer << "AParcel_write" << aparcel_name << "(" << c.parcel << ", static_cast<"
213                          << backing_type_name << ">(" << c.var << "))";
214               },
215       },
216       .array = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
217           .cpp_name = "std::vector<" + clazz + ">",
218           .value_is_cheap = false,
219           .read_func =
220               [aparcel_name, backing_type_name](const CodeGeneratorContext& c) {
221                 c.writer << "AParcel_read" << aparcel_name << "Array(" << c.parcel
222                          << ", static_cast<void*>(" << c.var
223                          << "), ndk::AParcel_stdVectorAllocator<" << backing_type_name << ">)";
224               },
225           .write_func =
226               [aparcel_name, backing_type_name](const CodeGeneratorContext& c) {
227                 c.writer << "AParcel_write" << aparcel_name << "Array(" << c.parcel
228                          << ", reinterpret_cast<const " << backing_type_name << "*>(" << c.var
229                          << ".data()), " << c.var << ".size())";
230               },
231       }),
232       .nullable = nullptr,
233       .nullable_array = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
234           .cpp_name = "std::optional<std::vector<" + clazz + ">>",
235           .value_is_cheap = false,
236           .read_func =
237               [aparcel_name, backing_type_name](const CodeGeneratorContext& c) {
238                 c.writer << "AParcel_read" << aparcel_name << "Array(" << c.parcel
239                          << ", static_cast<void*>(" << c.var
240                          << "), ndk::AParcel_nullableStdVectorAllocator<" << backing_type_name
241                          << ">)";
242               },
243           .write_func =
244               [aparcel_name, backing_type_name](const CodeGeneratorContext& c) {
245                 // If the var exists, use writeArray with data() and size().
246                 // Otherwise, use nullptr and -1.
247                 c.writer << "AParcel_write" << aparcel_name << "Array(" << c.parcel << ", ("
248                          << c.var << " ? reinterpret_cast<const " << backing_type_name << "*>("
249                          << c.var << "->data()) : nullptr)"
250                          << ", (" << c.var << " ? " << c.var << "->size() : -1))";
251               },
252       }),
253   };
254 }
255 
256 // map from AIDL built-in type name to the corresponding Ndk type info
257 static map<std::string, TypeInfo> kNdkTypeInfoMap = {
258     {"void", TypeInfo{{"void", true, nullptr, nullptr}, nullptr, nullptr, nullptr}},
259     {"boolean", PrimitiveType("bool", "Bool")},
260     {"byte", PrimitiveType("int8_t", "Byte", "uint8_t")},
261     {"char", PrimitiveType("char16_t", "Char")},
262     {"int", PrimitiveType("int32_t", "Int32")},
263     {"long", PrimitiveType("int64_t", "Int64")},
264     {"float", PrimitiveType("float", "Float")},
265     {"double", PrimitiveType("double", "Double")},
266     {"String",
267      TypeInfo{
268          .raw =
269              TypeInfo::Aspect{
270                  .cpp_name = "std::string",
271                  .value_is_cheap = false,
272                  .read_func = StandardRead("::ndk::AParcel_readString"),
273                  .write_func = StandardWrite("::ndk::AParcel_writeString"),
274              },
275          .array = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
276              .cpp_name = "std::vector<std::string>",
277              .value_is_cheap = false,
278              .read_func = StandardRead("::ndk::AParcel_readVector"),
279              .write_func = StandardWrite("::ndk::AParcel_writeVector"),
280          }),
281          .nullable = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
282              .cpp_name = "std::optional<std::string>",
283              .value_is_cheap = false,
284              .read_func = StandardRead("::ndk::AParcel_readString"),
285              .write_func = StandardWrite("::ndk::AParcel_writeString"),
286          }),
287          .nullable_array = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
288              .cpp_name = "std::optional<std::vector<std::optional<std::string>>>",
289              .value_is_cheap = false,
290              .read_func = StandardRead("::ndk::AParcel_readVector"),
291              .write_func = StandardWrite("::ndk::AParcel_writeVector"),
292          }),
293      }},
294     // TODO(b/136048684) {"Map", ""},
295     {"IBinder",
296      TypeInfo{
297          .raw =
298              TypeInfo::Aspect{
299                  .cpp_name = "::ndk::SpAIBinder",
300                  .value_is_cheap = false,
301                  .read_func = StandardRead("::ndk::AParcel_readRequiredStrongBinder"),
302                  .write_func = StandardRead("::ndk::AParcel_writeRequiredStrongBinder"),
303              },
304          .array = nullptr,
305          .nullable = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
306              .cpp_name = "::ndk::SpAIBinder",
307              .value_is_cheap = false,
308              .read_func = StandardRead("::ndk::AParcel_readNullableStrongBinder"),
309              .write_func = StandardRead("::ndk::AParcel_writeNullableStrongBinder"),
310          }),
311          .nullable_array = nullptr,
312      }},
313     {"ParcelFileDescriptor",
314      TypeInfo{
315          .raw =
316              TypeInfo::Aspect{
317                  .cpp_name = "::ndk::ScopedFileDescriptor",
318                  .value_is_cheap = false,
319                  .read_func = StandardRead("::ndk::AParcel_readRequiredParcelFileDescriptor"),
320                  .write_func = StandardRead("::ndk::AParcel_writeRequiredParcelFileDescriptor"),
321              },
322          .array = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
323              .cpp_name = "std::vector<::ndk::ScopedFileDescriptor>",
324              .value_is_cheap = false,
325              .read_func = StandardRead("::ndk::AParcel_readVector"),
326              .write_func = StandardWrite("::ndk::AParcel_writeVector"),
327          }),
328          .nullable = std::shared_ptr<TypeInfo::Aspect>(new TypeInfo::Aspect{
329              .cpp_name = "::ndk::ScopedFileDescriptor",
330              .value_is_cheap = false,
331              .read_func = StandardRead("::ndk::AParcel_readNullableParcelFileDescriptor"),
332              .write_func = StandardRead("::ndk::AParcel_writeNullableParcelFileDescriptor"),
333          }),
334          .nullable_array = nullptr,
335      }},
336     {"ParcelableHolder",
337      TypeInfo{
338          .raw =
339              TypeInfo::Aspect{
340                  .cpp_name = "::ndk::AParcelableHolder",
341                  .value_is_cheap = false,
342                  .read_func = StandardRead("::ndk::AParcel_readParcelable"),
343                  .write_func = StandardWrite("::ndk::AParcel_writeParcelable"),
344              },
345          .array = nullptr,
346          .nullable = nullptr,
347          .nullable_array = nullptr,
348      }},
349 };
350 
GetTypeAspect(const AidlTypenames & types,const AidlTypeSpecifier & aidl)351 static TypeInfo::Aspect GetTypeAspect(const AidlTypenames& types, const AidlTypeSpecifier& aidl) {
352   AIDL_FATAL_IF(!aidl.IsResolved(), aidl) << aidl.ToString();
353   auto& aidl_name = aidl.GetName();
354 
355   TypeInfo info;
356 
357   // TODO(b/136048684): For now, List<T> is converted to T[].(Both are using vector<T>)
358   if (aidl_name == "List") {
359     AIDL_FATAL_IF(!aidl.IsGeneric(), aidl) << "List must be generic type.";
360     AIDL_FATAL_IF(aidl.GetTypeParameters().size() != 1, aidl)
361         << "List can accept only one type parameter.";
362     const auto& type_param = aidl.GetTypeParameters()[0];
363     // TODO(b/136048684) AIDL doesn't support nested type parameter yet.
364     AIDL_FATAL_IF(type_param->IsGeneric(), aidl) << "AIDL doesn't support nested type parameter";
365 
366     AidlTypeSpecifier array_type =
367         AidlTypeSpecifier(AIDL_LOCATION_HERE, type_param->GetUnresolvedName(), true /* isArray */,
368                           nullptr /* type_params */, aidl.GetComments());
369     if (!(array_type.Resolve(types) && array_type.CheckValid(types))) {
370       AIDL_FATAL(aidl) << "The type parameter is wrong.";
371     }
372     return GetTypeAspect(types, array_type);
373   }
374 
375   if (AidlTypenames::IsBuiltinTypename(aidl_name)) {
376     auto it = kNdkTypeInfoMap.find(aidl_name);
377     AIDL_FATAL_IF(it == kNdkTypeInfoMap.end(), aidl_name);
378     info = it->second;
379   } else {
380     const AidlDefinedType* type = types.TryGetDefinedType(aidl_name);
381     AIDL_FATAL_IF(type == nullptr, aidl_name) << "Unrecognized type.";
382 
383     if (const AidlInterface* intf = type->AsInterface(); intf != nullptr) {
384       info = InterfaceTypeInfo(*intf);
385     } else if (const AidlParcelable* parcelable = type->AsParcelable(); parcelable != nullptr) {
386       info = ParcelableTypeInfo(*parcelable, aidl, types);
387     } else if (const AidlEnumDeclaration* enum_decl = type->AsEnumDeclaration();
388                enum_decl != nullptr) {
389       info = EnumDeclarationTypeInfo(*enum_decl);
390     } else {
391       AIDL_FATAL(aidl_name) << "Unrecognized type";
392     }
393   }
394 
395   if (aidl.IsArray()) {
396     if (aidl.IsNullable()) {
397       AIDL_FATAL_IF(info.nullable_array == nullptr, aidl)
398           << "Unsupported type in NDK Backend: " << aidl.ToString();
399       return *info.nullable_array;
400     }
401     AIDL_FATAL_IF(info.array == nullptr, aidl)
402         << "Unsupported type in NDK Backend: " << aidl.ToString();
403     return *info.array;
404   }
405 
406   if (aidl.IsNullable()) {
407     AIDL_FATAL_IF(info.nullable == nullptr, aidl)
408         << "Unsupported type in NDK Backend: " << aidl.ToString();
409     return *info.nullable;
410   }
411 
412   return info.raw;
413 }
414 
NdkFullClassName(const AidlDefinedType & type,cpp::ClassNames name)415 std::string NdkFullClassName(const AidlDefinedType& type, cpp::ClassNames name) {
416   std::vector<std::string> pieces = {"::aidl"};
417   std::vector<std::string> package = type.GetSplitPackage();
418   pieces.insert(pieces.end(), package.begin(), package.end());
419   pieces.push_back(cpp::ClassName(type, name));
420 
421   return Join(pieces, "::");
422 }
423 
NdkNameOf(const AidlTypenames & types,const AidlTypeSpecifier & aidl,StorageMode mode)424 std::string NdkNameOf(const AidlTypenames& types, const AidlTypeSpecifier& aidl, StorageMode mode) {
425   TypeInfo::Aspect aspect = GetTypeAspect(types, aidl);
426 
427   switch (mode) {
428     case StorageMode::STACK:
429       return aspect.cpp_name;
430     case StorageMode::ARGUMENT:
431       if (aspect.value_is_cheap) {
432         return aspect.cpp_name;
433       } else {
434         return "const " + aspect.cpp_name + "&";
435       }
436     case StorageMode::OUT_ARGUMENT:
437       return aspect.cpp_name + "*";
438     default:
439       AIDL_FATAL(aidl.GetName()) << "Unrecognized mode type: " << static_cast<int>(mode);
440   }
441 }
442 
NdkAlignmentOf(const AidlTypenames & types,const AidlTypeSpecifier & aidl)443 size_t NdkAlignmentOf(const AidlTypenames& types, const AidlTypeSpecifier& aidl) {
444   // map from NDK type name to the corresponding alignment size
445   static map<string, int> alignment = {
446       {"bool", 1},  {"int8_t", 1},  {"char16_t", 2}, {"double", 8},
447       {"float", 4}, {"int32_t", 4}, {"int64_t", 8},
448   };
449 
450   const string& name = NdkNameOf(types, aidl, StorageMode::STACK);
451   if (alignment.find(name) != alignment.end()) {
452     return alignment[name];
453   } else {
454     const auto& definedType = types.TryGetDefinedType(aidl.GetName());
455     AIDL_FATAL_IF(definedType == nullptr, aidl) << "Failed to resolve type.";
456     if (const auto& enumType = definedType->AsEnumDeclaration(); enumType != nullptr) {
457       return NdkAlignmentOf(types, enumType->GetBackingType());
458     }
459   }
460   return 0;
461 }
462 
WriteToParcelFor(const CodeGeneratorContext & c)463 void WriteToParcelFor(const CodeGeneratorContext& c) {
464   TypeInfo::Aspect aspect = GetTypeAspect(c.types, c.type);
465   aspect.write_func(c);
466 }
467 
ReadFromParcelFor(const CodeGeneratorContext & c)468 void ReadFromParcelFor(const CodeGeneratorContext& c) {
469   TypeInfo::Aspect aspect = GetTypeAspect(c.types, c.type);
470   aspect.read_func(c);
471 }
472 
NdkArgList(const AidlTypenames & types,const AidlMethod & method,std::function<std::string (const std::string & type,const std::string & name,bool isOut)> formatter)473 std::string NdkArgList(
474     const AidlTypenames& types, const AidlMethod& method,
475     std::function<std::string(const std::string& type, const std::string& name, bool isOut)>
476         formatter) {
477   std::vector<std::string> method_arguments;
478   for (const auto& a : method.GetArguments()) {
479     StorageMode mode = a->IsOut() ? StorageMode::OUT_ARGUMENT : StorageMode::ARGUMENT;
480     std::string type = NdkNameOf(types, a->GetType(), mode);
481     std::string name = cpp::BuildVarName(*a);
482     method_arguments.emplace_back(formatter(type, name, a->IsOut()));
483   }
484 
485   if (method.GetType().GetName() != "void") {
486     std::string type = NdkNameOf(types, method.GetType(), StorageMode::OUT_ARGUMENT);
487     std::string name = "_aidl_return";
488     method_arguments.emplace_back(formatter(type, name, true));
489   }
490 
491   return Join(method_arguments, ", ");
492 }
493 
NdkMethodDecl(const AidlTypenames & types,const AidlMethod & method,const std::string & clazz)494 std::string NdkMethodDecl(const AidlTypenames& types, const AidlMethod& method,
495                           const std::string& clazz) {
496   std::string class_prefix = clazz.empty() ? "" : (clazz + "::");
497   return "::ndk::ScopedAStatus " + class_prefix + method.GetName() + "(" +
498          NdkArgList(types, method, FormatArgForDecl) + ")";
499 }
500 
501 }  // namespace ndk
502 }  // namespace aidl
503 }  // namespace android
504