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