blob: fac699aa98c72a84f999dcf595f1291ec75c60b7 [file] [log] [blame]
Steven Morelandb0057e72018-08-27 01:44:11 -07001/*
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 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Jooyung Han7a9aceb2019-12-17 14:18:15 +000016#include "aidl_to_cpp_common.h"
Steven Morelandb0057e72018-08-27 01:44:11 -070017
Jooyung Han3df81ae2020-10-17 17:59:59 +090018#include <android-base/format.h>
Devin Moore53fc99c2020-08-12 08:07:52 -070019#include <android-base/stringprintf.h>
Jooyung Han7a9aceb2019-12-17 14:18:15 +000020#include <android-base/strings.h>
Jooyung Han3df81ae2020-10-17 17:59:59 +090021
22#include <set>
Jeongik Cha37e2ad52019-04-18 13:44:26 +090023#include <unordered_map>
Steven Morelandb0057e72018-08-27 01:44:11 -070024
Jooyung Han7a9aceb2019-12-17 14:18:15 +000025#include "ast_cpp.h"
Jeongik Cha37e2ad52019-04-18 13:44:26 +090026#include "logging.h"
Steven Morelandb0057e72018-08-27 01:44:11 -070027#include "os.h"
28
Jooyung Han7a9aceb2019-12-17 14:18:15 +000029using ::android::base::Join;
30
Steven Morelandb0057e72018-08-27 01:44:11 -070031namespace android {
32namespace aidl {
33namespace cpp {
34
35string ClassName(const AidlDefinedType& defined_type, ClassNames type) {
Jiyong Park5b7e5322019-04-03 20:05:01 +090036 string base_name = defined_type.GetName();
37 if (base_name.length() >= 2 && base_name[0] == 'I' && isupper(base_name[1])) {
38 base_name = base_name.substr(1);
39 }
Steven Morelandb0057e72018-08-27 01:44:11 -070040
41 switch (type) {
42 case ClassNames::CLIENT:
Jiyong Park5b7e5322019-04-03 20:05:01 +090043 return "Bp" + base_name;
Steven Morelandb0057e72018-08-27 01:44:11 -070044 case ClassNames::SERVER:
Jiyong Park5b7e5322019-04-03 20:05:01 +090045 return "Bn" + base_name;
Steven Morelandb0057e72018-08-27 01:44:11 -070046 case ClassNames::INTERFACE:
Jiyong Park5b7e5322019-04-03 20:05:01 +090047 return "I" + base_name;
Steven Morelandb0057e72018-08-27 01:44:11 -070048 case ClassNames::DEFAULT_IMPL:
Jiyong Park5b7e5322019-04-03 20:05:01 +090049 return "I" + base_name + "Default";
Steven Morelandb0057e72018-08-27 01:44:11 -070050 case ClassNames::BASE:
Jiyong Park5b7e5322019-04-03 20:05:01 +090051 return base_name;
52 case ClassNames::RAW:
53 [[fallthrough]];
54 default:
55 return defined_type.GetName();
Steven Morelandb0057e72018-08-27 01:44:11 -070056 }
Steven Morelandb0057e72018-08-27 01:44:11 -070057}
58
59std::string HeaderFile(const AidlDefinedType& defined_type, ClassNames class_type,
60 bool use_os_sep) {
61 std::string file_path = defined_type.GetPackage();
62 for (char& c : file_path) {
63 if (c == '.') {
64 c = (use_os_sep) ? OS_PATH_SEPARATOR : '/';
65 }
66 }
67 if (!file_path.empty()) {
68 file_path += (use_os_sep) ? OS_PATH_SEPARATOR : '/';
69 }
70 file_path += ClassName(defined_type, class_type);
71 file_path += ".h";
72
73 return file_path;
74}
75
76void EnterNamespace(CodeWriter& out, const AidlDefinedType& defined_type) {
77 const std::vector<std::string> packages = defined_type.GetSplitPackage();
78 for (const std::string& package : packages) {
79 out << "namespace " << package << " {\n";
80 }
81}
82void LeaveNamespace(CodeWriter& out, const AidlDefinedType& defined_type) {
83 const std::vector<std::string> packages = defined_type.GetSplitPackage();
84 for (auto it = packages.rbegin(); it != packages.rend(); ++it) {
85 out << "} // namespace " << *it << "\n";
86 }
87}
88
Steven Moreland3b1325f2018-09-20 14:45:40 -070089string BuildVarName(const AidlArgument& a) {
90 string prefix = "out_";
91 if (a.GetDirection() & AidlArgument::IN_DIR) {
92 prefix = "in_";
93 }
94 return prefix + a.GetName();
95}
96
Jeongik Cha37e2ad52019-04-18 13:44:26 +090097struct TypeInfo {
98 // name of the type in C++ output
99 std::string cpp_name;
100
101 // function that writes an expression to convert a variable to a Json::Value
102 // object
103 std::function<void(CodeWriter& w, const string& var_name, bool isNdk)> toJsonValueExpr;
104};
105
106const static std::unordered_map<std::string, TypeInfo> kTypeInfoMap = {
107 {"void", {"void", nullptr}},
108 {"boolean",
109 {
110 "bool",
111 [](CodeWriter& c, const string& var_name, bool) {
112 c << "Json::Value(" << var_name << "? \"true\" : \"false\")";
113 },
114 }},
115 {"byte",
116 {
117 "int8_t",
118 [](CodeWriter& c, const string& var_name, bool) {
119 c << "Json::Value(" << var_name << ")";
120 },
121 }},
122 {"char",
123 {
124 "char16_t",
125 [](CodeWriter& c, const string& var_name, bool isNdk) {
126 if (isNdk) {
127 c << "Json::Value(" << var_name << ")";
128 } else {
129 c << "Json::Value(std::string(android::String8(&" << var_name << ", 1)))";
130 }
131 },
132 }},
133 {"int",
134 {
135 "int32_t",
136 [](CodeWriter& c, const string& var_name, bool) {
137 c << "Json::Value(" << var_name << ")";
138 },
139 }},
140 {"long",
141 {
142 "int64_t",
143 [](CodeWriter& c, const string& var_name, bool) {
144 c << "Json::Value(static_cast<Json::Int64>(" << var_name << "))";
145 },
146 }},
147 {"float",
148 {
149 "float",
150 [](CodeWriter& c, const string& var_name, bool) {
151 c << "Json::Value(" << var_name << ")";
152 },
153 }},
154 {"double",
155 {
156 "double",
157 [](CodeWriter& c, const string& var_name, bool) {
158 c << "Json::Value(" << var_name << ")";
159 },
160 }},
161 {"String",
162 {
163 "std::string",
164 [](CodeWriter& c, const string& var_name, bool) {
165 c << "Json::Value(" << var_name << ")";
166 },
167 }}
168 // missing List, Map, ParcelFileDescriptor, IBinder
169};
170
171TypeInfo GetTypeInfo(const AidlTypeSpecifier& aidl) {
Steven Moreland21780812020-09-11 01:29:45 +0000172 AIDL_FATAL_IF(!aidl.IsResolved(), aidl) << aidl.ToString();
Jeongik Cha37e2ad52019-04-18 13:44:26 +0900173 const string& aidl_name = aidl.GetName();
174
175 TypeInfo info;
176 if (AidlTypenames::IsBuiltinTypename(aidl_name)) {
177 auto it = kTypeInfoMap.find(aidl_name);
178 if (it != kTypeInfoMap.end()) {
179 info = it->second;
180 }
181 }
182 // Missing interface and parcelable type
183 return info;
184}
185
186inline bool CanWriteLog(const TypeInfo& t) {
187 return t.cpp_name != "";
188}
189
190bool CanWriteLog(const AidlTypeSpecifier& aidl) {
191 return CanWriteLog(GetTypeInfo(aidl));
192}
193
194void WriteLogFor(CodeWriter& writer, const AidlTypeSpecifier& type, const std::string& name,
195 bool isPointer, const std::string& log, bool isNdk) {
196 const TypeInfo info = GetTypeInfo(type);
197 if (!CanWriteLog(info)) {
198 return;
199 }
200
201 const string var_object_expr = ((isPointer ? "*" : "")) + name;
202 if (type.IsArray()) {
203 writer << log << " = Json::Value(Json::arrayValue);\n";
204 writer << "for (const auto& v: " << var_object_expr << ") " << log << ".append(";
205 info.toJsonValueExpr(writer, "v", isNdk);
206 writer << ");";
207 } else {
208 writer << log << " = ";
209 info.toJsonValueExpr(writer, var_object_expr, isNdk);
210 writer << ";";
211 }
212 writer << "\n";
213}
214
215void WriteLogForArguments(CodeWriterPtr& writer, const AidlArgument& a, bool isServer,
216 string logVarName, bool isNdk) {
217 if (!CanWriteLog(a.GetType())) {
218 return;
219 }
220 string logElementVarName = "_log_arg_element";
221 (*writer) << "{\n";
222 (*writer).Indent();
223 (*writer) << "Json::Value " << logElementVarName << "(Json::objectValue);\n";
224 string varName = isServer || isNdk ? BuildVarName(a) : a.GetName();
225 (*writer) << logElementVarName << "[\"name\"] = \"" << varName << "\";\n";
226
227 bool isPointer = a.IsOut() && !isServer;
228 WriteLogFor(*(writer.get()), a.GetType(), varName, isPointer, logElementVarName + "[\"value\"]",
229 isNdk);
230 (*writer) << logVarName << ".append(" << logElementVarName << ");\n";
231 (*writer) << "}\n";
232 (*writer).Dedent();
233}
234
235const string GenLogBeforeExecute(const string className, const AidlMethod& method, bool isServer,
236 bool isNdk) {
237 string code;
238 CodeWriterPtr writer = CodeWriter::ForString(&code);
239 (*writer) << "Json::Value _log_input_args(Json::arrayValue);\n";
240
241 (*writer) << "if (" << className << "::logFunc != nullptr) {\n";
242 (*writer).Indent();
243
244 for (const auto& a : method.GetArguments()) {
245 if (a->IsIn()) {
246 WriteLogForArguments(writer, *a, isServer, "_log_input_args", isNdk);
247 }
248 }
249
250 (*writer).Dedent();
251 (*writer) << "}\n";
252
253 (*writer) << "auto _log_start = std::chrono::steady_clock::now();\n";
254 writer->Close();
255 return code;
256}
257
258const string GenLogAfterExecute(const string className, const AidlInterface& interface,
259 const AidlMethod& method, const string& statusVarName,
260 const string& returnVarName, bool isServer, bool isNdk) {
261 string code;
262 CodeWriterPtr writer = CodeWriter::ForString(&code);
263
264 (*writer) << "if (" << className << "::logFunc != nullptr) {\n";
265 (*writer).Indent();
266
267 // Write the log as a Json object. For example,
268 //
269 // Json log object for following interface description
270 //
271 // package foo.bar;
272 // interface IFoo {
273 // String TestMethod(int arg1, inout String[] arg2, out double arg3);
274 // }
275 //
276 // would be:
277 //
278 // {
279 // duration_ms: 100.42,
280 // interface_name: "foo.bar.IFoo",
281 // method_name: "TestMethod",
282 // (proxy|stub)_address: "0x12345678",
283 // input_args: [
284 // {name: "arg1", value: 30,},
285 // {name: "arg2", value: ["apple", "grape"],},
286 // ],
287 // output_args: [
288 // {name: "arg2", value: ["mango", "banana"],},
289 // {name: "arg3", value: "10.5",},
290 // ],
291 // _aidl_return: "ok",
292 // binder_status: {
293 // exception_code: -8,
294 // exception_message: "Something wrong",
295 // transaction_error: 0,
296 // service_specific_error_code: -42,
297 // },
298 // }
299 (*writer) << "auto _log_end = std::chrono::steady_clock::now();\n";
300 (*writer) << "Json::Value _log_transaction(Json::objectValue);\n";
301 (*writer) << "_log_transaction[\"duration_ms\"] = "
302 << "std::chrono::duration<double, std::milli>(_log_end - "
303 "_log_start).count();\n";
304 (*writer) << "_log_transaction[\"interface_name\"] = "
305 << "Json::Value(\"" << interface.GetCanonicalName() << "\");\n";
306 (*writer) << "_log_transaction[\"method_name\"] = "
307 << "Json::Value(\"" << method.GetName() << "\");\n";
308
309 (*writer) << "_log_transaction[\"" << (isServer ? "stub_address" : "proxy_address") << "\"] = ";
310 (*writer) << "Json::Value("
311 << "(std::ostringstream() << "
312 << (isNdk && isServer ? "_aidl_impl" : "static_cast<const void*>(this)") << ").str()"
313 << ");\n";
314 (*writer) << "_log_transaction[\"input_args\"] = _log_input_args;\n";
315 (*writer) << "Json::Value _log_output_args(Json::arrayValue);\n";
316
317 (*writer) << "Json::Value _log_status(Json::objectValue);\n";
318 if (isNdk) {
319 (*writer) << "_log_status[\"exception_code\"] = Json::Value(AStatus_getExceptionCode("
320 << statusVarName << ".get()));\n";
321 (*writer) << "_log_status[\"exception_message\"] = Json::Value(AStatus_getMessage("
322 << statusVarName << ".get()));\n";
323 (*writer) << "_log_status[\"transaction_error\"] = Json::Value(AStatus_getStatus("
324 << statusVarName << ".get()));\n";
Jeongik Chaeaf978e2019-05-04 00:32:35 +0900325 (*writer) << "_log_status[\"service_specific_error_code\"] = "
326 "Json::Value(AStatus_getServiceSpecificError("
Jeongik Cha37e2ad52019-04-18 13:44:26 +0900327 << statusVarName << ".get()));\n";
328 } else {
329 (*writer) << "_log_status[\"exception_code\"] = Json::Value(" << statusVarName
330 << ".exceptionCode());\n";
331 (*writer) << "_log_status[\"exception_message\"] = Json::Value(" << statusVarName
332 << ".exceptionMessage());\n";
333 (*writer) << "_log_status[\"transaction_error\"] = Json::Value(" << statusVarName
334 << ".transactionError());\n";
335 (*writer) << "_log_status[\"service_specific_error_code\"] = Json::Value(" << statusVarName
336 << ".serviceSpecificErrorCode());\n";
337 }
338
339 (*writer) << "_log_transaction[\"binder_status\"] = _log_status;\n";
340
341 for (const auto& a : method.GetOutArguments()) {
342 WriteLogForArguments(writer, *a, isServer, "_log_output_args", isNdk);
343 }
344
345 (*writer) << "_log_transaction[\"output_args\"] = _log_output_args;\n";
346
347 if (method.GetType().GetName() != "void") {
348 WriteLogFor(*(writer.get()), method.GetType(), returnVarName, !isServer,
349 "_log_transaction[\"" + returnVarName + "\"]", isNdk);
350 }
351
352 // call the user-provided function with the Json object for the entire
353 // transaction
354 (*writer) << className << "::logFunc(_log_transaction);\n";
355
356 (*writer).Dedent();
357 (*writer) << "}\n";
358
359 writer->Close();
360 return code;
361}
362
Jooyung Han7a9aceb2019-12-17 14:18:15 +0000363std::string GenerateEnumValues(const AidlEnumDeclaration& enum_decl,
364 const std::vector<std::string>& enclosing_namespaces_of_enum_decl) {
365 const auto fq_name =
366 Join(Append(enclosing_namespaces_of_enum_decl, enum_decl.GetSplitPackage()), "::") +
367 "::" + enum_decl.GetName();
368 const auto size = enum_decl.GetEnumerators().size();
369 std::ostringstream code;
Jooyung Han0e2a03c2019-12-17 23:25:39 +0900370 code << "#pragma clang diagnostic push\n";
371 code << "#pragma clang diagnostic ignored \"-Wc++17-extensions\"\n";
Jooyung Han7a9aceb2019-12-17 14:18:15 +0000372 code << "template <>\n";
373 code << "constexpr inline std::array<" << fq_name << ", " << size << "> enum_values<" << fq_name
374 << "> = {\n";
375 for (const auto& enumerator : enum_decl.GetEnumerators()) {
376 code << " " << fq_name << "::" << enumerator->GetName() << ",\n";
377 }
378 code << "};\n";
Jooyung Han0e2a03c2019-12-17 23:25:39 +0900379 code << "#pragma clang diagnostic pop\n";
Jooyung Han7a9aceb2019-12-17 14:18:15 +0000380 return code.str();
381}
382
Jooyung Han6beac042020-10-17 21:59:43 +0900383std::string TemplateDecl(const AidlParcelable& defined_type) {
Devin Moore53fc99c2020-08-12 08:07:52 -0700384 std::string decl = "";
385 if (defined_type.IsGeneric()) {
386 std::vector<std::string> template_params;
387 for (const auto& parameter : defined_type.GetTypeParameters()) {
388 template_params.push_back(parameter);
389 }
390 decl = base::StringPrintf("template <typename %s>\n",
391 base::Join(template_params, ", typename ").c_str());
392 }
393 return decl;
394}
395
Jooyung Han3df81ae2020-10-17 17:59:59 +0900396void GenerateParcelableComparisonOperators(CodeWriter& out, const AidlParcelable& parcelable) {
397 std::set<string> operators{"<", ">", "==", ">=", "<=", "!="};
398 bool is_empty = false;
399
400 auto comparable = [&](const string& prefix) {
401 vector<string> fields;
402 if (auto p = parcelable.AsStructuredParcelable(); p != nullptr) {
403 is_empty = p->GetFields().empty();
404 for (const auto& f : p->GetFields()) {
405 fields.push_back(prefix + f->GetName());
406 }
407 return "std::tie(" + Join(fields, ", ") + ")";
408 } else if (auto p = parcelable.AsUnionDeclaration(); p != nullptr) {
409 return prefix + "_value";
410 } else {
411 AIDL_FATAL(parcelable) << "Unknown paracelable type";
412 }
413 };
414
415 string lhs = comparable("");
416 string rhs = comparable("rhs.");
417 for (const auto& op : operators) {
418 out << "inline bool operator" << op << "(const " << parcelable.GetName() << "&"
419 << (is_empty ? "" : " rhs") << ") const {\n"
420 << " return " << lhs << " " << op << " " << rhs << ";\n"
421 << "}\n";
422 }
423 out << "\n";
424}
425
426const vector<string> UnionWriter::headers{
427 "type_traits", // std::is_same_v
428 "utility", // std::mode/forward for value
429 "variant", // std::variant for value
430};
431
432void UnionWriter::PrivateFields(CodeWriter& out) const {
433 vector<string> field_types;
434 for (const auto& f : decl.GetFields()) {
435 field_types.push_back(name_of(f->GetType(), typenames));
436 }
437 out << "std::variant<" + Join(field_types, ", ") + "> _value;\n";
438}
439
440void UnionWriter::PublicFields(CodeWriter& out) const {
441 AidlTypeSpecifier tag_type(AIDL_LOCATION_HERE, "int", /* is_array= */ false,
442 /* type_params= */ nullptr, /* comments= */ "");
443 tag_type.Resolve(typenames);
444
445 out << "enum Tag : " << name_of(tag_type, typenames) << " {\n";
446 bool is_first = true;
447 for (const auto& f : decl.GetFields()) {
448 out << " " << f->GetName() << (is_first ? " = 0" : "") << ", // " << f->Signature() << ";\n";
449 is_first = false;
450 }
451 out << "};\n";
452
453 const auto& name = decl.GetName();
454
455 AIDL_FATAL_IF(decl.GetFields().empty(), decl) << "Union '" << name << "' is empty.";
456 const auto& first_field = decl.GetFields()[0];
457 const auto& default_name = first_field->GetName();
458 const auto& default_value =
459 name_of(first_field->GetType(), typenames) + "(" + first_field->ValueString(decorator) + ")";
460
461 auto tmpl = R"--(
462template<typename _Tp>
463static constexpr bool _not_self = !std::is_same_v<std::remove_cv_t<std::remove_reference_t<_Tp>>, {name}>;
464
465{name}() : _value(std::in_place_index<{default_name}>, {default_value}) {{ }}
466{name}(const {name}&) = default;
467{name}({name}&&) = default;
468{name}& operator=(const {name}&) = default;
469{name}& operator=({name}&&) = default;
470
471template <typename _Tp, std::enable_if_t<_not_self<_Tp>, int> = 0>
472constexpr {name}(_Tp&& _arg)
473 : _value(std::forward<_Tp>(_arg)) {{}}
474
475template <typename... _Tp>
476constexpr explicit {name}(_Tp&&... _args)
477 : _value(std::forward<_Tp>(_args)...) {{}}
478
479template <Tag _tag, typename... _Tp>
480static {name} make(_Tp&&... _args) {{
481 return {name}(std::in_place_index<_tag>, std::forward<_Tp>(_args)...);
482}}
483
484template <Tag _tag, typename _Tp, typename... _Up>
485static {name} make(std::initializer_list<_Tp> _il, _Up&&... _args) {{
486 return {name}(std::in_place_index<_tag>, std::move(_il), std::forward<_Up>(_args)...);
487}}
488
489Tag getTag() const {{
490 return static_cast<Tag>(_value.index());
491}}
492
493template <Tag _tag>
494const auto& get() const {{
495 if (getTag() != _tag) {{ abort(); }}
496 return std::get<_tag>(_value);
497}}
498
499template <Tag _tag>
500auto& get() {{
501 if (getTag() != _tag) {{ abort(); }}
502 return std::get<_tag>(_value);
503}}
504
505template <Tag _tag, typename... _Tp>
506void set(_Tp&&... _args) {{
507 _value.emplace<_tag>(std::forward<_Tp>(_args)...);
508}}
509
510)--";
511 out << fmt::format(tmpl, fmt::arg("name", name), fmt::arg("default_name", default_name),
512 fmt::arg("default_value", default_value));
513}
514
515void UnionWriter::ReadFromParcel(CodeWriter& out, const ParcelWriterContext& ctx) const {
516 AidlTypeSpecifier tag_type(AIDL_LOCATION_HERE, "int", /* is_array= */ false,
517 /* type_params= */ nullptr, /* comments= */ "");
518 tag_type.Resolve(typenames);
519
520 const string tag = "_aidl_tag";
521 const string value = "_aidl_value";
522 const string status = "_aidl_ret_status";
523
524 auto read_var = [&](const string& var, const AidlTypeSpecifier& type) {
525 out << fmt::format("{} {};\n", name_of(type, typenames), var);
526 out << fmt::format("if (({} = ", status);
527 ctx.read_func(out, var, type);
528 out << fmt::format(") != {}) return {};\n", ctx.status_ok, status);
529 };
530
531 out << fmt::format("{} {};\n", ctx.status_type, status);
532 read_var(tag, tag_type);
533 out << fmt::format("switch ({}) {{\n", tag);
534 for (const auto& variable : decl.GetFields()) {
535 out << fmt::format("case {}: {{\n", variable->GetName());
536 out.Indent();
537 read_var(value, variable->GetType());
538 out << fmt::format("set<{}>(std::move({}));\n", variable->GetName(), value);
539 out << fmt::format("return {}; }}\n", ctx.status_ok);
540 out.Dedent();
541 }
542 out << "}\n";
543 out << fmt::format("return {};\n", ctx.status_bad);
544}
545
546void UnionWriter::WriteToParcel(CodeWriter& out, const ParcelWriterContext& ctx) const {
547 AidlTypeSpecifier tag_type(AIDL_LOCATION_HERE, "int", /* is_array= */ false,
548 /* type_params= */ nullptr, /* comments= */ "");
549 tag_type.Resolve(typenames);
550
551 const string tag = "_aidl_tag";
552 const string value = "_aidl_value";
553 const string status = "_aidl_ret_status";
554
555 out << fmt::format("{} {} = ", ctx.status_type, status);
556 ctx.write_func(out, "getTag()", tag_type);
557 out << ";\n";
558 out << fmt::format("if ({} != {}) return {};\n", status, ctx.status_ok, status);
559 out << "switch (getTag()) {\n";
560 for (const auto& variable : decl.GetFields()) {
561 out << fmt::format("case {}: return ", variable->GetName());
562 ctx.write_func(out, "get<" + variable->GetName() + ">()", variable->GetType());
563 out << ";\n";
564 }
565 out << "}\n";
566 out << "abort();\n";
567}
568
Steven Morelandb0057e72018-08-27 01:44:11 -0700569} // namespace cpp
570} // namespace aidl
571} // namespace android