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