1 package org.robolectric.versioning; 2 3 /* 4 * Copyright (C) 2023 The Android Open Source Project 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 import static java.util.Arrays.asList; 20 21 import java.io.IOException; 22 import java.lang.reflect.Field; 23 import java.lang.reflect.InvocationTargetException; 24 import java.lang.reflect.Modifier; 25 import java.util.AbstractMap; 26 import java.util.ArrayList; 27 import java.util.Collections; 28 import java.util.HashMap; 29 import java.util.List; 30 import java.util.Locale; 31 import java.util.Map; 32 import java.util.Properties; 33 import java.util.jar.JarFile; 34 import java.util.zip.ZipEntry; 35 import javax.annotation.Nullable; 36 import org.robolectric.util.Logger; 37 import org.robolectric.util.ReflectionHelpers; 38 39 /** 40 * Android versioning is complicated.<br> 41 * 1) There is a yearly letter release with an increasing of one alpha step each year A-> B, B-> C, 42 * and so on. While commonly referenced these are not the release numbers. This class calls these 43 * shortcodes. Also minor version number releases (usually within the same year) will start with the 44 * same letter.<br> 45 * 2) There is an SDK_INT field in android.os.Build.VERSION that tracks a version of the internal 46 * SDK. While useful to track the actual released versions of Android, these are not the release 47 * number. More importantly, android.os.Build.VERSION uses code names to describe future versions. 48 * Multiple code names may be in development at once on different branches of Android.<br> 49 * 3) There is a yearly release major number followed by a minor number, which may or may not be 50 * used.<br> 51 * 4) Relevant logic and reasoning should match androidx.core.os.BuildCompat.java with the caveat 52 * that this class guess at the future release version number and short of the current dev branch. 53 * <br> 54 */ 55 public final class AndroidVersions { 56 AndroidVersions()57 private AndroidVersions() {} 58 59 /** Representation of an android release, one that has occurred, or is expected. */ 60 public abstract static class AndroidRelease implements Comparable<AndroidRelease> { 61 62 /** 63 * true if this release has already occurred, false otherwise. If unreleased, the getSdkInt may 64 * still be that of the prior release. 65 */ getSdkInt()66 public int getSdkInt() { 67 return ReflectionHelpers.getStaticField(this.getClass(), "SDK_INT"); 68 } 69 70 /** 71 * single character short code for the release, multiple characters for minor releases (only 72 * minor version numbers increment - usually within the same year). 73 */ getShortCode()74 public String getShortCode() { 75 return ReflectionHelpers.getStaticField(this.getClass(), "SHORT_CODE"); 76 } 77 78 /** 79 * true if this release has already occurred, false otherwise. If unreleased, the getSdkInt will 80 * guess at the likely sdk number. Your code will need to recompile if this value changes - 81 * including most modern build tools; bazle, soong all are full build systems - and as such 82 * organizations using them have no concerns. 83 */ isReleased()84 public boolean isReleased() { 85 return ReflectionHelpers.getStaticField(this.getClass(), "RELEASED"); 86 } 87 88 /** major.minor version number as String. */ getVersion()89 public String getVersion() { 90 return ReflectionHelpers.getStaticField(this.getClass(), "VERSION"); 91 } 92 93 /** 94 * Implements comparable. 95 * 96 * @param other the object to be compared. 97 * @return 1 if this is greater than other, 0 if equal, -1 if less 98 * @throws RuntimeException if other is not an instance of AndroidRelease. 99 */ 100 @Override compareTo(AndroidRelease other)101 public int compareTo(AndroidRelease other) { 102 if (other == null) { 103 throw new RuntimeException( 104 "Only " 105 + AndroidVersions.class.getName() 106 + " should define Releases, illegal class " 107 + other.getClass()); 108 } 109 return Integer.compare(this.getSdkInt(), other.getSdkInt()); 110 } 111 112 @Override toString()113 public String toString() { 114 return "Android " 115 + (this.isReleased() ? "" : "Future ") 116 + "Release: " 117 + this.getVersion() 118 + " ( sdk: " 119 + this.getSdkInt() 120 + " code: " 121 + this.getShortCode() 122 + " )"; 123 } 124 } 125 126 /** 127 * Version: 4.1 <br> 128 * ShortCode: J <br> 129 * SDK API Level: 16 <br> 130 * release: true <br> 131 */ 132 public static final class J extends AndroidRelease { 133 134 public static final int SDK_INT = 16; 135 136 public static final boolean RELEASED = true; 137 138 public static final String SHORT_CODE = "J"; 139 140 public static final String VERSION = "4.1"; 141 } 142 143 /** 144 * Version: 4.2 <br> 145 * ShortCode: JMR1 <br> 146 * SDK API Level: 17 <br> 147 * release: true <br> 148 */ 149 public static final class JMR1 extends AndroidRelease { 150 151 public static final int SDK_INT = 17; 152 153 public static final boolean RELEASED = true; 154 155 public static final String SHORT_CODE = "JMR1"; 156 157 public static final String VERSION = "4.2"; 158 } 159 160 /** 161 * Version: 4.3 <br> 162 * ShortCode: JMR2 <br> 163 * SDK API Level: 18 <br> 164 * release: true <br> 165 */ 166 public static final class JMR2 extends AndroidRelease { 167 168 public static final int SDK_INT = 18; 169 170 public static final boolean RELEASED = true; 171 172 public static final String SHORT_CODE = "JMR2"; 173 174 public static final String VERSION = "4.3"; 175 } 176 177 /** 178 * Version: 4.4 <br> 179 * ShortCode: K <br> 180 * SDK API Level: 19 <br> 181 * release: true <br> 182 */ 183 public static final class K extends AndroidRelease { 184 185 public static final int SDK_INT = 19; 186 187 public static final boolean RELEASED = true; 188 189 public static final String SHORT_CODE = "K"; 190 191 public static final String VERSION = "4.4"; 192 } 193 194 // Skipping K Watch release, which was 20. 195 196 /** 197 * Version: 5.0 <br> 198 * ShortCode: L <br> 199 * SDK API Level: 21 <br> 200 * release: true <br> 201 */ 202 public static final class L extends AndroidRelease { 203 204 public static final int SDK_INT = 21; 205 206 public static final boolean RELEASED = true; 207 208 public static final String SHORT_CODE = "L"; 209 210 public static final String VERSION = "5.0"; 211 } 212 213 /** 214 * Version: 5.1 <br> 215 * ShortCode: LMR1 <br> 216 * SDK API Level: 22 <br> 217 * release: true <br> 218 */ 219 public static final class LMR1 extends AndroidRelease { 220 221 public static final int SDK_INT = 22; 222 223 public static final boolean RELEASED = true; 224 225 public static final String SHORT_CODE = "LMR1"; 226 227 public static final String VERSION = "5.1"; 228 } 229 230 /** 231 * Version: 6.0 <br> 232 * ShortCode: M <br> 233 * SDK API Level: 23 <br> 234 * release: true <br> 235 */ 236 public static final class M extends AndroidRelease { 237 238 public static final int SDK_INT = 23; 239 240 public static final boolean RELEASED = true; 241 242 public static final String SHORT_CODE = "M"; 243 244 public static final String VERSION = "6.0"; 245 } 246 247 /** 248 * Version: 7.0 <br> 249 * ShortCode: N <br> 250 * SDK API Level: 24 <br> 251 * release: true <br> 252 */ 253 public static final class N extends AndroidRelease { 254 255 public static final int SDK_INT = 24; 256 257 public static final boolean RELEASED = true; 258 259 public static final String SHORT_CODE = "N"; 260 261 public static final String VERSION = "7.0"; 262 } 263 264 /** 265 * Release: 7.1 <br> 266 * ShortCode: NMR1 <br> 267 * SDK Framework: 25 <br> 268 * release: true <br> 269 */ 270 public static final class NMR1 extends AndroidRelease { 271 272 public static final int SDK_INT = 25; 273 274 public static final boolean RELEASED = true; 275 276 public static final String SHORT_CODE = "NMR1"; 277 278 private static final String VERSION = "7.1"; 279 } 280 281 /** 282 * Release: 8.0 <br> 283 * ShortCode: O <br> 284 * SDK API Level: 26 <br> 285 * release: true <br> 286 */ 287 public static final class O extends AndroidRelease { 288 289 public static final int SDK_INT = 26; 290 291 public static final boolean RELEASED = true; 292 293 public static final String SHORT_CODE = "O"; 294 295 public static final String VERSION = "8.0"; 296 } 297 298 /** 299 * Release: 8.1 <br> 300 * ShortCode: OMR1 <br> 301 * SDK API Level: 27 <br> 302 * release: true <br> 303 */ 304 public static final class OMR1 extends AndroidRelease { 305 306 public static final int SDK_INT = 27; 307 308 public static final boolean RELEASED = true; 309 310 public static final String SHORT_CODE = "OMR1"; 311 312 public static final String VERSION = "8.1"; 313 } 314 315 /** 316 * Release: 9.0 <br> 317 * ShortCode: P <br> 318 * SDK API Level: 28 <br> 319 * release: true <br> 320 */ 321 public static final class P extends AndroidRelease { 322 323 public static final int SDK_INT = 28; 324 325 public static final boolean RELEASED = true; 326 327 public static final String SHORT_CODE = "P"; 328 329 public static final String VERSION = "9.0"; 330 } 331 332 /** 333 * Release: 10.0 <br> 334 * ShortCode: Q <br> 335 * SDK API Level: 29 <br> 336 * release: true <br> 337 */ 338 public static final class Q extends AndroidRelease { 339 340 public static final int SDK_INT = 29; 341 342 public static final boolean RELEASED = true; 343 344 public static final String SHORT_CODE = "Q"; 345 346 public static final String VERSION = "10.0"; 347 } 348 349 /** 350 * Release: 11.0 <br> 351 * ShortCode: R <br> 352 * SDK API Level: 30 <br> 353 * release: true <br> 354 */ 355 public static final class R extends AndroidRelease { 356 357 public static final int SDK_INT = 30; 358 359 public static final boolean RELEASED = true; 360 361 public static final String SHORT_CODE = "R"; 362 363 public static final String VERSION = "11.0"; 364 } 365 366 /** 367 * Release: 12.0 <br> 368 * ShortCode: S <br> 369 * SDK API Level: 31 <br> 370 * release: true <br> 371 */ 372 public static final class S extends AndroidRelease { 373 374 public static final int SDK_INT = 31; 375 376 public static final boolean RELEASED = true; 377 378 public static final String SHORT_CODE = "S"; 379 380 public static final String VERSION = "12.0"; 381 } 382 383 /** 384 * Release: 12.1 <br> 385 * ShortCode: Sv2 <br> 386 * SDK API Level: 32 <br> 387 * release: true <br> 388 */ 389 @SuppressWarnings("UPPER_SNAKE_CASE") 390 public static final class Sv2 extends AndroidRelease { 391 392 public static final int SDK_INT = 32; 393 394 public static final boolean RELEASED = true; 395 396 public static final String SHORT_CODE = "Sv2"; 397 398 public static final String VERSION = "12.1"; 399 } 400 401 /** 402 * Release: 13.0 <br> 403 * ShortCode: T <br> 404 * SDK API Level: 33 <br> 405 * release: true <br> 406 */ 407 public static final class T extends AndroidRelease { 408 409 public static final int SDK_INT = 33; 410 411 public static final boolean RELEASED = true; 412 413 public static final String SHORT_CODE = "T"; 414 415 public static final String VERSION = "13.0"; 416 } 417 418 /** 419 * Potential Release: 14.0 <br> 420 * ShortCode: U <br> 421 * SDK API Level: 34 <br> 422 * release: false <br> 423 */ 424 public static final class U extends AndroidRelease { 425 426 public static final int SDK_INT = 34; 427 428 public static final boolean RELEASED = true; 429 430 public static final String SHORT_CODE = "U"; 431 432 public static final String VERSION = "14.0"; 433 } 434 435 /** 436 * Potential Release: 15.0 <br> 437 * ShortCode: V <br> 438 * SDK API Level: 34+ <br> 439 * release: false <br> 440 */ 441 public static final class V extends AndroidRelease { 442 443 public static final int SDK_INT = 35; 444 445 public static final boolean RELEASED = false; 446 447 public static final String SHORT_CODE = "V"; 448 449 public static final String VERSION = "15"; 450 } 451 452 /** The current release this process is running on. */ 453 public static final AndroidRelease CURRENT; 454 455 @Nullable getReleaseForSdkInt(@ullable Integer sdkInt)456 public static AndroidRelease getReleaseForSdkInt(@Nullable Integer sdkInt) { 457 if (sdkInt == null) { 458 return null; 459 } else { 460 return information.sdkIntToAllReleases.get(sdkInt); 461 } 462 } 463 getReleases()464 public static List<AndroidRelease> getReleases() { 465 List<AndroidRelease> output = new ArrayList<>(); 466 for (AndroidRelease release : information.allReleases) { 467 if (release.isReleased()) { 468 output.add(release); 469 } 470 } 471 return output; 472 } 473 getUnreleased()474 public static List<AndroidRelease> getUnreleased() { 475 List<AndroidRelease> output = new ArrayList<>(); 476 for (AndroidRelease release : information.allReleases) { 477 if (!release.isReleased()) { 478 output.add(release); 479 } 480 } 481 return output; 482 } 483 484 /** 485 * Responsible for aggregating and interpreting the static state representing the current 486 * AndroidReleases known to AndroidVersions class. 487 */ 488 static class SdkInformation { 489 final List<AndroidRelease> allReleases; 490 final List<Class<? extends AndroidRelease>> classesWithIllegalNames; 491 final AndroidRelease latestRelease; 492 final AndroidRelease earliestUnreleased; 493 494 // In the future we may need a multimap for sdkInts should they stay static across releases. 495 final Map<Integer, AndroidRelease> sdkIntToAllReleases = new HashMap<>(); 496 final Map<String, AndroidRelease> shortCodeToAllReleases = new HashMap<>(); 497 498 // detected errors 499 final List<Map.Entry<AndroidRelease, AndroidRelease>> sdkIntCollisions = new ArrayList<>(); 500 Map.Entry<AndroidRelease, AndroidRelease> sdkApiMisordered = null; 501 SdkInformation( List<AndroidRelease> releases, List<Class<? extends AndroidRelease>> classesWithIllegalNames)502 public SdkInformation( 503 List<AndroidRelease> releases, 504 List<Class<? extends AndroidRelease>> classesWithIllegalNames) { 505 this.allReleases = releases; 506 this.classesWithIllegalNames = classesWithIllegalNames; 507 AndroidRelease latestRelease = null; 508 AndroidRelease earliestUnreleased = null; 509 for (AndroidRelease release : allReleases) { 510 if (release.isReleased()) { 511 if (latestRelease == null || latestRelease.compareTo(release) > 0) { 512 latestRelease = release; 513 } 514 } else { 515 if (earliestUnreleased == null || earliestUnreleased.compareTo(release) < 0) { 516 earliestUnreleased = release; 517 } 518 } 519 } 520 this.latestRelease = latestRelease; 521 this.earliestUnreleased = earliestUnreleased; 522 verifyStaticInformation(); 523 } 524 verifyStaticInformation()525 private void verifyStaticInformation() { 526 for (AndroidRelease release : this.allReleases) { 527 // Construct a map of all sdkInts to releases and note duplicates 528 AndroidRelease sdkCollision = this.sdkIntToAllReleases.put(release.getSdkInt(), release); 529 if (sdkCollision != null) { 530 this.sdkIntCollisions.add(new AbstractMap.SimpleEntry<>(release, sdkCollision)); 531 } 532 // Construct a map of all short codes to releases, and note duplicates 533 this.shortCodeToAllReleases.put(release.getShortCode(), release); 534 // There is no need to check for shortCode duplicates as the Field name must match the 535 // short code. 536 } 537 if (earliestUnreleased != null 538 && latestRelease != null 539 && latestRelease.getSdkInt() >= earliestUnreleased.getSdkInt()) { 540 sdkApiMisordered = new AbstractMap.SimpleEntry<>(latestRelease, earliestUnreleased); 541 } 542 } 543 throwStaticErrors()544 private void throwStaticErrors() { 545 StringBuilder errors = new StringBuilder(); 546 if (!this.classesWithIllegalNames.isEmpty()) { 547 errors 548 .append("The following classes do not follow the naming criteria for ") 549 .append("releases or do not have the short codes in ") 550 .append("their internal fields. Please correct them: ") 551 .append(this.classesWithIllegalNames) 552 .append("\n"); 553 } 554 if (sdkApiMisordered != null) { 555 errors 556 .append("The latest released sdk ") 557 .append(sdkApiMisordered.getKey().getShortCode()) 558 .append(" has a sdkInt greater than the earliest unreleased sdk ") 559 .append(sdkApiMisordered.getValue().getShortCode()) 560 .append("this implies sdks were released out of order which is highly unlikely.\n"); 561 } 562 if (!sdkIntCollisions.isEmpty()) { 563 errors.append( 564 "The following sdks have different shortCodes, but identical sdkInt " + "versions:\n"); 565 for (Map.Entry<AndroidRelease, AndroidRelease> entry : sdkIntCollisions) { 566 errors 567 .append("Both ") 568 .append(entry.getKey().getShortCode()) 569 .append(" and ") 570 .append(entry.getValue().getShortCode()) 571 .append("have the same sdkInt value of ") 572 .append(entry.getKey().getSdkInt()) 573 .append("\n"); 574 } 575 } 576 if (errors.length() > 0) { 577 throw new RuntimeException( 578 errors 579 .append("Please check the AndroidReleases defined ") 580 .append("in ") 581 .append(AndroidVersions.class.getName()) 582 .append("and ensure they are aligned with the versions of") 583 .append(" Android.") 584 .toString()); 585 } 586 } 587 computeCurrentSdk( int reportedVersion, String releaseName, String codename, List<String> activeCodeNames)588 public AndroidRelease computeCurrentSdk( 589 int reportedVersion, String releaseName, String codename, List<String> activeCodeNames) { 590 Logger.info("Reported Version: " + reportedVersion); 591 Logger.info("Release Name: " + releaseName); 592 Logger.info("Code Name: " + codename); 593 Logger.info("Active Code Names: " + String.join(",", activeCodeNames)); 594 595 AndroidRelease current = null; 596 // Special case "REL", which means the build is not a pre-release build. 597 if ("REL".equals(codename)) { 598 // the first letter of the code name equal to the release number. 599 current = sdkIntToAllReleases.get(reportedVersion); 600 if (current != null && !current.isReleased()) { 601 throw new RuntimeException( 602 "The current sdk " 603 + current.getShortCode() 604 + " has been released. Please update the contents of " 605 + AndroidVersions.class.getName() 606 + " to mark sdk " 607 + current.getShortCode() 608 + " as released."); 609 } 610 } else { 611 // Get known active code name letters 612 613 List<String> activeCodenameLetter = new ArrayList<>(); 614 for (String name : activeCodeNames) { 615 activeCodenameLetter.add(name.toUpperCase(Locale.getDefault()).substring(0, 1)); 616 } 617 618 // If the process is operating with a code name. 619 if (codename != null) { 620 StringBuilder detectedProblems = new StringBuilder(); 621 // This is safe for minor releases ( X.1 ) as long as they have added an entry 622 // corresponding to the sdk of that release and the prior major release is marked as 623 // "released" on its entry in this file. If not this class will fail to initialize. 624 // The assumption is that only one of the major or minor version of a code name 625 // is under development and unreleased at any give time (S or Sv2). 626 String foundCode = codename.toUpperCase(Locale.getDefault()).substring(0, 1); 627 int loc = activeCodenameLetter.indexOf(foundCode); 628 if (loc == -1) { 629 detectedProblems 630 .append("The current codename's (") 631 .append(codename) 632 .append(") first letter (") 633 .append(foundCode) 634 .append(") is not in the list of active code's first letters: ") 635 .append(activeCodenameLetter) 636 .append("\n"); 637 } else { 638 // attempt to find assume the fullname is the "shortCode", aka "Sv2", "OMR1". 639 current = shortCodeToAllReleases.get(codename); 640 // else, assume the fullname is the first letter is correct. 641 if (current == null) { 642 current = shortCodeToAllReleases.get(String.valueOf(foundCode)); 643 } 644 } 645 if (current == null) { 646 detectedProblems 647 .append("No known release is associated with the shortCode of \"") 648 .append(foundCode) 649 .append("\" or \"") 650 .append(codename) 651 .append("\"\n"); 652 } else if (current.isReleased()) { 653 detectedProblems 654 .append("The current sdk ") 655 .append(current.getShortCode()) 656 .append(" has been been marked as released. Please update the ") 657 .append("contents of current sdk jar to the released version.\n"); 658 } 659 if (detectedProblems.length() > 0) { 660 throw new RuntimeException(detectedProblems.toString()); 661 } 662 } 663 } 664 return current; 665 } 666 } 667 668 /** 669 * Reads all AndroidReleases in this class and populates SdkInformation, checking for sanity in 670 * the shortCode, sdkInt, and release information. 671 * 672 * <p>All errors are stored and can be reported at once by asking the SdkInformation to throw a 673 * runtime exception after it has been populated. 674 */ gatherStaticSdkInformationFromThisClass()675 static SdkInformation gatherStaticSdkInformationFromThisClass() { 676 List<AndroidRelease> allReleases = new ArrayList<>(); 677 List<Class<? extends AndroidRelease>> classesWithIllegalNames = new ArrayList<>(); 678 for (Class<?> clazz : AndroidVersions.class.getClasses()) { 679 if (AndroidRelease.class.isAssignableFrom(clazz) 680 && !clazz.isInterface() 681 && !Modifier.isAbstract(clazz.getModifiers())) { 682 try { 683 AndroidRelease rel = (AndroidRelease) clazz.getDeclaredConstructor().newInstance(); 684 allReleases.add(rel); 685 // inspect field name - as this is our only chance to inspect it. 686 if (!rel.getClass().getSimpleName().equals(rel.getShortCode())) { 687 classesWithIllegalNames.add(rel.getClass()); 688 } 689 } catch (NoSuchMethodException 690 | InstantiationException 691 | IllegalArgumentException 692 | IllegalAccessException 693 | InvocationTargetException ex) { 694 throw new RuntimeException( 695 "Classes " 696 + clazz.getName() 697 + "should be accessible via " 698 + AndroidVersions.class.getCanonicalName() 699 + " and have a default public no-op constructor ", 700 ex); 701 } 702 } 703 } 704 Collections.sort(allReleases, AndroidRelease::compareTo); 705 706 SdkInformation sdkInformation = new SdkInformation(allReleases, classesWithIllegalNames); 707 sdkInformation.throwStaticErrors(); 708 return sdkInformation; 709 } 710 computeReleaseVersion(JarFile jarFile)711 static AndroidRelease computeReleaseVersion(JarFile jarFile) throws IOException { 712 ZipEntry buildProp = jarFile.getEntry("build.prop"); 713 Properties buildProps = new Properties(); 714 buildProps.load(jarFile.getInputStream(buildProp)); 715 return computeCurrentSdkFromBuildProps(buildProps); 716 } 717 computeCurrentSdkFromBuildProps(Properties buildProps)718 static AndroidRelease computeCurrentSdkFromBuildProps(Properties buildProps) { 719 // 33, 34, 35 .... 720 String sdkVersionString = buildProps.getProperty("ro.build.version.sdk"); 721 int sdk = sdkVersionString == null ? 0 : Integer.parseInt(sdkVersionString); 722 // "REL" 723 String release = buildProps.getProperty("ro.build.version.release"); 724 // "Tiramasu", "UpsideDownCake" 725 String codename = buildProps.getProperty("ro.build.version.codename"); 726 // "Tiramasu,UpsideDownCake", "UpsideDownCake", "REL" 727 String codenames = buildProps.getProperty("ro.build.version.all_codenames"); 728 String[] allCodeNames = codenames == null ? new String[0] : codenames.split(","); 729 String[] activeCodeNames = 730 allCodeNames.length > 0 && allCodeNames[0].equals("REL") ? new String[0] : allCodeNames; 731 return information.computeCurrentSdk(sdk, release, codename, asList(activeCodeNames)); 732 } 733 734 /** 735 * If we are working in android source, this code detects the list of active code names if any. 736 */ getActiveCodeNamesIfAny(Class<?> targetClass)737 private static List<String> getActiveCodeNamesIfAny(Class<?> targetClass) { 738 try { 739 Field activeCodeFields = targetClass.getDeclaredField("ACTIVE_CODENAMES"); 740 String[] activeCodeNames = (String[]) activeCodeFields.get(null); 741 if (activeCodeNames == null) { 742 return new ArrayList<>(); 743 } 744 return asList(activeCodeNames); 745 } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException ex) { 746 return new ArrayList<>(); 747 } 748 } 749 750 private static final SdkInformation information; 751 752 static { 753 AndroidRelease currentRelease = null; 754 information = gatherStaticSdkInformationFromThisClass(); 755 try { 756 Class<?> buildClass = 757 Class.forName("android.os.Build", false, Thread.currentThread().getContextClassLoader()); 758 System.out.println("build class " + buildClass); 759 Class<?> versionClass = null; 760 for (Class<?> c : buildClass.getClasses()) { 761 if (c.getSimpleName().equals("VERSION")) { 762 versionClass = c; 763 System.out.println("Version class " + versionClass); 764 break; 765 } 766 } 767 if (versionClass != null) { 768 // 33, 34, etc.... 769 int sdkInt = (int) ReflectionHelpers.getStaticField(versionClass, "SDK_INT"); 770 // Either unset, or 13, 14, etc.... 771 String release = ReflectionHelpers.getStaticField(versionClass, "RELEASE"); 772 // Either REL if release is set, or Tiramasu, UpsideDownCake, etc 773 String codename = ReflectionHelpers.getStaticField(versionClass, "CODENAME"); 774 List<String> activeCodeNames = getActiveCodeNamesIfAny(versionClass); 775 currentRelease = information.computeCurrentSdk(sdkInt, release, codename, activeCodeNames); 776 } 777 } catch (ClassNotFoundException | IllegalArgumentException | UnsatisfiedLinkError e) { 778 // No op, this class should be usable outside of a Robolectric sandbox. 779 } 780 CURRENT = currentRelease; 781 } 782 } 783