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