1 /*
2 * Copyright (C) 2019, 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 */
16
17 #include "java_writer.h"
18
19 #include <stdio.h>
20
21 #include <algorithm>
22
23 #include "Collation.h"
24 #include "java_writer_q.h"
25 #include "utils.h"
26
27 namespace android {
28 namespace stats_log_api_gen {
29
write_java_q_logger_class(FILE * out,const SignatureInfoMap & signatureInfoMap,const AtomDecl & attributionDecl)30 static int write_java_q_logger_class(FILE* out, const SignatureInfoMap& signatureInfoMap,
31 const AtomDecl& attributionDecl) {
32 fprintf(out, "\n");
33 fprintf(out, " // Write logging helper methods for statsd in Q and earlier.\n");
34 fprintf(out, " private static class QLogger {\n");
35
36 write_java_q_logging_constants(out, " ");
37
38 // Print Q write methods.
39 fprintf(out, "\n");
40 fprintf(out, " // Write methods.\n");
41 write_java_methods_q_schema(out, signatureInfoMap, attributionDecl, " ");
42
43 fprintf(out, " }\n");
44 return 0;
45 }
46
47 // TODO(b/330817229): Get rid of these generated constants and inline the logic inside the generated
48 // write/buildStatsEvent methods. Also add @RequiresApi support for annotations added in S and
49 // earlier.
write_java_annotation_constants(FILE * out,const int minApiLevel)50 static void write_java_annotation_constants(FILE* out, const int minApiLevel) {
51 fprintf(out, " // Annotation constants.\n");
52
53 const map<AnnotationId, AnnotationStruct>& ANNOTATION_ID_CONSTANTS =
54 get_annotation_id_constants(ANNOTATION_CONSTANT_NAME_PREFIX);
55 for (const auto& [id, annotation] : ANNOTATION_ID_CONSTANTS) {
56 if (annotation.minApiLevel >= API_U) { // we don't generate annotation constants for U+
57 continue;
58 }
59 fprintf(out, " @android.annotation.SuppressLint(\"InlinedApi\")\n");
60 if (minApiLevel <= API_R) {
61 fprintf(out, " public static final byte %s =\n", annotation.name.c_str());
62 fprintf(out, " Build.VERSION.SDK_INT <= %s ?\n",
63 get_java_build_version_code(API_R).c_str());
64 fprintf(out, " %hhu : StatsLog.%s;\n", id, annotation.name.c_str());
65 } else {
66 fprintf(out, " public static final byte %s = StatsLog.%s;\n",
67 annotation.name.c_str(), annotation.name.c_str());
68 }
69 fprintf(out, "\n");
70 }
71
72 fprintf(out, "\n");
73 }
74
write_annotations(FILE * out,int argIndex,const FieldNumberToAtomDeclSet & fieldNumberToAtomDeclSet)75 static void write_annotations(FILE* out, int argIndex,
76 const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet) {
77 const FieldNumberToAtomDeclSet::const_iterator fieldNumberToAtomDeclSetIt =
78 fieldNumberToAtomDeclSet.find(argIndex);
79 if (fieldNumberToAtomDeclSet.end() == fieldNumberToAtomDeclSetIt) {
80 return;
81 }
82 const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second;
83 const map<AnnotationId, AnnotationStruct>& ANNOTATION_ID_CONSTANTS =
84 get_annotation_id_constants(ANNOTATION_CONSTANT_NAME_PREFIX);
85 for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) {
86 const string atomConstant = make_constant_name(atomDecl->name);
87 fprintf(out, " if (%s == code) {\n", atomConstant.c_str());
88 const AnnotationSet& annotations = atomDecl->fieldNumberToAnnotations.at(argIndex);
89 int resetState = -1;
90 int defaultState = -1;
91 for (const shared_ptr<Annotation>& annotation : annotations) {
92 const AnnotationStruct& annotationConstant =
93 ANNOTATION_ID_CONSTANTS.at(annotation->annotationId);
94 const char *prefix = annotationConstant.minApiLevel >= API_U ? "StatsLog." : "";
95 switch (annotation->type) {
96 case ANNOTATION_TYPE_INT:
97 if (ANNOTATION_ID_TRIGGER_STATE_RESET == annotation->annotationId) {
98 resetState = annotation->value.intValue;
99 } else if (ANNOTATION_ID_DEFAULT_STATE == annotation->annotationId) {
100 defaultState = annotation->value.intValue;
101 } else if (ANNOTATION_ID_RESTRICTION_CATEGORY == annotation->annotationId) {
102 fprintf(out, " builder.addIntAnnotation(%s%s,\n",
103 prefix, annotationConstant.name.c_str());
104 fprintf(out, " %s%s);\n",
105 prefix, get_restriction_category_str(annotation->value.intValue)
106 .c_str());
107 } else {
108 fprintf(out, " builder.addIntAnnotation(%s%s, %d);\n",
109 prefix, annotationConstant.name.c_str(),
110 annotation->value.intValue);
111 }
112 break;
113 case ANNOTATION_TYPE_BOOL:
114 fprintf(out, " builder.addBooleanAnnotation(%s%s, %s);\n",
115 prefix, annotationConstant.name.c_str(),
116 annotation->value.boolValue ? "true" : "false");
117 break;
118 default:
119 break;
120 }
121 }
122 if (defaultState != -1 && resetState != -1) {
123 const AnnotationStruct& annotationConstant =
124 ANNOTATION_ID_CONSTANTS.at(ANNOTATION_ID_TRIGGER_STATE_RESET);
125 fprintf(out, " if (arg%d == %d) {\n", argIndex, resetState);
126 fprintf(out, " builder.addIntAnnotation(%s, %d);\n",
127 annotationConstant.name.c_str(), defaultState);
128 fprintf(out, " }\n");
129 }
130 fprintf(out, " }\n");
131 }
132 }
133
write_method_body(FILE * out,const vector<java_type_t> & signature,const FieldNumberToAtomDeclSet & fieldNumberToAtomDeclSet,const AtomDecl & attributionDecl,const string & indent)134 static int write_method_body(FILE* out, const vector<java_type_t>& signature,
135 const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet,
136 const AtomDecl& attributionDecl, const string& indent) {
137 // Start StatsEvent.Builder.
138 fprintf(out,
139 "%s final StatsEvent.Builder builder = "
140 "StatsEvent.newBuilder();\n",
141 indent.c_str());
142
143 // Write atom code.
144 fprintf(out, "%s builder.setAtomId(code);\n", indent.c_str());
145 write_annotations(out, ATOM_ID_FIELD_NUMBER, fieldNumberToAtomDeclSet);
146
147 // Write the args.
148 int argIndex = 1;
149 for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end();
150 arg++) {
151 switch (*arg) {
152 case JAVA_TYPE_BOOLEAN:
153 fprintf(out, "%s builder.writeBoolean(arg%d);\n", indent.c_str(), argIndex);
154 break;
155 case JAVA_TYPE_INT:
156 case JAVA_TYPE_ENUM:
157 fprintf(out, "%s builder.writeInt(arg%d);\n", indent.c_str(), argIndex);
158 break;
159 case JAVA_TYPE_FLOAT:
160 fprintf(out, "%s builder.writeFloat(arg%d);\n", indent.c_str(), argIndex);
161 break;
162 case JAVA_TYPE_LONG:
163 fprintf(out, "%s builder.writeLong(arg%d);\n", indent.c_str(), argIndex);
164 break;
165 case JAVA_TYPE_STRING:
166 fprintf(out, "%s builder.writeString(arg%d);\n", indent.c_str(), argIndex);
167 break;
168 case JAVA_TYPE_BYTE_ARRAY:
169 fprintf(out,
170 "%s builder.writeByteArray(null == arg%d ? new byte[0] : "
171 "arg%d);\n",
172 indent.c_str(), argIndex, argIndex);
173 break;
174 case JAVA_TYPE_BOOLEAN_ARRAY:
175 fprintf(out,
176 "%s builder.writeBooleanArray(null == arg%d ? new boolean[0] : "
177 "arg%d);\n",
178 indent.c_str(), argIndex, argIndex);
179 break;
180 case JAVA_TYPE_INT_ARRAY:
181 fprintf(out,
182 "%s builder.writeIntArray(null == arg%d ? new int[0] : arg%d);\n",
183 indent.c_str(), argIndex, argIndex);
184 break;
185 case JAVA_TYPE_FLOAT_ARRAY:
186 fprintf(out,
187 "%s builder.writeFloatArray(null == arg%d ? new float[0] : "
188 "arg%d);\n",
189 indent.c_str(), argIndex, argIndex);
190 break;
191 case JAVA_TYPE_LONG_ARRAY:
192 fprintf(out,
193 "%s builder.writeLongArray(null == arg%d ? new long[0] : arg%d);\n",
194 indent.c_str(), argIndex, argIndex);
195 break;
196 case JAVA_TYPE_STRING_ARRAY:
197 fprintf(out,
198 "%s builder.writeStringArray(null == arg%d ? new String[0] : "
199 "arg%d);\n",
200 indent.c_str(), argIndex, argIndex);
201 break;
202 case JAVA_TYPE_ATTRIBUTION_CHAIN: {
203 const char* uidName = attributionDecl.fields.front().name.c_str();
204 const char* tagName = attributionDecl.fields.back().name.c_str();
205
206 fprintf(out, "%s builder.writeAttributionChain(\n", indent.c_str());
207 fprintf(out, "%s null == %s ? new int[0] : %s,\n", indent.c_str(),
208 uidName, uidName);
209 fprintf(out, "%s null == %s ? new String[0] : %s);\n",
210 indent.c_str(), tagName, tagName);
211 break;
212 }
213 default:
214 // Unsupported types: OBJECT, DOUBLE.
215 fprintf(stderr, "Encountered unsupported type.");
216 return 1;
217 }
218 write_annotations(out, argIndex, fieldNumberToAtomDeclSet);
219 argIndex++;
220 }
221 return 0;
222 }
223
write_requires_api_annotation(FILE * out,int minApiLevel,const FieldNumberToAtomDeclSet & fieldNumberToAtomDeclSet,const vector<java_type_t> & signature)224 static void write_requires_api_annotation(FILE* out, int minApiLevel,
225 const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet,
226 const vector<java_type_t>& signature) {
227 const auto fieldNumberToAtomDeclSetIt = fieldNumberToAtomDeclSet.find(ATOM_ID_FIELD_NUMBER);
228 const AtomDeclSet* atomDeclSet = fieldNumberToAtomDeclSetIt == fieldNumberToAtomDeclSet.end()
229 ? nullptr
230 : &fieldNumberToAtomDeclSetIt->second;
231 const int maxRequiresApiLevel = get_max_requires_api_level(minApiLevel, atomDeclSet, signature);
232 if (maxRequiresApiLevel > 0) {
233 // Suppress lint error complaining the @RequiresApi annotation is unnecessary because
234 // minSdk of the binary is already at least the SDK level specified in @RequiresApi.
235 fprintf(out, " @android.annotation.SuppressLint(\"ObsoleteSdkInt\")\n");
236 fprintf(out, " @RequiresApi(%s)\n",
237 get_java_build_version_code(maxRequiresApiLevel).c_str());
238 }
239 }
240
write_java_pushed_methods(FILE * out,const SignatureInfoMap & signatureInfoMap,const AtomDecl & attributionDecl,const int minApiLevel,const bool staticMethods)241 static int write_java_pushed_methods(FILE* out, const SignatureInfoMap& signatureInfoMap,
242 const AtomDecl& attributionDecl, const int minApiLevel,
243 const bool staticMethods) {
244 const char* methodPrefix = staticMethods ? "static " : "";
245 for (auto signatureInfoMapIt = signatureInfoMap.begin();
246 signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) {
247 const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet = signatureInfoMapIt->second;
248 const vector<java_type_t>& signature = signatureInfoMapIt->first;
249 write_requires_api_annotation(out, minApiLevel, fieldNumberToAtomDeclSet, signature);
250 // Print method signature.
251 fprintf(out, " public %svoid write(int code", methodPrefix);
252 write_java_method_signature(out, signature, attributionDecl);
253 fprintf(out, ") {\n");
254
255 // Print method body.
256 string indent("");
257 if (minApiLevel == API_Q) {
258 fprintf(out, " if (Build.VERSION.SDK_INT > %s) {\n",
259 get_java_build_version_code(API_Q).c_str());
260 indent = " ";
261 }
262
263 const int ret = write_method_body(out, signature, fieldNumberToAtomDeclSet, attributionDecl,
264 indent);
265 if (ret != 0) {
266 return ret;
267 }
268 fprintf(out, "\n");
269
270 fprintf(out, "%s builder.usePooledBuffer();\n", indent.c_str());
271 fprintf(out, "%s StatsLog.write(builder.build());\n", indent.c_str());
272
273 // Add support for writing using Q schema if this is not the default module.
274 if (minApiLevel == API_Q) {
275 fprintf(out, " } else {\n");
276 fprintf(out, " QLogger.write(code");
277 int argIndex = 1;
278 for (vector<java_type_t>::const_iterator arg = signature.begin();
279 arg != signature.end(); arg++) {
280 if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
281 const char* uidName = attributionDecl.fields.front().name.c_str();
282 const char* tagName = attributionDecl.fields.back().name.c_str();
283 fprintf(out, ", %s, %s", uidName, tagName);
284 } else if (is_repeated_field(*arg)) {
285 // Module logging does not support repeated fields.
286 fprintf(stderr, "Module logging does not support repeated fields.\n");
287 return 1;
288 } else {
289 fprintf(out, ", arg%d", argIndex);
290 }
291 argIndex++;
292 }
293 fprintf(out, ");\n");
294 fprintf(out, " }\n"); // if
295 }
296
297 fprintf(out, " }\n"); // method
298 fprintf(out, "\n");
299 }
300 return 0;
301 }
302
write_java_pulled_methods(FILE * out,const SignatureInfoMap & signatureInfoMap,const AtomDecl & attributionDecl,const int minApiLevel,const bool staticMethods)303 static int write_java_pulled_methods(FILE* out, const SignatureInfoMap& signatureInfoMap,
304 const AtomDecl& attributionDecl, const int minApiLevel,
305 const bool staticMethods) {
306 const char* methodPrefix = staticMethods ? "static " : "";
307 for (auto signatureInfoMapIt = signatureInfoMap.begin();
308 signatureInfoMapIt != signatureInfoMap.end(); signatureInfoMapIt++) {
309 const vector<java_type_t>& signature = signatureInfoMapIt->first;
310 const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet = signatureInfoMapIt->second;
311 write_requires_api_annotation(out, minApiLevel, fieldNumberToAtomDeclSet, signature);
312 // Print method signature.
313 fprintf(out, " public %sStatsEvent buildStatsEvent(int code", methodPrefix);
314 int ret = write_java_method_signature(out, signature, attributionDecl);
315 if (ret != 0) {
316 return ret;
317 }
318 fprintf(out, ") {\n");
319
320 // Print method body.
321 const string indent("");
322 ret = write_method_body(out, signature, fieldNumberToAtomDeclSet, attributionDecl, indent);
323 if (ret != 0) {
324 return ret;
325 }
326 fprintf(out, "\n");
327
328 fprintf(out, "%s return builder.build();\n", indent.c_str());
329
330 fprintf(out, " }\n"); // method
331 fprintf(out, "\n");
332 }
333 return 0;
334 }
335
get_max_requires_api_level(int minApiLevel,const SignatureInfoMap & signatureInfoMap)336 static int get_max_requires_api_level(int minApiLevel, const SignatureInfoMap& signatureInfoMap) {
337 int maxRequiresApiLevel = 0;
338 for (const auto& [signature, fieldNumberToAtomDeclSet] : signatureInfoMap) {
339 const auto fieldNumberToAtomDeclSetIt = fieldNumberToAtomDeclSet.find(ATOM_ID_FIELD_NUMBER);
340 const AtomDeclSet* atomDeclSet =
341 fieldNumberToAtomDeclSetIt == fieldNumberToAtomDeclSet.end()
342 ? nullptr
343 : &fieldNumberToAtomDeclSetIt->second;
344 maxRequiresApiLevel =
345 std::max(maxRequiresApiLevel,
346 get_max_requires_api_level(minApiLevel, atomDeclSet, signature));
347 }
348 return maxRequiresApiLevel;
349 }
350
write_stats_log_java(FILE * out,const Atoms & atoms,const AtomDecl & attributionDecl,const string & javaClass,const string & javaPackage,const int minApiLevel,const bool supportWorkSource,const bool staticMethods)351 int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
352 const string& javaClass, const string& javaPackage, const int minApiLevel,
353 const bool supportWorkSource, const bool staticMethods) {
354 // Print prelude
355 fprintf(out, "// This file is autogenerated\n");
356 fprintf(out, "\n");
357 fprintf(out, "package %s;\n", javaPackage.c_str());
358 fprintf(out, "\n");
359 fprintf(out, "\n");
360 fprintf(out, "import android.os.Build;\n");
361 if (minApiLevel <= API_Q) {
362 fprintf(out, "import android.os.SystemClock;\n");
363 }
364
365 fprintf(out, "import android.util.StatsEvent;\n");
366 fprintf(out, "import android.util.StatsLog;\n");
367 const int maxRequiresApiLevel =
368 std::max(get_max_requires_api_level(minApiLevel, atoms.signatureInfoMap),
369 get_max_requires_api_level(minApiLevel, atoms.pulledAtomsSignatureInfoMap));
370 if (maxRequiresApiLevel > 0) {
371 fprintf(out, "import androidx.annotation.RequiresApi;\n");
372 }
373
374 int errors = 0;
375
376 #ifdef JAVA_INCLUDE_SRCS_DIR
377 const bool hasHistograms = has_histograms(atoms.decls);
378 const vector<string> excludeList =
379 hasHistograms ? vector<string>{} : vector<string>{HISTOGRAM_STEM};
380 errors += write_srcs_header(out, JAVA_INCLUDE_SRCS_DIR, excludeList);
381 #endif
382
383 fprintf(out, "\n");
384 fprintf(out, "\n");
385 fprintf(out, "/**\n");
386 fprintf(out, " * Utility class for logging statistics events.\n");
387 fprintf(out, " * @hide\n");
388 fprintf(out, " */\n");
389
390 fprintf(out, "public class %s {\n", javaClass.c_str());
391
392 write_java_atom_codes(out, atoms);
393 write_java_enum_values(out, atoms);
394 write_java_annotation_constants(out, minApiLevel);
395
396 #ifdef JAVA_INCLUDE_SRCS_DIR
397 if (hasHistograms) {
398 errors += write_java_histogram_helpers(out, atoms.decls, staticMethods);
399 }
400 #endif
401
402 // Print write methods.
403 fprintf(out, " // Write methods\n");
404 errors += write_java_pushed_methods(out, atoms.signatureInfoMap, attributionDecl, minApiLevel,
405 staticMethods);
406 errors += write_java_non_chained_methods(out, atoms.nonChainedSignatureInfoMap,
407 staticMethods);
408 errors += write_java_pulled_methods(out, atoms.pulledAtomsSignatureInfoMap, attributionDecl,
409 minApiLevel, staticMethods);
410 if (supportWorkSource) {
411 errors += write_java_work_source_methods(out, atoms.signatureInfoMap);
412 }
413
414 #ifdef JAVA_INCLUDE_SRCS_DIR
415 errors += write_java_srcs_classes(out, JAVA_INCLUDE_SRCS_DIR, excludeList);
416 #endif
417
418 if (minApiLevel == API_Q) {
419 errors += write_java_q_logger_class(out, atoms.signatureInfoMap, attributionDecl);
420 }
421
422 fprintf(out, "}\n");
423
424 return errors;
425 }
426
427 } // namespace stats_log_api_gen
428 } // namespace android
429