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