1 /* 2 * Copyright (C) 2010 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 package com.android.tradefed.targetprep; 18 19 import com.android.tradefed.command.remote.DeviceDescriptor; 20 import com.android.tradefed.util.MultiMap; 21 22 import java.io.BufferedReader; 23 import java.io.File; 24 import java.io.IOException; 25 import java.io.InputStreamReader; 26 import java.util.ArrayList; 27 import java.util.Collection; 28 import java.util.HashMap; 29 import java.util.Map; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 import java.util.zip.ZipEntry; 33 import java.util.zip.ZipException; 34 import java.util.zip.ZipFile; 35 36 /** 37 * A class that parses out required versions of auxiliary image files needed to flash a device. 38 * (e.g. bootloader, baseband, etc) 39 */ 40 public class FlashingResourcesParser implements IFlashingResourcesParser { 41 /** 42 * A filtering interface, intended to allow {@link FlashingResourcesParser} to ignore some 43 * resources that it otherwise might use 44 */ 45 public static interface Constraint { 46 /** 47 * Check if the provided {@code item} passes the constraint. 48 * @return {@code true} for accept, {@code false} for reject 49 */ 50 public boolean shouldAccept(String item); 51 } 52 53 private static final String ANDROID_INFO_FILE_NAME = "android-info.txt"; 54 /** 55 * Some resource files use "require-foo=bar", others use "foo=bar". This expression handles 56 * both. 57 */ 58 private static final Pattern REQUIRE_PATTERN = Pattern.compile("(?:require\\s)?(.*?)=(.*)"); 59 /** 60 * Some resource files have special product-specific requirements, for instance: 61 * {@code require-for-product:product1 version-bootloader=xyz} would only require bootloader 62 * {@code xyz} for device {@code product1}. This pattern matches the require-for-product line 63 */ 64 private static final Pattern PRODUCT_REQUIRE_PATTERN = 65 Pattern.compile("require-for-product:(\\S+) +(.*?)=(.*)"); 66 67 // expected keys 68 public static final String PRODUCT_KEY = "product"; 69 public static final String BOARD_KEY = "board"; 70 public static final String BOOTLOADER_VERSION_KEY = "version-bootloader"; 71 public static final String BASEBAND_VERSION_KEY = "version-baseband"; 72 73 // key-value pairs of build requirements 74 private AndroidInfo mReqs; 75 76 /** 77 * A typedef for {@code Map<String, MultiMap<String, String>>}. Useful parsed 78 * format for storing the data encoded in ANDROID_INFO_FILE_NAME 79 */ 80 @SuppressWarnings("serial") 81 public static class AndroidInfo extends HashMap<String, MultiMap<String, String>> {} 82 83 /** 84 * Create a {@link FlashingResourcesParser} and have it parse the specified device image for 85 * flashing requirements. Flashing requirements must pass the appropriate constraint (if one 86 * exists) before being added. Rejected requirements will be dropped silently. 87 * 88 * @param deviceImgZipFile The {@code updater.zip} file to be flashed 89 * @param c A map from key name to {@link Constraint}. Image names will be checked against 90 * the appropriate constraint (if any) as a prereq for being added. May be null to 91 * disable filtering. 92 */ 93 public FlashingResourcesParser(File deviceImgZipFile, Map<String, Constraint> c) 94 throws TargetSetupError { 95 mReqs = getBuildRequirements(deviceImgZipFile, c); 96 } 97 98 /** 99 * Create a {@link FlashingResourcesParser} and have it parse the specified device image for 100 * flashing requirements. 101 * 102 * @param deviceImgZipFile The {@code updater.zip} file to be flashed 103 */ 104 public FlashingResourcesParser(File deviceImgZipFile) throws TargetSetupError { 105 this(deviceImgZipFile, null); 106 } 107 108 /** 109 * Constructs a FlashingResourcesParser with the supplied AndroidInfo Reader 110 * <p/> 111 * Exposed for unit testing 112 * 113 * @param infoReader a {@link BufferedReader} containing the equivalent of android-info.txt to 114 * parse 115 * @param c A map from key name to {@link Constraint}. Image names will be checked against 116 * the appropriate constraint (if any) as a prereq for being added. May be null to 117 * disable filtering. 118 */ 119 public FlashingResourcesParser(BufferedReader infoReader, Map<String, Constraint> c) 120 throws IOException { 121 mReqs = parseAndroidInfo(infoReader, c); 122 } 123 124 /** 125 * Constructs a FlashingResourcesParser with the supplied AndroidInfo Reader 126 * <p/> 127 * Exposed for unit testing 128 * 129 * @param infoReader a {@link BufferedReader} containing the equivalent of android-info.txt to 130 * parse 131 */ 132 public FlashingResourcesParser(BufferedReader infoReader) throws IOException { 133 this(infoReader, null); 134 } 135 136 /** 137 * {@inheritDoc} 138 * <p/> 139 * If multiple versions are listed, get the latest with the assumption that versions sort from 140 * oldest to newest alphabetically. 141 */ 142 @Override 143 public String getRequiredBootloaderVersion() { 144 return getRequiredImageVersion(BOOTLOADER_VERSION_KEY); 145 } 146 147 /** 148 * {@inheritDoc} 149 * <p/> 150 * If multiple versions are listed, get the latest with the assumption that versions sort from 151 * oldest to newest alphabetically. 152 */ 153 @Override 154 public String getRequiredBasebandVersion() { 155 return getRequiredImageVersion(BASEBAND_VERSION_KEY); 156 } 157 158 /** 159 * {@inheritDoc} 160 * <p/> 161 * If multiple versions are listed, get the latest with the assumption that versions sort from 162 * oldest to newest alphabetically. 163 */ 164 @Override 165 public String getRequiredImageVersion(String imageVersionKey) { 166 // Use null to designate the global product requirements 167 return getRequiredImageVersion(imageVersionKey, null); 168 } 169 170 /** 171 * {@inheritDoc} 172 * <p/> 173 * If multiple versions are listed, get the latest with the assumption that versions sort from 174 * oldest to newest alphabetically. 175 */ 176 @Override 177 public String getRequiredImageVersion(String imageVersionKey, String productName) { 178 MultiMap<String, String> productReqs = mReqs.get(productName); 179 180 if (productReqs == null && productName != null) { 181 // There aren't any product-specific requirements for productName. Fall back to global 182 // requirements. 183 return getRequiredImageVersion(imageVersionKey, null); 184 } 185 186 // Get the latest version assuming versions are sorted alphabetically. 187 String result = getNewest(productReqs.get(imageVersionKey)); 188 189 if (result != null) { 190 // If there's a result, return it 191 return result; 192 } 193 if (result == null && productName != null) { 194 // There aren't any product-specific requirements for this particular imageVersionKey 195 // for productName. Fall back to global requirements. 196 return getRequiredImageVersion(imageVersionKey, null); 197 } 198 199 // Neither a specific nor a global result exists; return null 200 return null; 201 } 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override 207 public Collection<String> getRequiredBoards() { 208 Collection<String> all = new ArrayList<String>(); 209 MultiMap<String, String> boardReqs = mReqs.get(null); 210 if (boardReqs == null) { 211 return null; 212 } 213 214 Collection<String> board = boardReqs.get(BOARD_KEY); 215 Collection<String> product = boardReqs.get(PRODUCT_KEY); 216 217 // board overrides product here 218 if (board != null) { 219 all.addAll(board); 220 } else if (product != null) { 221 all.addAll(product); 222 } else { 223 return null; 224 } 225 226 return all; 227 } 228 229 /** 230 * Gets the newest element in the given {@link Collection} or <code>null</code> with the 231 * assumption that newer elements follow older elements when sorted alphabetically. 232 */ 233 private static String getNewest(Collection<String> values) { 234 if (values == null || values.isEmpty()) { 235 return null; 236 } 237 String newest = null; 238 for (String element : values) { 239 if (newest == null || element.compareTo(newest) > 0) { 240 newest = element; 241 } 242 } 243 return newest; 244 } 245 246 /** 247 * This parses android-info.txt from system image zip and returns key value pairs of required 248 * image files. 249 * <p/> 250 * Expects the following syntax: 251 * <p/> 252 * <i>[require] key=value1[|value2]</i> 253 * 254 * @return a {@link Map} of parsed key value pairs, or <code>null</code> if data could not be 255 * parsed 256 */ 257 static AndroidInfo getBuildRequirements(File deviceImgZipFile, 258 Map<String, Constraint> constraints) throws TargetSetupError { 259 ZipFile deviceZip = null; 260 BufferedReader infoReader = null; 261 try { 262 deviceZip = new ZipFile(deviceImgZipFile); 263 ZipEntry androidInfoEntry = deviceZip.getEntry(ANDROID_INFO_FILE_NAME); 264 if (androidInfoEntry == null) { 265 DeviceDescriptor nullDescriptor = null; 266 throw new TargetSetupError(String.format("Could not find %s in device image zip %s", 267 ANDROID_INFO_FILE_NAME, deviceImgZipFile.getName()), nullDescriptor); 268 } 269 infoReader = new BufferedReader(new InputStreamReader( 270 deviceZip.getInputStream(androidInfoEntry))); 271 272 return parseAndroidInfo(infoReader, constraints); 273 } catch (ZipException e) { 274 throw new TargetSetupError(String.format("Could not read device image zip %s", 275 deviceImgZipFile.getName()), e, null); 276 } catch (IOException e) { 277 throw new TargetSetupError(String.format("Could not read device image zip %s", 278 deviceImgZipFile.getName()), e, null); 279 } finally { 280 if (deviceZip != null) { 281 try { 282 deviceZip.close(); 283 } catch (IOException e) { 284 // ignore 285 } 286 } 287 if (infoReader != null) { 288 try { 289 infoReader.close(); 290 } catch (IOException e) { 291 // ignore 292 } 293 } 294 } 295 } 296 297 /** 298 * Returns the current value for the provided key if one exists, or creates and returns a new 299 * value if one does not exist. 300 */ 301 private static MultiMap<String, String> getOrCreateEntry(AndroidInfo map, String key) { 302 if (map.containsKey(key)) { 303 return map.get(key); 304 } else { 305 MultiMap<String, String> value = new MultiMap<String, String>(); 306 map.put(key, value); 307 return value; 308 } 309 } 310 311 /** 312 * Parses the required build attributes from an android-info data source. 313 * <p/> 314 * Exposed as package-private for unit testing. 315 * 316 * @param infoReader the {@link BufferedReader} to read android-info text data from 317 * @return a Map of parsed attribute name-value pairs 318 * @throws IOException 319 */ 320 static AndroidInfo parseAndroidInfo(BufferedReader infoReader, 321 Map<String, Constraint> constraints) throws IOException { 322 AndroidInfo requiredImageMap = new AndroidInfo(); 323 324 boolean eof = false; 325 while (!eof) { 326 String line = infoReader.readLine(); 327 if (line != null) { 328 Matcher matcher = PRODUCT_REQUIRE_PATTERN.matcher(line); 329 if (matcher.matches()) { 330 String product = matcher.group(1); 331 String key = matcher.group(2); 332 String values = matcher.group(3); 333 // Requirements specific to product {@code product} 334 MultiMap<String, String> reqs = getOrCreateEntry(requiredImageMap, product); 335 for (String value : values.split("\\|")) { 336 reqs.put(key, value); 337 } 338 } else { 339 matcher = REQUIRE_PATTERN.matcher(line); 340 if (matcher.matches()) { 341 String key = matcher.group(1); 342 String values = matcher.group(2); 343 Constraint c = null; 344 if (constraints != null) { 345 c = constraints.get(key); 346 } 347 348 // Use a null product identifier to designate requirements for all products 349 MultiMap<String, String> reqs = getOrCreateEntry(requiredImageMap, null); 350 for (String value : values.split("\\|")) { 351 if ((c == null) || c.shouldAccept(value)) { 352 reqs.put(key, value); 353 } 354 } 355 } 356 } 357 } else { 358 eof = true; 359 } 360 } 361 return requiredImageMap; 362 } 363 } 364