1 /* 2 * Copyright (C) 2018 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 package com.android.game.qualification; 17 18 import org.w3c.dom.Document; 19 import org.w3c.dom.Element; 20 import org.w3c.dom.Node; 21 import org.w3c.dom.NodeList; 22 import org.xml.sax.SAXException; 23 24 import java.io.File; 25 import java.io.FileInputStream; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.List; 31 import java.util.Locale; 32 import java.util.function.Function; 33 34 import javax.xml.parsers.DocumentBuilder; 35 import javax.xml.parsers.DocumentBuilderFactory; 36 import javax.xml.parsers.ParserConfigurationException; 37 38 /** 39 * Parser to read apk-info.xml. 40 */ 41 public class GameCoreConfigurationXmlParser { 42 43 public enum Field { 44 NAME("name", null), 45 46 // Certification requirements 47 TARGET_FRAME_TIME("frameTime", "16.666"), 48 MAX_JANK_RATE("jankRate", "0.0"), 49 MAX_LOAD_TIME("loadTime", "-1"), 50 51 // Apk info 52 FILE_NAME("fileName", null), 53 PACKAGE_NAME("packageName", null), 54 ACTIVITY_NAME("activityName", null), 55 LAYER_NAME("layerName", null), 56 SCRIPT("script", null), 57 ARGS("args", null), 58 LOAD_TIME("loadTime", "10000"), 59 RUN_TIME("runTime", "10000"), 60 EXPECT_INTENTS("expectIntents", "false"); 61 62 private String mTag; 63 private String mDefaultValue; 64 Field(String tag, String defaultValue)65 Field(String tag, String defaultValue) { 66 mTag = tag; 67 mDefaultValue = defaultValue; 68 } 69 getTag()70 public String getTag() { 71 return mTag; 72 } 73 getDefaultValue()74 public String getDefaultValue() { 75 return mDefaultValue; 76 } 77 } 78 GameCoreConfigurationXmlParser()79 public GameCoreConfigurationXmlParser() { 80 } 81 parse(File file)82 public GameCoreConfiguration parse(File file) 83 throws IOException, ParserConfigurationException, SAXException { 84 try (InputStream is = new FileInputStream(file)) { 85 return parse(is); 86 } 87 } 88 parse(InputStream inputStream)89 public GameCoreConfiguration parse(InputStream inputStream) 90 throws ParserConfigurationException, IOException, SAXException { 91 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 92 DocumentBuilder db = dbf.newDocumentBuilder(); 93 Document doc = db.parse(inputStream); 94 doc.getDocumentElement().normalize(); 95 96 List<CertificationRequirements> requirements = parseList( 97 doc.getDocumentElement(), 98 "certification", 99 this::createCertificationRequirements); 100 if (requirements == null) { 101 requirements = Collections.emptyList(); 102 } 103 List<ApkInfo> apkInfo = 104 parseList(doc.getDocumentElement(), "apk-info", this::createApkInfo); 105 return new GameCoreConfiguration(requirements, apkInfo); 106 } 107 parseList(Element element, String tagName, Function<Element, T> parser)108 private <T> List<T> parseList(Element element, String tagName, Function<Element, T> parser) { 109 NodeList nodeList = element.getElementsByTagName(tagName); 110 if (nodeList.getLength() != 1) { 111 return null; 112 } 113 List<T> result = new ArrayList<>(); 114 NodeList children = nodeList.item(0).getChildNodes(); 115 for (int i = 0; i < children.getLength(); i++) { 116 Node child = children.item(i); 117 if (child.getNodeType() == Node.ELEMENT_NODE) { 118 result.add(parser.apply((Element) child)); 119 } 120 } 121 return result; 122 } 123 createCertificationRequirements(Element element)124 private CertificationRequirements createCertificationRequirements(Element element) { 125 if (!element.getTagName().equals("apk")) { 126 throw new RuntimeException( 127 "Unexpected tag <" 128 + element.getNodeName() 129 + "> in <certification>. Only <apk> is allowed." ); 130 } 131 return new CertificationRequirements( 132 getElement(element, Field.NAME), 133 Float.parseFloat(getElement(element, Field.TARGET_FRAME_TIME)), 134 Float.parseFloat(getElement(element, Field.MAX_JANK_RATE)), 135 Integer.parseInt(getElement(element, Field.MAX_LOAD_TIME))); 136 } 137 createApkInfo(Element element)138 private ApkInfo createApkInfo(Element element) { 139 if (!element.getTagName().equals("apk")) { 140 throw new RuntimeException( 141 "Unexpected tag <" 142 + element.getNodeName() 143 + "> in <apk-info>. Only <apk> is allowed." ); 144 } 145 146 List<ApkInfo.Argument> args = parseList(element, Field.ARGS.getTag(), this::createArgument); 147 if (args == null) { 148 args = Collections.emptyList(); 149 } 150 151 return new ApkInfo( 152 getElement(element, Field.NAME), 153 getElement(element, Field.FILE_NAME), 154 getElement(element, Field.PACKAGE_NAME), 155 getElement(element, Field.ACTIVITY_NAME), 156 getElement(element, Field.LAYER_NAME), 157 getElement(element, Field.SCRIPT), 158 args, 159 Integer.parseInt(getElement(element, Field.LOAD_TIME)), 160 Integer.parseInt(getElement(element, Field.RUN_TIME)), 161 Boolean.parseBoolean(getElement(element, Field.EXPECT_INTENTS)) 162 ); 163 } 164 createArgument(Element element)165 private ApkInfo.Argument createArgument(Element element) { 166 String type = element.getAttribute("type"); 167 if (type == null || type.isEmpty()) { 168 type = "STRING"; 169 } 170 return new ApkInfo.Argument( 171 element.getTagName(), 172 element.getTextContent(), 173 ApkInfo.Argument.Type.valueOf(type.toUpperCase(Locale.US))); 174 } 175 getElement(Element element, Field field)176 private String getElement(Element element, Field field) { 177 NodeList elements = element.getElementsByTagName(field.getTag()); 178 if (elements.getLength() > 0) { 179 return elements.item(0).getTextContent(); 180 } else { 181 return field.getDefaultValue(); 182 } 183 } 184 } 185