• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Copyright (C) 2012 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"""
18A set of helpers for rendering Mako templates with a Metadata model.
19"""
20
21import metadata_model
22import re
23import markdown
24import textwrap
25import sys
26import bs4
27# Monkey-patch BS4. WBR element must not have an end tag.
28bs4.builder.HTMLTreeBuilder.empty_element_tags.add("wbr")
29
30from collections import OrderedDict
31
32# Relative path from HTML file to the base directory used by <img> tags
33IMAGE_SRC_METADATA="images/camera2/metadata/"
34
35# Prepend this path to each <img src="foo"> in javadocs
36JAVADOC_IMAGE_SRC_METADATA="/reference/" + IMAGE_SRC_METADATA
37NDKDOC_IMAGE_SRC_METADATA="../" + IMAGE_SRC_METADATA
38
39#Corresponds to Android Q, where the camera VNDK was added (minor version 4 and vndk version 29).
40# Minor version and vndk version must correspond to the same release
41FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION = 4
42FRAMEWORK_CAMERA_VNDK_STARTING_VERSION =  29
43
44_context_buf = None
45_enum = None
46
47def _is_sec_or_ins(x):
48  return isinstance(x, metadata_model.Section) or    \
49         isinstance(x, metadata_model.InnerNamespace)
50
51##
52## Metadata Helpers
53##
54
55def find_all_sections(root):
56  """
57  Find all descendants that are Section or InnerNamespace instances.
58
59  Args:
60    root: a Metadata instance
61
62  Returns:
63    A list of Section/InnerNamespace instances
64
65  Remarks:
66    These are known as "sections" in the generated C code.
67  """
68  return root.find_all(_is_sec_or_ins)
69
70def find_parent_section(entry):
71  """
72  Find the closest ancestor that is either a Section or InnerNamespace.
73
74  Args:
75    entry: an Entry or Clone node
76
77  Returns:
78    An instance of Section or InnerNamespace
79  """
80  return entry.find_parent_first(_is_sec_or_ins)
81
82# find uniquely named entries (w/o recursing through inner namespaces)
83def find_unique_entries(node):
84  """
85  Find all uniquely named entries, without recursing through inner namespaces.
86
87  Args:
88    node: a Section or InnerNamespace instance
89
90  Yields:
91    A sequence of MergedEntry nodes representing an entry
92
93  Remarks:
94    This collapses multiple entries with the same fully qualified name into
95    one entry (e.g. if there are multiple entries in different kinds).
96  """
97  if not isinstance(node, metadata_model.Section) and    \
98     not isinstance(node, metadata_model.InnerNamespace):
99      raise TypeError("expected node to be a Section or InnerNamespace")
100
101  d = OrderedDict()
102  # remove the 'kinds' from the path between sec and the closest entries
103  # then search the immediate children of the search path
104  search_path = isinstance(node, metadata_model.Section) and node.kinds \
105                or [node]
106  for i in search_path:
107      for entry in i.entries:
108          d[entry.name] = entry
109
110  for k,v in d.items():
111      yield v.merge()
112
113def path_name(node):
114  """
115  Calculate a period-separated string path from the root to this element,
116  by joining the names of each node and excluding the Metadata/Kind nodes
117  from the path.
118
119  Args:
120    node: a Node instance
121
122  Returns:
123    A string path
124  """
125
126  isa = lambda x,y: isinstance(x, y)
127  fltr = lambda x: not isa(x, metadata_model.Metadata) and \
128                   not isa(x, metadata_model.Kind)
129
130  path = node.find_parents(fltr)
131  path = list(path)
132  path.reverse()
133  path.append(node)
134
135  return ".".join((i.name for i in path))
136
137def ndk(name):
138  """
139  Return the NDK version of given name, which replace
140  the leading "android" to "acamera"
141
142  Args:
143    name: name string of an entry
144
145  Returns:
146    A NDK version name string of the input name
147  """
148  name_list = name.split(".")
149  if name_list[0] == "android":
150    name_list[0] = "acamera"
151  return ".".join(name_list)
152
153def protobuf_type(entry):
154  """
155  Return the protocol buffer message type for input metadata entry.
156  Only support types used by static metadata right now
157
158  Returns:
159    A string of protocol buffer type. Ex: "optional int32" or "repeated RangeInt"
160  """
161  typeName = None
162  if entry.typedef is None:
163    typeName = entry.type
164  else:
165    typeName = entry.typedef.name
166
167  typename_to_protobuftype = {
168    "rational"               : "Rational",
169    "size"                   : "Size",
170    "sizeF"                  : "SizeF",
171    "rectangle"              : "Rect",
172    "streamConfigurationMap" : "StreamConfigurations",
173    "mandatoryStreamCombination" : "MandatoryStreamCombination",
174    "rangeInt"               : "RangeInt",
175    "rangeLong"              : "RangeLong",
176    "rangeFloat"             : "RangeFloat",
177    "colorSpaceTransform"    : "ColorSpaceTransform",
178    "blackLevelPattern"      : "BlackLevelPattern",
179    "byte"                   : "int32", # protocol buffer don't support byte
180    "boolean"                : "bool",
181    "float"                  : "float",
182    "double"                 : "double",
183    "int32"                  : "int32",
184    "int64"                  : "int64",
185    "enumList"               : "int32",
186    "string"                 : "string",
187    "capability"             : "Capability",
188    "multiResolutionStreamConfigurationMap" : "MultiResolutionStreamConfigurations",
189    "deviceStateSensorOrientationMap"  : "DeviceStateSensorOrientationMap",
190    "dynamicRangeProfiles"   : "DynamicRangeProfiles",
191    "colorSpaceProfiles"     : "ColorSpaceProfiles",
192  }
193
194  if typeName not in typename_to_protobuftype:
195    print("  ERROR: Could not find protocol buffer type for {%s} type {%s} typedef {%s}" % \
196          (entry.name, entry.type, entry.typedef), file=sys.stderr)
197
198  proto_type = typename_to_protobuftype[typeName]
199
200  prefix = "optional"
201  if entry.container == 'array':
202    has_variable_size = False
203    for size in entry.container_sizes:
204      try:
205        size_int = int(size)
206      except ValueError:
207        has_variable_size = True
208
209    if has_variable_size:
210      prefix = "repeated"
211
212  return "%s %s" %(prefix, proto_type)
213
214
215def protobuf_name(entry):
216  """
217  Return the protocol buffer field name for input metadata entry
218
219  Returns:
220    A string. Ex: "android_colorCorrection_availableAberrationModes"
221  """
222  return entry.name.replace(".", "_")
223
224def has_descendants_with_enums(node):
225  """
226  Determine whether or not the current node is or has any descendants with an
227  Enum node.
228
229  Args:
230    node: a Node instance
231
232  Returns:
233    True if it finds an Enum node in the subtree, False otherwise
234  """
235  return bool(node.find_first(lambda x: isinstance(x, metadata_model.Enum)))
236
237def get_children_by_throwing_away_kind(node, member='entries'):
238  """
239  Get the children of this node by compressing the subtree together by removing
240  the kind and then combining any children nodes with the same name together.
241
242  Args:
243    node: An instance of Section, InnerNamespace, or Kind
244
245  Returns:
246    An iterable over the combined children of the subtree of node,
247    as if the Kinds never existed.
248
249  Remarks:
250    Not recursive. Call this function repeatedly on each child.
251  """
252
253  if isinstance(node, metadata_model.Section):
254    # Note that this makes jump from Section to Kind,
255    # skipping the Kind entirely in the tree.
256    node_to_combine = node.combine_kinds_into_single_node()
257  else:
258    node_to_combine = node
259
260  combined_kind = node_to_combine.combine_children_by_name()
261
262  return (i for i in getattr(combined_kind, member))
263
264def get_children_by_filtering_kind(section, kind_name, member='entries'):
265  """
266  Takes a section and yields the children of the merged kind under this section.
267
268  Args:
269    section: An instance of Section
270    kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls'
271
272  Returns:
273    An iterable over the children of the specified merged kind.
274  """
275
276  matched_kind = next((i for i in section.merged_kinds if i.name == kind_name), None)
277
278  if matched_kind:
279    return getattr(matched_kind, member)
280  else:
281    return ()
282
283##
284## Filters
285##
286
287# abcDef.xyz -> ABC_DEF_XYZ
288def csym(name):
289  """
290  Convert an entry name string into an uppercase C symbol.
291
292  Returns:
293    A string
294
295  Example:
296    csym('abcDef.xyz') == 'ABC_DEF_XYZ'
297  """
298  newstr = name
299  newstr = "".join([i.isupper() and ("_" + i) or i for i in newstr]).upper()
300  newstr = newstr.replace(".", "_")
301  return newstr
302
303# abcDef.xyz -> abc_def_xyz
304def csyml(name):
305  """
306  Convert an entry name string into a lowercase C symbol.
307
308  Returns:
309    A string
310
311  Example:
312    csyml('abcDef.xyz') == 'abc_def_xyz'
313  """
314  return csym(name).lower()
315
316# pad with spaces to make string len == size. add new line if too big
317def ljust(size, indent=4):
318  """
319  Creates a function that given a string will pad it with spaces to make
320  the string length == size. Adds a new line if the string was too big.
321
322  Args:
323    size: an integer representing how much spacing should be added
324    indent: an integer representing the initial indendation level
325
326  Returns:
327    A function that takes a string and returns a string.
328
329  Example:
330    ljust(8)("hello") == 'hello   '
331
332  Remarks:
333    Deprecated. Use pad instead since it works for non-first items in a
334    Mako template.
335  """
336  def inner(what):
337    newstr = what.ljust(size)
338    if len(newstr) > size:
339      return what + "\n" + "".ljust(indent + size)
340    else:
341      return newstr
342  return inner
343
344def _find_new_line():
345
346  if _context_buf is None:
347    raise ValueError("Context buffer was not set")
348
349  buf = _context_buf
350  x = -1 # since the first read is always ''
351  cur_pos = buf.tell()
352  while buf.tell() > 0 and buf.read(1) != '\n':
353    buf.seek(cur_pos - x)
354    x = x + 1
355
356  buf.seek(cur_pos)
357
358  return int(x)
359
360# Pad the string until the buffer reaches the desired column.
361# If string is too long, insert a new line with 'col' spaces instead
362def pad(col):
363  """
364  Create a function that given a string will pad it to the specified column col.
365  If the string overflows the column, put the string on a new line and pad it.
366
367  Args:
368    col: an integer specifying the column number
369
370  Returns:
371    A function that given a string will produce a padded string.
372
373  Example:
374    pad(8)("hello") == 'hello   '
375
376  Remarks:
377    This keeps track of the line written by Mako so far, so it will always
378    align to the column number correctly.
379  """
380  def inner(what):
381    wut = int(col)
382    current_col = _find_new_line()
383
384    if len(what) > wut - current_col:
385      return what + "\n".ljust(col)
386    else:
387      return what.ljust(wut - current_col)
388  return inner
389
390# int32 -> TYPE_INT32, byte -> TYPE_BYTE, etc. note that enum -> TYPE_INT32
391def ctype_enum(what):
392  """
393  Generate a camera_metadata_type_t symbol from a type string.
394
395  Args:
396    what: a type string
397
398  Returns:
399    A string representing the camera_metadata_type_t
400
401  Example:
402    ctype_enum('int32') == 'TYPE_INT32'
403    ctype_enum('int64') == 'TYPE_INT64'
404    ctype_enum('float') == 'TYPE_FLOAT'
405
406  Remarks:
407    An enum is coerced to a byte since the rest of the camera_metadata
408    code doesn't support enums directly yet.
409  """
410  return 'TYPE_%s' %(what.upper())
411
412
413# Calculate a java type name from an entry with a Typedef node
414def _jtypedef_type(entry):
415  typedef = entry.typedef
416  additional = ''
417
418  # Hacky way to deal with arrays. Assume that if we have
419  # size 'Constant x N' the Constant is part of the Typedef size.
420  # So something sized just 'Constant', 'Constant1 x Constant2', etc
421  # is not treated as a real java array.
422  if entry.container == 'array':
423    has_variable_size = False
424    for size in entry.container_sizes:
425      try:
426        size_int = int(size)
427      except ValueError:
428        has_variable_size = True
429
430    if has_variable_size:
431      additional = '[]'
432
433  try:
434    name = typedef.languages['java']
435
436    return "%s%s" %(name, additional)
437  except KeyError:
438    return None
439
440# Box if primitive. Otherwise leave unboxed.
441def _jtype_box(type_name):
442  mapping = {
443    'boolean': 'Boolean',
444    'byte': 'Byte',
445    'int': 'Integer',
446    'float': 'Float',
447    'double': 'Double',
448    'long': 'Long'
449  }
450
451  return mapping.get(type_name, type_name)
452
453def jtype_unboxed(entry):
454  """
455  Calculate the Java type from an entry type string, to be used whenever we
456  need the regular type in Java. It's not boxed, so it can't be used as a
457  generic type argument when the entry type happens to resolve to a primitive.
458
459  Remarks:
460    Since Java generics cannot be instantiated with primitives, this version
461    is not applicable in that case. Use jtype_boxed instead for that.
462
463  Returns:
464    The string representing the Java type.
465  """
466  if not isinstance(entry, metadata_model.Entry):
467    raise ValueError("Expected entry to be an instance of Entry")
468
469  metadata_type = entry.type
470
471  java_type = None
472
473  if entry.typedef:
474    typedef_name = _jtypedef_type(entry)
475    if typedef_name:
476      java_type = typedef_name # already takes into account arrays
477
478  if not java_type:
479    if not java_type and entry.enum and metadata_type == 'byte':
480      # Always map byte enums to Java ints, unless there's a typedef override
481      base_type = 'int'
482
483    else:
484      mapping = {
485        'int32': 'int',
486        'int64': 'long',
487        'float': 'float',
488        'double': 'double',
489        'byte': 'byte',
490        'rational': 'Rational'
491      }
492
493      base_type = mapping[metadata_type]
494
495    # Convert to array (enums, basic types)
496    if entry.container == 'array':
497      additional = '[]'
498    else:
499      additional = ''
500
501    java_type = '%s%s' %(base_type, additional)
502
503  # Now box this sucker.
504  return java_type
505
506def jtype_boxed(entry):
507  """
508  Calculate the Java type from an entry type string, to be used as a generic
509  type argument in Java. The type is guaranteed to inherit from Object.
510
511  It will only box when absolutely necessary, i.e. int -> Integer[], but
512  int[] -> int[].
513
514  Remarks:
515    Since Java generics cannot be instantiated with primitives, this version
516    will use boxed types when absolutely required.
517
518  Returns:
519    The string representing the boxed Java type.
520  """
521  unboxed_type = jtype_unboxed(entry)
522  return _jtype_box(unboxed_type)
523
524def _is_jtype_generic(entry):
525  """
526  Determine whether or not the Java type represented by the entry type
527  string and/or typedef is a Java generic.
528
529  For example, "Range<Integer>" would be considered a generic, whereas
530  a "MeteringRectangle" or a plain "Integer" would not be considered a generic.
531
532  Args:
533    entry: An instance of an Entry node
534
535  Returns:
536    True if it's a java generic, False otherwise.
537  """
538  if entry.typedef:
539    local_typedef = _jtypedef_type(entry)
540    if local_typedef:
541      match = re.search(r'<.*>', local_typedef)
542      return bool(match)
543  return False
544
545def _jtype_primitive(what):
546  """
547  Calculate the Java type from an entry type string.
548
549  Remarks:
550    Makes a special exception for Rational, since it's a primitive in terms of
551    the C-library camera_metadata type system.
552
553  Returns:
554    The string representing the primitive type
555  """
556  mapping = {
557    'int32': 'int',
558    'int64': 'long',
559    'float': 'float',
560    'double': 'double',
561    'byte': 'byte',
562    'rational': 'Rational'
563  }
564
565  try:
566    return mapping[what]
567  except KeyError as e:
568    raise ValueError("Can't map '%s' to a primitive, not supported" %what)
569
570def jclass(entry):
571  """
572  Calculate the java Class reference string for an entry.
573
574  Args:
575    entry: an Entry node
576
577  Example:
578    <entry name="some_int" type="int32"/>
579    <entry name="some_int_array" type="int32" container='array'/>
580
581    jclass(some_int) == 'int.class'
582    jclass(some_int_array) == 'int[].class'
583
584  Returns:
585    The ClassName.class string
586  """
587
588  return "%s.class" %jtype_unboxed(entry)
589
590def jkey_type_token(entry):
591  """
592  Calculate the java type token compatible with a Key constructor.
593  This will be the Java Class<T> for non-generic classes, and a
594  TypeReference<T> for generic classes.
595
596  Args:
597    entry: An entry node
598
599  Returns:
600    The ClassName.class string, or 'new TypeReference<ClassName>() {{ }}' string
601  """
602  if _is_jtype_generic(entry):
603    return "new TypeReference<%s>() {{ }}" %(jtype_boxed(entry))
604  else:
605    return jclass(entry)
606
607def jidentifier(what):
608  """
609  Convert the input string into a valid Java identifier.
610
611  Args:
612    what: any identifier string
613
614  Returns:
615    String with added underscores if necessary.
616  """
617  if re.match("\d", what):
618    return "_%s" %what
619  else:
620    return what
621
622def enum_calculate_value_string(enum_value):
623  """
624  Calculate the value of the enum, even if it does not have one explicitly
625  defined.
626
627  This looks back for the first enum value that has a predefined value and then
628  applies addition until we get the right value, using C-enum semantics.
629
630  Args:
631    enum_value: an EnumValue node with a valid Enum parent
632
633  Example:
634    <enum>
635      <value>X</value>
636      <value id="5">Y</value>
637      <value>Z</value>
638    </enum>
639
640    enum_calculate_value_string(X) == '0'
641    enum_calculate_Value_string(Y) == '5'
642    enum_calculate_value_string(Z) == '6'
643
644  Returns:
645    String that represents the enum value as an integer literal.
646  """
647
648  enum_value_siblings = list(enum_value.parent.values)
649  this_index = enum_value_siblings.index(enum_value)
650
651  def is_hex_string(instr):
652    return bool(re.match('0x[a-f0-9]+$', instr, re.IGNORECASE))
653
654  base_value = 0
655  base_offset = 0
656  emit_as_hex = False
657
658  this_id = enum_value_siblings[this_index].id
659  while this_index != 0 and not this_id:
660    this_index -= 1
661    base_offset += 1
662    this_id = enum_value_siblings[this_index].id
663
664  if this_id:
665    base_value = int(this_id, 0)  # guess base
666    emit_as_hex = is_hex_string(this_id)
667
668  if emit_as_hex:
669    return "0x%X" %(base_value + base_offset)
670  else:
671    return "%d" %(base_value + base_offset)
672
673def enumerate_with_last(iterable):
674  """
675  Enumerate a sequence of iterable, while knowing if this element is the last in
676  the sequence or not.
677
678  Args:
679    iterable: an Iterable of some sequence
680
681  Yields:
682    (element, bool) where the bool is True iff the element is last in the seq.
683  """
684  it = (i for i in iterable)
685
686  try:
687    first = next(it)  # OK: raises exception if it is empty
688  except StopIteration:
689    return
690
691  second = first  # for when we have only 1 element in iterable
692
693  try:
694    while True:
695      second = next(it)
696      # more elements remaining.
697      yield (first, False)
698      first = second
699  except StopIteration:
700    # last element. no more elements left
701    yield (second, True)
702
703def pascal_case(what):
704  """
705  Convert the first letter of a string to uppercase, to make the identifier
706  conform to PascalCase.
707
708  If there are dots, remove the dots, and capitalize the letter following
709  where the dot was. Letters that weren't following dots are left unchanged,
710  except for the first letter of the string (which is made upper-case).
711
712  Args:
713    what: a string representing some identifier
714
715  Returns:
716    String with first letter capitalized
717
718  Example:
719    pascal_case("helloWorld") == "HelloWorld"
720    pascal_case("foo") == "Foo"
721    pascal_case("hello.world") = "HelloWorld"
722    pascal_case("fooBar.fooBar") = "FooBarFooBar"
723  """
724  return "".join([s[0:1].upper() + s[1:] for s in what.split('.')])
725
726def jkey_identifier(what):
727  """
728  Return a Java identifier from a property name.
729
730  Args:
731    what: a string representing a property name.
732
733  Returns:
734    Java identifier corresponding to the property name. May need to be
735    prepended with the appropriate Java class name by the caller of this
736    function. Note that the outer namespace is stripped from the property
737    name.
738
739  Example:
740    jkey_identifier("android.lens.facing") == "LENS_FACING"
741  """
742  return csym(what[what.find('.') + 1:])
743
744def jenum_value(enum_entry, enum_value):
745  """
746  Calculate the Java name for an integer enum value
747
748  Args:
749    enum: An enum-typed Entry node
750    value: An EnumValue node for the enum
751
752  Returns:
753    String representing the Java symbol
754  """
755
756  cname = csym(enum_entry.name)
757  return cname[cname.find('_') + 1:] + '_' + enum_value.name
758
759def generate_extra_javadoc_detail(entry):
760  """
761  Returns a function to add extra details for an entry into a string for inclusion into
762  javadoc. Adds information about units, the list of enum values for this key, and the valid
763  range.
764  """
765  def inner(text):
766    if entry.units and not (entry.typedef and entry.typedef.name == 'string'):
767      text += '\n\n<b>Units</b>: %s\n' % (dedent(entry.units))
768    if entry.enum and not (entry.typedef and entry.typedef.languages.get('java')):
769      text += '\n\n<b>Possible values:</b>\n<ul>\n'
770      for value in entry.enum.values:
771        if not value.hidden:
772          text += '  <li>{@link #%s %s}</li>\n' % ( jenum_value(entry, value ), value.name )
773      text += '</ul>\n'
774    if entry.range:
775      if entry.enum and not (entry.typedef and entry.typedef.languages.get('java')):
776        text += '\n\n<b>Available values for this device:</b><br>\n'
777      else:
778        text += '\n\n<b>Range of valid values:</b><br>\n'
779      text += '%s\n' % (dedent(entry.range))
780    if entry.hwlevel != 'legacy': # covers any of (None, 'limited', 'full')
781      text += '\n\n<b>Optional</b> - The value for this key may be {@code null} on some devices.\n'
782    if entry.hwlevel == 'full':
783      text += \
784        '\n<b>Full capability</b> - \n' + \
785        'Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the\n' + \
786        'android.info.supportedHardwareLevel key\n'
787    if entry.hwlevel == 'limited':
788      text += \
789        '\n<b>Limited capability</b> - \n' + \
790        'Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the\n' + \
791        'android.info.supportedHardwareLevel key\n'
792    if entry.hwlevel == 'legacy':
793      text += "\nThis key is available on all devices."
794    if entry.permission_needed == "true":
795      text += "\n\n<b>Permission {@link android.Manifest.permission#CAMERA} is needed to access this property</b>\n\n"
796
797    return text
798  return inner
799
800
801def javadoc(metadata, indent = 4):
802  """
803  Returns a function to format a markdown syntax text block as a
804  javadoc comment section, given a set of metadata
805
806  Args:
807    metadata: A Metadata instance, representing the top-level root
808      of the metadata for cross-referencing
809    indent: baseline level of indentation for javadoc block
810  Returns:
811    A function that transforms a String text block as follows:
812    - Indent and * for insertion into a Javadoc comment block
813    - Trailing whitespace removed
814    - Entire body rendered via markdown to generate HTML
815    - All tag names converted to appropriate Javadoc {@link} with @see
816      for each tag
817
818  Example:
819    "This is a comment for Javadoc\n" +
820    "     with multiple lines, that should be   \n" +
821    "     formatted better\n" +
822    "\n" +
823    "    That covers multiple lines as well\n"
824    "    And references android.control.mode\n"
825
826    transforms to
827    "    * <p>This is a comment for Javadoc\n" +
828    "    * with multiple lines, that should be\n" +
829    "    * formatted better</p>\n" +
830    "    * <p>That covers multiple lines as well</p>\n" +
831    "    * and references {@link CaptureRequest#CONTROL_MODE android.control.mode}\n" +
832    "    *\n" +
833    "    * @see CaptureRequest#CONTROL_MODE\n"
834  """
835  def javadoc_formatter(text):
836    comment_prefix = " " * indent + " * "
837
838    # render with markdown => HTML
839    javatext = md(text, JAVADOC_IMAGE_SRC_METADATA)
840
841    # Identity transform for javadoc links
842    def javadoc_link_filter(target, target_ndk, shortname):
843      return '{@link %s %s}' % (target, shortname)
844
845    javatext = filter_links(javatext, javadoc_link_filter)
846
847    # Crossref tag names
848    kind_mapping = {
849        'static': 'CameraCharacteristics',
850        'dynamic': 'CaptureResult',
851        'controls': 'CaptureRequest' }
852
853    # Convert metadata entry "android.x.y.z" to form
854    # "{@link CaptureRequest#X_Y_Z android.x.y.z}"
855    def javadoc_crossref_filter(node):
856      if node.applied_visibility in ('public', 'java_public', 'fwk_java_public'):
857        return '{@link %s#%s %s}' % (kind_mapping[node.kind],
858                                     jkey_identifier(node.name),
859                                     node.name)
860      else:
861        return node.name
862
863    # For each public tag "android.x.y.z" referenced, add a
864    # "@see CaptureRequest#X_Y_Z"
865    def javadoc_crossref_see_filter(node_set):
866      node_set = (x for x in node_set if x.applied_visibility in \
867                  ('public', 'java_public', 'fwk_java_public'))
868
869      text = '\n'
870      for node in node_set:
871        text = text + '\n@see %s#%s' % (kind_mapping[node.kind],
872                                      jkey_identifier(node.name))
873
874      return text if text != '\n' else ''
875
876    javatext = filter_tags(javatext, metadata, javadoc_crossref_filter, javadoc_crossref_see_filter)
877
878    def line_filter(line):
879      # Indent each line
880      # Add ' * ' to it for stylistic reasons
881      # Strip right side of trailing whitespace
882      return (comment_prefix + line).rstrip()
883
884    # Process each line with above filter
885    javatext = "\n".join(line_filter(i) for i in javatext.split("\n")) + "\n"
886
887    return javatext
888
889  return javadoc_formatter
890
891def ndkdoc(metadata, indent = 4):
892  """
893  Returns a function to format a markdown syntax text block as a
894  NDK camera API C/C++ comment section, given a set of metadata
895
896  Args:
897    metadata: A Metadata instance, representing the top-level root
898      of the metadata for cross-referencing
899    indent: baseline level of indentation for comment block
900  Returns:
901    A function that transforms a String text block as follows:
902    - Indent and * for insertion into a comment block
903    - Trailing whitespace removed
904    - Entire body rendered via markdown
905    - All tag names converted to appropriate NDK tag name for each tag
906
907  Example:
908    "This is a comment for NDK\n" +
909    "     with multiple lines, that should be   \n" +
910    "     formatted better\n" +
911    "\n" +
912    "    That covers multiple lines as well\n"
913    "    And references android.control.mode\n"
914
915    transforms to
916    "    * This is a comment for NDK\n" +
917    "    * with multiple lines, that should be\n" +
918    "    * formatted better\n" +
919    "    * That covers multiple lines as well\n" +
920    "    * and references ACAMERA_CONTROL_MODE\n" +
921    "    *\n" +
922    "    * @see ACAMERA_CONTROL_MODE\n"
923  """
924  def ndkdoc_formatter(text):
925    # render with markdown => HTML
926    # Turn off the table plugin since doxygen doesn't recognize generated <thead> <tbody> tags
927    ndktext = md(text, NDKDOC_IMAGE_SRC_METADATA, False)
928
929    # Simple transform for ndk doc links
930    def ndkdoc_link_filter(target, target_ndk, shortname):
931      if target_ndk is not None:
932        return '{@link %s %s}' % (target_ndk, shortname)
933
934      # Create HTML link to Javadoc
935      if shortname == '':
936        lastdot = target.rfind('.')
937        if lastdot == -1:
938          shortname = target
939        else:
940          shortname = target[lastdot + 1:]
941
942      target = target.replace('.','/')
943      if target.find('#') != -1:
944        target = target.replace('#','.html#')
945      else:
946        target = target + '.html'
947
948      # Work around html links with inner classes.
949      target = target.replace('CaptureRequest/Builder', 'CaptureRequest.Builder')
950      target = target.replace('Build/VERSION', 'Build.VERSION')
951
952      return '<a href="https://developer.android.com/reference/%s">%s</a>' % (target, shortname)
953
954    ndktext = filter_links(ndktext, ndkdoc_link_filter)
955
956    # Convert metadata entry "android.x.y.z" to form
957    # NDK tag format of "ACAMERA_X_Y_Z"
958    def ndkdoc_crossref_filter(node):
959      if node.applied_ndk_visible == 'true':
960        return csym(ndk(node.name))
961      else:
962        return node.name
963
964    # For each public tag "android.x.y.z" referenced, add a
965    # "@see ACAMERA_X_Y_Z"
966    def ndkdoc_crossref_see_filter(node_set):
967      node_set = (x for x in node_set if x.applied_ndk_visible == 'true')
968
969      text = '\n'
970      for node in node_set:
971        text = text + '\n@see %s' % (csym(ndk(node.name)))
972
973      return text if text != '\n' else ''
974
975    ndktext = filter_tags(ndktext, metadata, ndkdoc_crossref_filter, ndkdoc_crossref_see_filter)
976
977    ndktext = ndk_replace_tag_wildcards(ndktext, metadata)
978
979    comment_prefix = " " * indent + " * ";
980
981    def line_filter(line):
982      # Indent each line
983      # Add ' * ' to it for stylistic reasons
984      # Strip right side of trailing whitespace
985      return (comment_prefix + line).rstrip()
986
987    # Process each line with above filter
988    ndktext = "\n".join(line_filter(i) for i in ndktext.split("\n")) + "\n"
989
990    return ndktext
991
992  return ndkdoc_formatter
993
994def hidldoc(metadata, indent = 4):
995  """
996  Returns a function to format a markdown syntax text block as a
997  HIDL camera HAL module C/C++ comment section, given a set of metadata
998
999  Args:
1000    metadata: A Metadata instance, representing the top-level root
1001      of the metadata for cross-referencing
1002    indent: baseline level of indentation for comment block
1003  Returns:
1004    A function that transforms a String text block as follows:
1005    - Indent and * for insertion into a comment block
1006    - Trailing whitespace removed
1007    - Entire body rendered via markdown
1008    - All tag names converted to appropriate HIDL tag name for each tag
1009
1010  Example:
1011    "This is a comment for NDK\n" +
1012    "     with multiple lines, that should be   \n" +
1013    "     formatted better\n" +
1014    "\n" +
1015    "    That covers multiple lines as well\n"
1016    "    And references android.control.mode\n"
1017
1018    transforms to
1019    "    * This is a comment for NDK\n" +
1020    "    * with multiple lines, that should be\n" +
1021    "    * formatted better\n" +
1022    "    * That covers multiple lines as well\n" +
1023    "    * and references ANDROID_CONTROL_MODE\n" +
1024    "    *\n" +
1025    "    * @see ANDROID_CONTROL_MODE\n"
1026  """
1027  def hidldoc_formatter(text):
1028    # render with markdown => HTML
1029    # Turn off the table plugin since doxygen doesn't recognize generated <thead> <tbody> tags
1030    hidltext = md(text, NDKDOC_IMAGE_SRC_METADATA, False)
1031
1032    # Simple transform for hidl doc links
1033    def hidldoc_link_filter(target, target_ndk, shortname):
1034      if target_ndk is not None:
1035        return '{@link %s %s}' % (target_ndk, shortname)
1036
1037      # Create HTML link to Javadoc
1038      if shortname == '':
1039        lastdot = target.rfind('.')
1040        if lastdot == -1:
1041          shortname = target
1042        else:
1043          shortname = target[lastdot + 1:]
1044
1045      target = target.replace('.','/')
1046      if target.find('#') != -1:
1047        target = target.replace('#','.html#')
1048      else:
1049        target = target + '.html'
1050
1051      return '<a href="https://developer.android.com/reference/%s">%s</a>' % (target, shortname)
1052
1053    hidltext = filter_links(hidltext, hidldoc_link_filter)
1054
1055    # Convert metadata entry "android.x.y.z" to form
1056    # HIDL tag format of "ANDROID_X_Y_Z"
1057    def hidldoc_crossref_filter(node):
1058      return csym(node.name)
1059
1060    # For each public tag "android.x.y.z" referenced, add a
1061    # "@see ANDROID_X_Y_Z"
1062    def hidldoc_crossref_see_filter(node_set):
1063      text = '\n'
1064      for node in node_set:
1065        text = text + '\n@see %s' % (csym(node.name))
1066
1067      return text if text != '\n' else ''
1068
1069    hidltext = filter_tags(hidltext, metadata, hidldoc_crossref_filter, hidldoc_crossref_see_filter)
1070
1071    comment_prefix = " " * indent + " * ";
1072
1073    def line_filter(line):
1074      # Indent each line
1075      # Add ' * ' to it for stylistic reasons
1076      # Strip right side of trailing whitespace
1077      return (comment_prefix + line).rstrip()
1078
1079    # Process each line with above filter
1080    hidltext = "\n".join(line_filter(i) for i in hidltext.split("\n")) + "\n"
1081
1082    return hidltext
1083
1084  return hidldoc_formatter
1085
1086def dedent(text):
1087  """
1088  Remove all common indentation from every line but the 0th.
1089  This will avoid getting <code> blocks when rendering text via markdown.
1090  Ignoring the 0th line will also allow the 0th line not to be aligned.
1091
1092  Args:
1093    text: A string of text to dedent.
1094
1095  Returns:
1096    String dedented by above rules.
1097
1098  For example:
1099    assertEquals("bar\nline1\nline2",   dedent("bar\n  line1\n  line2"))
1100    assertEquals("bar\nline1\nline2",   dedent(" bar\n  line1\n  line2"))
1101    assertEquals("bar\n  line1\nline2", dedent(" bar\n    line1\n  line2"))
1102  """
1103  text = textwrap.dedent(text)
1104  text_lines = text.split('\n')
1105  text_not_first = "\n".join(text_lines[1:])
1106  text_not_first = textwrap.dedent(text_not_first)
1107  text = text_lines[0] + "\n" + text_not_first
1108
1109  return text
1110
1111def md(text, img_src_prefix="", table_ext=True):
1112    """
1113    Run text through markdown to produce HTML.
1114
1115    This also removes all common indentation from every line but the 0th.
1116    This will avoid getting <code> blocks in markdown.
1117    Ignoring the 0th line will also allow the 0th line not to be aligned.
1118
1119    Args:
1120      text: A markdown-syntax using block of text to format.
1121      img_src_prefix: An optional string to prepend to each <img src="target"/>
1122
1123    Returns:
1124      String rendered by markdown and other rules applied (see above).
1125
1126    For example, this avoids the following situation:
1127
1128      <!-- Input -->
1129
1130      <!--- can't use dedent directly since 'foo' has no indent -->
1131      <notes>foo
1132          bar
1133          bar
1134      </notes>
1135
1136      <!-- Bad Output -- >
1137      <!-- if no dedent is done generated code looks like -->
1138      <p>foo
1139        <code><pre>
1140          bar
1141          bar</pre></code>
1142      </p>
1143
1144    Instead we get the more natural expected result:
1145
1146      <!-- Good Output -->
1147      <p>foo
1148      bar
1149      bar</p>
1150
1151    """
1152    text = dedent(text)
1153
1154    # full list of extensions at http://pythonhosted.org/Markdown/extensions/
1155    md_extensions = ['tables'] if table_ext else []# make <table> with ASCII |_| tables
1156    # render with markdown
1157    text = markdown.markdown(text, extensions=md_extensions)
1158
1159    # prepend a prefix to each <img src="foo"> -> <img src="${prefix}foo">
1160    text = re.sub(r'src="([^"]*)"', 'src="' + img_src_prefix + r'\1"', text)
1161    return text
1162
1163def filter_tags(text, metadata, filter_function, summary_function = None):
1164    """
1165    Find all references to tags in the form outer_namespace.xxx.yyy[.zzz] in
1166    the provided text, and pass them through filter_function and summary_function.
1167
1168    Used to linkify entry names in HMTL, javadoc output.
1169
1170    Args:
1171      text: A string representing a block of text destined for output
1172      metadata: A Metadata instance, the root of the metadata properties tree
1173      filter_function: A Node->string function to apply to each node
1174        when found in text; the string returned replaces the tag name in text.
1175      summary_function: A Node list->string function that is provided the list of
1176        unique tag nodes found in text, and which must return a string that is
1177        then appended to the end of the text. The list is sorted alphabetically
1178        by node name.
1179    """
1180
1181    tag_set = set()
1182    def name_match(name):
1183      return lambda node: node.name == name
1184
1185    # Match outer_namespace.x.y or outer_namespace.x.y.z, making sure
1186    # to grab .z and not just outer_namespace.x.y.  (sloppy, but since we
1187    # check for validity, a few false positives don't hurt).
1188    # Try to ignore items of the form {@link <outer_namespace>...
1189    for outer_namespace in metadata.outer_namespaces:
1190
1191      tag_match = r"(?<!\{@link\s)" + outer_namespace.name + \
1192        r"\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)(\.[a-zA-Z0-9\n]+)?([/]?)"
1193
1194      def filter_sub(match):
1195        whole_match = match.group(0)
1196        section1 = match.group(1)
1197        section2 = match.group(2)
1198        section3 = match.group(3)
1199        end_slash = match.group(4)
1200
1201        # Don't linkify things ending in slash (urls, for example)
1202        if end_slash:
1203          return whole_match
1204
1205        candidate = ""
1206
1207        # First try a two-level match
1208        candidate2 = "%s.%s.%s" % (outer_namespace.name, section1, section2)
1209        got_two_level = False
1210
1211        node = metadata.find_first(name_match(candidate2.replace('\n','')))
1212        if not node and '\n' in section2:
1213          # Linefeeds are ambiguous - was the intent to add a space,
1214          # or continue a lengthy name? Try the former now.
1215          candidate2b = "%s.%s.%s" % (outer_namespace.name, section1, section2[:section2.find('\n')])
1216          node = metadata.find_first(name_match(candidate2b))
1217          if node:
1218            candidate2 = candidate2b
1219
1220        if node:
1221          # Have two-level match
1222          got_two_level = True
1223          candidate = candidate2
1224        elif section3:
1225          # Try three-level match
1226          candidate3 = "%s%s" % (candidate2, section3)
1227          node = metadata.find_first(name_match(candidate3.replace('\n','')))
1228
1229          if not node and '\n' in section3:
1230            # Linefeeds are ambiguous - was the intent to add a space,
1231            # or continue a lengthy name? Try the former now.
1232            candidate3b = "%s%s" % (candidate2, section3[:section3.find('\n')])
1233            node = metadata.find_first(name_match(candidate3b))
1234            if node:
1235              candidate3 = candidate3b
1236
1237          if node:
1238            # Have 3-level match
1239            candidate = candidate3
1240
1241        # Replace match with crossref or complain if a likely match couldn't be matched
1242
1243        if node:
1244          tag_set.add(node)
1245          return whole_match.replace(candidate,filter_function(node))
1246        else:
1247          print("  WARNING: Could not crossref likely reference {%s}" % (match.group(0)),
1248                file=sys.stderr)
1249          return whole_match
1250
1251      text = re.sub(tag_match, filter_sub, text)
1252
1253    if summary_function is not None:
1254      return text + summary_function(sorted(tag_set, key=lambda x: x.name))
1255    else:
1256      return text
1257
1258def ndk_replace_tag_wildcards(text, metadata):
1259    """
1260    Find all references to tags in the form android.xxx.* or android.xxx.yyy.*
1261    in the provided text, and replace them by NDK format of "ACAMERA_XXX_*" or
1262    "ACAMERA_XXX_YYY_*"
1263
1264    Args:
1265      text: A string representing a block of text destined for output
1266      metadata: A Metadata instance, the root of the metadata properties tree
1267    """
1268    tag_match = r"android\.([a-zA-Z0-9\n]+)\.\*"
1269    tag_match_2 = r"android\.([a-zA-Z0-9\n]+)\.([a-zA-Z0-9\n]+)\*"
1270
1271    def filter_sub(match):
1272      return "ACAMERA_" + match.group(1).upper() + "_*"
1273    def filter_sub_2(match):
1274      return "ACAMERA_" + match.group(1).upper() + match.group(2).upper() + "_*"
1275
1276    text = re.sub(tag_match, filter_sub, text)
1277    text = re.sub(tag_match_2, filter_sub_2, text)
1278    return text
1279
1280def filter_links(text, filter_function, summary_function = None):
1281    """
1282    Find all references to tags in the form {@link xxx#yyy [zzz]} in the
1283    provided text, and pass them through filter_function and
1284    summary_function.
1285
1286    Used to linkify documentation cross-references in HMTL, javadoc output.
1287
1288    Args:
1289      text: A string representing a block of text destined for output
1290      metadata: A Metadata instance, the root of the metadata properties tree
1291      filter_function: A (string, string)->string function to apply to each 'xxx#yyy',
1292        zzz pair when found in text; the string returned replaces the tag name in text.
1293      summary_function: A string list->string function that is provided the list of
1294        unique targets found in text, and which must return a string that is
1295        then appended to the end of the text. The list is sorted alphabetically
1296        by node name.
1297
1298    """
1299
1300    target_set = set()
1301    def name_match(name):
1302      return lambda node: node.name == name
1303
1304    tag_match = r"\{@link\s+([^\s\}\|]+)(?:\|([^\s\}]+))*([^\}]*)\}"
1305
1306    def filter_sub(match):
1307      whole_match = match.group(0)
1308      target = match.group(1)
1309      target_ndk = match.group(2)
1310      shortname = match.group(3).strip()
1311
1312      #print("Found link '%s' ndk '%s' as '%s' -> '%s'" % (target, target_ndk, shortname, filter_function(target, target_ndk, shortname)))
1313
1314      # Replace match with crossref
1315      target_set.add(target)
1316      return filter_function(target, target_ndk, shortname)
1317
1318    text = re.sub(tag_match, filter_sub, text)
1319
1320    if summary_function is not None:
1321      return text + summary_function(sorted(target_set))
1322    else:
1323      return text
1324
1325def any_visible(section, kind_name, visibilities):
1326  """
1327  Determine if entries in this section have an applied visibility that's in
1328  the list of given visibilities.
1329
1330  Args:
1331    section: A section of metadata
1332    kind_name: A name of the kind, i.e. 'dynamic' or 'static' or 'controls'
1333    visibilities: An iterable of visibilities to match against
1334
1335  Returns:
1336    True if the section has any entries with any of the given visibilities. False otherwise.
1337  """
1338
1339  for inner_namespace in get_children_by_filtering_kind(section, kind_name,
1340                                                        'namespaces'):
1341    if any(filter_visibility(inner_namespace.merged_entries, visibilities)):
1342      return True
1343
1344  return any(filter_visibility(get_children_by_filtering_kind(section, kind_name,
1345                                                              'merged_entries'),
1346                               visibilities))
1347
1348
1349def filter_visibility(entries, visibilities):
1350  """
1351  Remove entries whose applied visibility is not in the supplied visibilities.
1352
1353  Args:
1354    entries: An iterable of Entry nodes
1355    visibilities: An iterable of visibilities to filter against
1356
1357  Yields:
1358    An iterable of Entry nodes
1359  """
1360  return (e for e in entries if e.applied_visibility in visibilities)
1361
1362def remove_hal_non_visible(entries):
1363  """
1364  Filter the given entries by removing those that are not HAL visible:
1365  synthetic, fwk_only, or fwk_java_public.
1366
1367  Args:
1368    entries: An iterable of Entry nodes
1369
1370  Yields:
1371    An iterable of Entry nodes
1372  """
1373  return (e for e in entries if not (e.synthetic or e.visibility == 'fwk_only'
1374                                     or e.visibility == 'fwk_java_public'))
1375
1376"""
1377  Return the vndk version for a given hal minor version. The major version is assumed to be 3
1378
1379  Args:
1380    hal_minor_version : minor version to retrieve the vndk version for
1381
1382  Yields:
1383    int representing the vndk version
1384  """
1385def get_vndk_version(hal_minor_version):
1386  if hal_minor_version <= FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION:
1387    return 0
1388  return hal_minor_version - FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION \
1389        + FRAMEWORK_CAMERA_VNDK_STARTING_VERSION
1390
1391"""
1392  Returns an api level -> dict of metadata tags corresponding to the api level
1393
1394  Args:
1395    sections : metadata sections to create the mapping for
1396    metadata: the metadata structure to be used to create the mapping
1397    kind : kind of entries to create a mapping for : 'static' or 'dynamic'
1398
1399  Yields:
1400    A dictionary mapping api level to a dictionary of metadata tags for the particular key (api level)
1401  """
1402def get_api_level_to_keys(sections, metadata, kind):
1403  api_level_to_keys = {}
1404  for sec in sections:
1405    for idx,entry in enumerate(remove_synthetic(find_unique_entries(sec))):
1406      if entry._hal_minor_version > FRAMEWORK_CAMERA_VNDK_HAL_MINOR_VERSION and \
1407          metadata.is_entry_this_kind(entry, kind):
1408        api_level = get_vndk_version(entry._hal_minor_version)
1409        try:
1410          api_level_to_keys[api_level].add(entry.name)
1411        except KeyError:
1412          api_level_to_keys[api_level] = {entry.name}
1413  #Insert the keys in sorted order since dicts in python (< 3.7, even OrderedDicts don't actually
1414  # sort keys)
1415  api_level_to_keys_ordered = OrderedDict()
1416  for api_level_ordered in sorted(api_level_to_keys.keys()):
1417    api_level_to_keys_ordered[api_level_ordered] = sorted(api_level_to_keys[api_level_ordered])
1418  return api_level_to_keys_ordered
1419
1420def remove_synthetic(entries):
1421  """
1422  Filter the given entries by removing those that are synthetic.
1423
1424  Args:
1425    entries: An iterable of Entry nodes
1426
1427  Yields:
1428    An iterable of Entry nodes
1429  """
1430  return (e for e in entries if not e.synthetic)
1431
1432def filter_added_in_hal_version(entries, hal_major_version, hal_minor_version):
1433  """
1434  Filter the given entries to those added in the given HIDL HAL version
1435
1436  Args:
1437    entries: An iterable of Entry nodes
1438    hal_major_version: Major HIDL version to filter for
1439    hal_minor_version: Minor HIDL version to filter for
1440
1441  Yields:
1442    An iterable of Entry nodes
1443  """
1444  return (e for e in entries if e.hal_major_version == hal_major_version and e.hal_minor_version == hal_minor_version)
1445
1446def filter_has_enum_values_added_in_hal_version(entries, hal_major_version, hal_minor_version):
1447  """
1448  Filter the given entries to those that have a new enum value added in the given HIDL HAL version
1449
1450  Args:
1451    entries: An iterable of Entry nodes
1452    hal_major_version: Major HIDL version to filter for
1453    hal_minor_version: Minor HIDL version to filter for
1454
1455  Yields:
1456    An iterable of Entry nodes
1457  """
1458  return (e for e in entries if e.has_new_values_added_in_hal_version(hal_major_version, hal_minor_version))
1459
1460def permission_needed_count(root):
1461  """
1462  Return the number entries that need camera permission.
1463
1464  Args:
1465    root: a Metadata instance
1466
1467  Returns:
1468    The number of entires that need camera permission.
1469
1470  """
1471  ret = 0
1472  for sec in find_all_sections(root):
1473      ret += len(list(filter_has_permission_needed(remove_hal_non_visible(find_unique_entries(sec)))))
1474
1475  return ret
1476
1477def filter_has_permission_needed(entries):
1478  """
1479    Filter the given entries by removing those that don't need camera permission.
1480
1481    Args:
1482      entries: An iterable of Entry nodes
1483
1484    Yields:
1485      An iterable of Entry nodes
1486  """
1487  return (e for e in entries if e.permission_needed == 'true')
1488
1489def filter_ndk_visible(entries):
1490  """
1491  Filter the given entries by removing those that are not NDK visible.
1492
1493  Args:
1494    entries: An iterable of Entry nodes
1495
1496  Yields:
1497    An iterable of Entry nodes
1498  """
1499  return (e for e in entries if e.applied_ndk_visible == 'true')
1500
1501def wbr(text):
1502  """
1503  Insert word break hints for the browser in the form of <wbr> HTML tags.
1504
1505  Word breaks are inserted inside an HTML node only, so the nodes themselves
1506  will not be changed. Attributes are also left unchanged.
1507
1508  The following rules apply to insert word breaks:
1509  - For characters in [ '.', '/', '_' ]
1510  - For uppercase letters inside a multi-word X.Y.Z (at least 3 parts)
1511
1512  Args:
1513    text: A string of text containing HTML content.
1514
1515  Returns:
1516    A string with <wbr> inserted by the above rules.
1517  """
1518  SPLIT_CHARS_LIST = ['.', '_', '/']
1519  SPLIT_CHARS = r'([.|/|_/,]+)' # split by these characters
1520  CAP_LETTER_MIN = 3 # at least 3 components split by above chars, i.e. x.y.z
1521  def wbr_filter(text):
1522      new_txt = text
1523
1524      # for johnyOrange.appleCider.redGuardian also insert wbr before the caps
1525      # => johny<wbr>Orange.apple<wbr>Cider.red<wbr>Guardian
1526      for words in text.split(" "):
1527        for char in SPLIT_CHARS_LIST:
1528          # match at least x.y.z, don't match x or x.y
1529          if len(words.split(char)) >= CAP_LETTER_MIN:
1530            new_word = re.sub(r"([a-z])([A-Z])", r"\1<wbr>\2", words)
1531            new_txt = new_txt.replace(words, new_word)
1532
1533      # e.g. X/Y/Z -> X/<wbr>Y/<wbr>/Z. also for X.Y.Z, X_Y_Z.
1534      new_txt = re.sub(SPLIT_CHARS, r"\1<wbr>", new_txt)
1535
1536      return new_txt
1537
1538  # Do not mangle HTML when doing the replace by using BeatifulSoup
1539  # - Use the 'html.parser' to avoid inserting <html><body> when decoding
1540  soup = bs4.BeautifulSoup(text, features='html.parser')
1541  wbr_tag = lambda: soup.new_tag('wbr') # must generate new tag every time
1542
1543  for navigable_string in soup.findAll(text=True):
1544      parent = navigable_string.parent
1545
1546      # Insert each '$text<wbr>$foo' before the old '$text$foo'
1547      split_by_wbr_list = wbr_filter(navigable_string).split("<wbr>")
1548      for (split_string, last) in enumerate_with_last(split_by_wbr_list):
1549          navigable_string.insert_before(split_string)
1550
1551          if not last:
1552            # Note that 'insert' will move existing tags to this spot
1553            # so make a new tag instead
1554            navigable_string.insert_before(wbr_tag())
1555
1556      # Remove the old unmodified text
1557      navigable_string.extract()
1558
1559  return soup.decode()
1560
1561def copyright_year():
1562  return _copyright_year
1563
1564def enum():
1565  return _enum
1566
1567def first_hal_minor_version(hal_major_version):
1568  return 2 if hal_major_version == 3 else 0
1569
1570def find_all_sections_added_in_hal(root, hal_major_version, hal_minor_version):
1571  """
1572  Find all descendants that are Section or InnerNamespace instances, which
1573  were added in HIDL HAL version major.minor. The section is defined to be
1574  added in a HAL version iff the lowest HAL version number of its entries is
1575  that HAL version.
1576
1577  Args:
1578    root: a Metadata instance
1579    hal_major/minor_version: HAL version numbers
1580
1581  Returns:
1582    A list of Section/InnerNamespace instances
1583
1584  Remarks:
1585    These are known as "sections" in the generated C code.
1586  """
1587  all_sections = find_all_sections(root)
1588  new_sections = []
1589  for section in all_sections:
1590    min_major_version = None
1591    min_minor_version = None
1592    for entry in remove_hal_non_visible(find_unique_entries(section)):
1593      min_major_version = (min_major_version or entry.hal_major_version)
1594      min_minor_version = (min_minor_version or entry.hal_minor_version)
1595      if entry.hal_major_version < min_major_version or \
1596          (entry.hal_major_version == min_major_version and entry.hal_minor_version < min_minor_version):
1597        min_minor_version = entry.hal_minor_version
1598        min_major_version = entry.hal_major_version
1599    if min_major_version == hal_major_version and min_minor_version == hal_minor_version:
1600      new_sections.append(section)
1601  return new_sections
1602
1603def find_first_older_used_hal_version(section, hal_major_version, hal_minor_version):
1604  hal_version = (0, 0)
1605  for v in section.hal_versions:
1606    if (v[0] > hal_version[0] or (v[0] == hal_version[0] and v[1] > hal_version[1])) and \
1607        (v[0] < hal_major_version or (v[0] == hal_major_version and v[1] < hal_minor_version)):
1608      hal_version = v
1609  return hal_version
1610
1611# Some exceptions need to be made regarding enum value identifiers in AIDL.
1612# Process them here.
1613def aidl_enum_value_name(name):
1614  if name == 'ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_HIDL_DEVICE_3_5':
1615    name = 'ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_AIDL_DEVICE'
1616  return name
1617
1618def aidl_enum_values(entry):
1619  ignoreList = [
1620    'ANDROID_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_PUBLIC_END',
1621    'ANDROID_SCALER_AVAILABLE_RECOMMENDED_STREAM_CONFIGURATIONS_PUBLIC_END_3_8'
1622  ]
1623  return [
1624    val for val in entry.enum.values if '%s_%s'%(csym(entry.name), val.name) not in ignoreList
1625  ]