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.io.InputStream; 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.Objects; 33 import java.util.Properties; 34 import java.util.jar.JarFile; 35 import java.util.stream.Collectors; 36 import java.util.zip.ZipEntry; 37 import javax.annotation.Nullable; 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 57 private static boolean warnOnly; 58 AndroidVersions()59 private AndroidVersions() {} 60 61 /** Representation of an android release, one that has occurred, or is expected. */ 62 public abstract static class AndroidRelease implements Comparable<AndroidRelease> { 63 64 /** 65 * true if this release has already occurred, false otherwise. If unreleased, the getSdkInt may 66 * still be that of the prior release. 67 */ getSdkInt()68 public abstract int getSdkInt(); 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 abstract String getShortCode(); 75 76 /** 77 * true if this release has already occurred, false otherwise. If unreleased, the getSdkInt will 78 * guess at the likely sdk number. Your code will need to recompile if this value changes - 79 * including most modern build tools; bazle, soong all are full build systems - and as such 80 * organizations using them have no concerns. 81 */ isReleased()82 public abstract boolean isReleased(); 83 84 /** major.minor version number as String. */ getVersion()85 public abstract String getVersion(); 86 87 /** 88 * Implements comparable. 89 * 90 * @param other the object to be compared. 91 * @return 1 if this is greater than other, 0 if equal, -1 if less 92 * @throws IllegalStateException if other is not an instance of AndroidRelease. 93 */ 94 @Override compareTo(AndroidRelease other)95 public int compareTo(AndroidRelease other) { 96 if (other == null) { 97 throw new IllegalStateException( 98 "Only " 99 + AndroidVersions.class.getName() 100 + " should define Releases, illegal class " 101 + other.getClass()); 102 } 103 return Integer.compare(this.getSdkInt(), other.getSdkInt()); 104 } 105 106 @Override toString()107 public String toString() { 108 return "Android " 109 + (this.isReleased() ? "" : "Future ") 110 + "Release: " 111 + this.getVersion() 112 + " ( sdk: " 113 + this.getSdkInt() 114 + " code: " 115 + this.getShortCode() 116 + " )"; 117 } 118 } 119 120 /** A released version of Android */ 121 public abstract static class AndroidReleased extends AndroidRelease { 122 @Override isReleased()123 public boolean isReleased() { 124 return true; 125 } 126 } 127 128 /** An in-development version of Android */ 129 public abstract static class AndroidUnreleased extends AndroidRelease { 130 @Override isReleased()131 public boolean isReleased() { 132 return false; 133 } 134 } 135 136 /** 137 * Version: -1 <br> 138 * ShortCode: "" <br> 139 * SDK API Level: "" <br> 140 * release: false <br> 141 */ 142 public static final class Unbound extends AndroidUnreleased { 143 144 public static final int SDK_INT = -1; 145 146 public static final String SHORT_CODE = "_"; 147 148 public static final String VERSION = "_"; 149 150 @Override getSdkInt()151 public int getSdkInt() { 152 return SDK_INT; 153 } 154 155 @Override getShortCode()156 public String getShortCode() { 157 return SHORT_CODE; 158 } 159 160 @Override getVersion()161 public String getVersion() { 162 return VERSION; 163 } 164 } 165 166 /** 167 * Version: 4.1 <br> 168 * ShortCode: J <br> 169 * SDK API Level: 16 <br> 170 * release: true <br> 171 */ 172 public static final class J extends AndroidReleased { 173 174 public static final int SDK_INT = 16; 175 176 public static final String SHORT_CODE = "J"; 177 178 public static final String VERSION = "4.1"; 179 180 @Override getSdkInt()181 public int getSdkInt() { 182 return SDK_INT; 183 } 184 185 @Override getShortCode()186 public String getShortCode() { 187 return SHORT_CODE; 188 } 189 190 @Override getVersion()191 public String getVersion() { 192 return VERSION; 193 } 194 } 195 196 /** 197 * Version: 4.2 <br> 198 * ShortCode: JMR1 <br> 199 * SDK API Level: 17 <br> 200 * release: true <br> 201 */ 202 public static final class JMR1 extends AndroidReleased { 203 204 public static final int SDK_INT = 17; 205 206 public static final String SHORT_CODE = "JMR1"; 207 208 public static final String VERSION = "4.2"; 209 210 @Override getSdkInt()211 public int getSdkInt() { 212 return SDK_INT; 213 } 214 215 @Override getShortCode()216 public String getShortCode() { 217 return SHORT_CODE; 218 } 219 220 @Override getVersion()221 public String getVersion() { 222 return VERSION; 223 } 224 } 225 226 /** 227 * Version: 4.3 <br> 228 * ShortCode: JMR2 <br> 229 * SDK API Level: 18 <br> 230 * release: true <br> 231 */ 232 public static final class JMR2 extends AndroidReleased { 233 234 public static final int SDK_INT = 18; 235 236 public static final String SHORT_CODE = "JMR2"; 237 238 public static final String VERSION = "4.3"; 239 240 @Override getSdkInt()241 public int getSdkInt() { 242 return SDK_INT; 243 } 244 245 @Override getShortCode()246 public String getShortCode() { 247 return SHORT_CODE; 248 } 249 250 @Override getVersion()251 public String getVersion() { 252 return VERSION; 253 } 254 } 255 256 /** 257 * Version: 4.4 <br> 258 * ShortCode: K <br> 259 * SDK API Level: 19 <br> 260 * release: true <br> 261 */ 262 public static final class K extends AndroidReleased { 263 264 public static final int SDK_INT = 19; 265 266 public static final String SHORT_CODE = "K"; 267 268 public static final String VERSION = "4.4"; 269 270 @Override getSdkInt()271 public int getSdkInt() { 272 return SDK_INT; 273 } 274 275 @Override getShortCode()276 public String getShortCode() { 277 return SHORT_CODE; 278 } 279 280 @Override getVersion()281 public String getVersion() { 282 return VERSION; 283 } 284 } 285 286 // Skipping K Watch release, which was 20. 287 288 /** 289 * Version: 5.0 <br> 290 * ShortCode: L <br> 291 * SDK API Level: 21 <br> 292 * release: true <br> 293 */ 294 public static final class L extends AndroidReleased { 295 296 public static final int SDK_INT = 21; 297 298 public static final String SHORT_CODE = "L"; 299 300 public static final String VERSION = "5.0"; 301 302 @Override getSdkInt()303 public int getSdkInt() { 304 return SDK_INT; 305 } 306 307 @Override getShortCode()308 public String getShortCode() { 309 return SHORT_CODE; 310 } 311 312 @Override getVersion()313 public String getVersion() { 314 return VERSION; 315 } 316 } 317 318 /** 319 * Version: 5.1 <br> 320 * ShortCode: LMR1 <br> 321 * SDK API Level: 22 <br> 322 * release: true <br> 323 */ 324 public static final class LMR1 extends AndroidReleased { 325 326 public static final int SDK_INT = 22; 327 328 public static final String SHORT_CODE = "LMR1"; 329 330 public static final String VERSION = "5.1"; 331 332 @Override getSdkInt()333 public int getSdkInt() { 334 return SDK_INT; 335 } 336 337 @Override getShortCode()338 public String getShortCode() { 339 return SHORT_CODE; 340 } 341 342 @Override getVersion()343 public String getVersion() { 344 return VERSION; 345 } 346 } 347 348 /** 349 * Version: 6.0 <br> 350 * ShortCode: M <br> 351 * SDK API Level: 23 <br> 352 * release: true <br> 353 */ 354 public static final class M extends AndroidReleased { 355 356 public static final int SDK_INT = 23; 357 358 public static final String SHORT_CODE = "M"; 359 360 public static final String VERSION = "6.0"; 361 362 @Override getSdkInt()363 public int getSdkInt() { 364 return SDK_INT; 365 } 366 367 @Override getShortCode()368 public String getShortCode() { 369 return SHORT_CODE; 370 } 371 372 @Override getVersion()373 public String getVersion() { 374 return VERSION; 375 } 376 } 377 378 /** 379 * Version: 7.0 <br> 380 * ShortCode: N <br> 381 * SDK API Level: 24 <br> 382 * release: true <br> 383 */ 384 public static final class N extends AndroidReleased { 385 386 public static final int SDK_INT = 24; 387 388 public static final String SHORT_CODE = "N"; 389 390 public static final String VERSION = "7.0"; 391 392 @Override getSdkInt()393 public int getSdkInt() { 394 return SDK_INT; 395 } 396 397 @Override getShortCode()398 public String getShortCode() { 399 return SHORT_CODE; 400 } 401 402 @Override getVersion()403 public String getVersion() { 404 return VERSION; 405 } 406 } 407 408 /** 409 * Release: 7.1 <br> 410 * ShortCode: NMR1 <br> 411 * SDK Framework: 25 <br> 412 * release: true <br> 413 */ 414 public static final class NMR1 extends AndroidReleased { 415 416 public static final int SDK_INT = 25; 417 418 public static final String SHORT_CODE = "NMR1"; 419 420 public static final String VERSION = "7.1"; 421 422 @Override getSdkInt()423 public int getSdkInt() { 424 return SDK_INT; 425 } 426 427 @Override getShortCode()428 public String getShortCode() { 429 return SHORT_CODE; 430 } 431 432 @Override getVersion()433 public String getVersion() { 434 return VERSION; 435 } 436 } 437 438 /** 439 * Release: 8.0 <br> 440 * ShortCode: O <br> 441 * SDK API Level: 26 <br> 442 * release: true <br> 443 */ 444 public static final class O extends AndroidReleased { 445 446 public static final int SDK_INT = 26; 447 448 public static final String SHORT_CODE = "O"; 449 450 public static final String VERSION = "8.0"; 451 452 @Override getSdkInt()453 public int getSdkInt() { 454 return SDK_INT; 455 } 456 457 @Override getShortCode()458 public String getShortCode() { 459 return SHORT_CODE; 460 } 461 462 @Override getVersion()463 public String getVersion() { 464 return VERSION; 465 } 466 } 467 468 /** 469 * Release: 8.1 <br> 470 * ShortCode: OMR1 <br> 471 * SDK API Level: 27 <br> 472 * release: true <br> 473 */ 474 public static final class OMR1 extends AndroidReleased { 475 476 public static final int SDK_INT = 27; 477 478 public static final String SHORT_CODE = "OMR1"; 479 480 public static final String VERSION = "8.1"; 481 482 @Override getSdkInt()483 public int getSdkInt() { 484 return SDK_INT; 485 } 486 487 @Override getShortCode()488 public String getShortCode() { 489 return SHORT_CODE; 490 } 491 492 @Override getVersion()493 public String getVersion() { 494 return VERSION; 495 } 496 } 497 498 /** 499 * Release: 9.0 <br> 500 * ShortCode: P <br> 501 * SDK API Level: 28 <br> 502 * release: true <br> 503 */ 504 public static final class P extends AndroidReleased { 505 506 public static final int SDK_INT = 28; 507 508 public static final String SHORT_CODE = "P"; 509 510 public static final String VERSION = "9.0"; 511 512 @Override getSdkInt()513 public int getSdkInt() { 514 return SDK_INT; 515 } 516 517 @Override getShortCode()518 public String getShortCode() { 519 return SHORT_CODE; 520 } 521 522 @Override getVersion()523 public String getVersion() { 524 return VERSION; 525 } 526 } 527 528 /** 529 * Release: 10.0 <br> 530 * ShortCode: Q <br> 531 * SDK API Level: 29 <br> 532 * release: true <br> 533 */ 534 public static final class Q extends AndroidReleased { 535 536 public static final int SDK_INT = 29; 537 538 public static final String SHORT_CODE = "Q"; 539 540 public static final String VERSION = "10.0"; 541 542 @Override getSdkInt()543 public int getSdkInt() { 544 return SDK_INT; 545 } 546 547 @Override getShortCode()548 public String getShortCode() { 549 return SHORT_CODE; 550 } 551 552 @Override getVersion()553 public String getVersion() { 554 return VERSION; 555 } 556 } 557 558 /** 559 * Release: 11.0 <br> 560 * ShortCode: R <br> 561 * SDK API Level: 30 <br> 562 * release: true <br> 563 */ 564 public static final class R extends AndroidReleased { 565 566 public static final int SDK_INT = 30; 567 568 public static final String SHORT_CODE = "R"; 569 570 public static final String VERSION = "11.0"; 571 572 @Override getSdkInt()573 public int getSdkInt() { 574 return SDK_INT; 575 } 576 577 @Override getShortCode()578 public String getShortCode() { 579 return SHORT_CODE; 580 } 581 582 @Override getVersion()583 public String getVersion() { 584 return VERSION; 585 } 586 } 587 588 /** 589 * Release: 12.0 <br> 590 * ShortCode: S <br> 591 * SDK API Level: 31 <br> 592 * release: true <br> 593 */ 594 public static final class S extends AndroidReleased { 595 596 public static final int SDK_INT = 31; 597 598 public static final String SHORT_CODE = "S"; 599 600 public static final String VERSION = "12.0"; 601 602 @Override getSdkInt()603 public int getSdkInt() { 604 return SDK_INT; 605 } 606 607 @Override getShortCode()608 public String getShortCode() { 609 return SHORT_CODE; 610 } 611 612 @Override getVersion()613 public String getVersion() { 614 return VERSION; 615 } 616 } 617 618 /** 619 * Release: 12.1 <br> 620 * ShortCode: Sv2 <br> 621 * SDK API Level: 32 <br> 622 * release: true <br> 623 */ 624 @SuppressWarnings("UPPER_SNAKE_CASE") 625 public static final class Sv2 extends AndroidReleased { 626 627 public static final int SDK_INT = 32; 628 629 public static final String SHORT_CODE = "Sv2"; 630 631 public static final String VERSION = "12.1"; 632 633 @Override getSdkInt()634 public int getSdkInt() { 635 return SDK_INT; 636 } 637 638 @Override getShortCode()639 public String getShortCode() { 640 return SHORT_CODE; 641 } 642 643 @Override getVersion()644 public String getVersion() { 645 return VERSION; 646 } 647 } 648 649 /** 650 * Release: 13.0 <br> 651 * ShortCode: T <br> 652 * SDK API Level: 33 <br> 653 * release: true <br> 654 */ 655 public static final class T extends AndroidReleased { 656 657 public static final int SDK_INT = 33; 658 659 public static final String SHORT_CODE = "T"; 660 661 public static final String VERSION = "13.0"; 662 663 @Override getSdkInt()664 public int getSdkInt() { 665 return SDK_INT; 666 } 667 668 @Override getShortCode()669 public String getShortCode() { 670 return SHORT_CODE; 671 } 672 673 @Override getVersion()674 public String getVersion() { 675 return VERSION; 676 } 677 } 678 679 /** 680 * Potential Release: 14.0 <br> 681 * ShortCode: U <br> 682 * SDK API Level: 34 <br> 683 * release: false <br> 684 */ 685 public static final class U extends AndroidReleased { 686 687 public static final int SDK_INT = 34; 688 689 public static final String SHORT_CODE = "U"; 690 691 public static final String VERSION = "14.0"; 692 693 @Override getSdkInt()694 public int getSdkInt() { 695 return SDK_INT; 696 } 697 698 @Override getShortCode()699 public String getShortCode() { 700 return SHORT_CODE; 701 } 702 703 @Override getVersion()704 public String getVersion() { 705 return VERSION; 706 } 707 } 708 709 /** 710 * Potential Release: 15.0 <br> 711 * ShortCode: V <br> 712 * SDK API Level: 34+ <br> 713 * release: false <br> 714 */ 715 public static final class V extends AndroidReleased { 716 717 public static final int SDK_INT = 35; 718 719 public static final String SHORT_CODE = "V"; 720 721 public static final String VERSION = "15"; 722 723 @Override getSdkInt()724 public int getSdkInt() { 725 return SDK_INT; 726 } 727 728 @Override getShortCode()729 public String getShortCode() { 730 return SHORT_CODE; 731 } 732 733 @Override getVersion()734 public String getVersion() { 735 return VERSION; 736 } 737 } 738 739 /** 740 * Baklava is an InDevelopment SDK after V, the name scheme has wrapped the alphabet. 741 * 742 * <p>All values here subject to change. 743 */ 744 public static final class Baklava extends AndroidUnreleased { 745 746 public static final int SDK_INT = 36; 747 748 public static final String SHORT_CODE = "Baklava"; 749 750 public static final String VERSION = "16"; 751 752 @Override getSdkInt()753 public int getSdkInt() { 754 return SDK_INT; 755 } 756 757 @Override getShortCode()758 public String getShortCode() { 759 return SHORT_CODE; 760 } 761 762 @Override getVersion()763 public String getVersion() { 764 return VERSION; 765 } 766 } 767 768 /** The current release this process is running on. */ 769 public static final AndroidRelease CURRENT; 770 771 @Nullable getReleaseForSdkInt(@ullable Integer sdkInt)772 public static AndroidRelease getReleaseForSdkInt(@Nullable Integer sdkInt) { 773 if (sdkInt == null) { 774 return null; 775 } else { 776 return information.sdkIntToAllReleases.get(sdkInt); 777 } 778 } 779 getReleases()780 public static List<AndroidRelease> getReleases() { 781 return information.released; 782 } 783 getUnreleased()784 public static List<AndroidRelease> getUnreleased() { 785 return information.unreleased; 786 } 787 788 /** 789 * Responsible for aggregating and interpreting the static state representing the current 790 * AndroidReleases known to AndroidVersions class. 791 */ 792 static class SdkInformation { 793 final List<AndroidRelease> allReleases; 794 final List<Class<? extends AndroidRelease>> classesWithIllegalNames; 795 final AndroidRelease latestRelease; 796 final AndroidRelease earliestUnreleased; 797 final List<AndroidRelease> unreleased; 798 final List<AndroidRelease> released; 799 800 // In the future we may need a multimap for sdkInts should they stay static across releases. 801 final Map<Integer, AndroidRelease> sdkIntToAllReleases = new HashMap<>(); 802 final Map<String, AndroidRelease> shortCodeToAllReleases = new HashMap<>(); 803 804 // detected errors 805 final List<Map.Entry<AndroidRelease, AndroidRelease>> sdkIntCollisions = new ArrayList<>(); 806 Map.Entry<AndroidRelease, AndroidRelease> sdkApiMisordered = null; 807 SdkInformation( List<AndroidRelease> releases, List<Class<? extends AndroidRelease>> classesWithIllegalNames)808 public SdkInformation( 809 List<AndroidRelease> releases, 810 List<Class<? extends AndroidRelease>> classesWithIllegalNames) { 811 this.allReleases = releases; 812 this.classesWithIllegalNames = classesWithIllegalNames; 813 AndroidRelease latestRelease = null; 814 AndroidRelease earliestUnreleased = null; 815 for (AndroidRelease release : allReleases) { 816 if (release.isReleased()) { 817 if (latestRelease == null || release.compareTo(latestRelease) > 0) { 818 latestRelease = release; 819 } 820 } else { 821 if (earliestUnreleased == null || release.compareTo(earliestUnreleased) < 0) { 822 earliestUnreleased = release; 823 } 824 } 825 } 826 this.latestRelease = latestRelease; 827 this.earliestUnreleased = earliestUnreleased; 828 this.unreleased = 829 allReleases.stream() 830 .filter(r -> !r.isReleased()) 831 .sorted() 832 .collect(Collectors.toUnmodifiableList()); 833 this.released = 834 allReleases.stream() 835 .filter(AndroidRelease::isReleased) 836 .sorted() 837 .collect(Collectors.toUnmodifiableList()); 838 verifyStaticInformation(); 839 } 840 verifyStaticInformation()841 private void verifyStaticInformation() { 842 for (AndroidRelease release : this.allReleases) { 843 // Construct a map of all sdkInts to releases and note duplicates 844 AndroidRelease sdkCollision = this.sdkIntToAllReleases.put(release.getSdkInt(), release); 845 if (sdkCollision != null) { 846 this.sdkIntCollisions.add(new AbstractMap.SimpleEntry<>(release, sdkCollision)); 847 } 848 // Construct a map of all short codes to releases, and note duplicates 849 this.shortCodeToAllReleases.put(release.getShortCode(), release); 850 // There is no need to check for shortCode duplicates as the Field name must match the 851 // short code. 852 } 853 if (earliestUnreleased != null 854 && latestRelease != null 855 && latestRelease.getSdkInt() >= earliestUnreleased.getSdkInt()) { 856 sdkApiMisordered = new AbstractMap.SimpleEntry<>(latestRelease, earliestUnreleased); 857 } 858 } 859 handleStaticErrors()860 private void handleStaticErrors() { 861 StringBuilder errors = new StringBuilder(); 862 if (!this.classesWithIllegalNames.isEmpty()) { 863 errors 864 .append("The following classes do not follow the naming criteria for ") 865 .append("releases or do not have the short codes in ") 866 .append("their internal fields. Please correct them: ") 867 .append(this.classesWithIllegalNames) 868 .append("\n"); 869 } 870 if (sdkApiMisordered != null) { 871 errors 872 .append("The latest released sdk ") 873 .append(sdkApiMisordered.getKey().getShortCode()) 874 .append(" has a sdkInt greater than the earliest unreleased sdk ") 875 .append(sdkApiMisordered.getValue().getShortCode()) 876 .append("this implies sdks were released out of order which is highly unlikely.\n"); 877 } 878 if (!sdkIntCollisions.isEmpty()) { 879 errors.append( 880 "The following sdks have different shortCodes, but identical sdkInt " + "versions:\n"); 881 for (Map.Entry<AndroidRelease, AndroidRelease> entry : sdkIntCollisions) { 882 errors 883 .append("Both ") 884 .append(entry.getKey().getShortCode()) 885 .append(" and ") 886 .append(entry.getValue().getShortCode()) 887 .append("have the same sdkInt value of ") 888 .append(entry.getKey().getSdkInt()) 889 .append("\n"); 890 } 891 } 892 if (errors.length() > 0) { 893 errorMessage( 894 errors 895 .append("Please check the AndroidReleases defined ") 896 .append("in ") 897 .append(AndroidVersions.class.getName()) 898 .append("and ensure they are aligned with the versions of") 899 .append(" Android.") 900 .toString(), 901 null); 902 } 903 } 904 computeCurrentSdk( int reportedVersion, String releaseName, String codename, List<String> activeCodeNames)905 public AndroidRelease computeCurrentSdk( 906 int reportedVersion, String releaseName, String codename, List<String> activeCodeNames) { 907 AndroidRelease current = null; 908 // Special case "REL", which means the build is not a pre-release build. 909 if (Objects.equals(codename, "REL")) { 910 // the first letter of the code name equal to the release number. 911 current = sdkIntToAllReleases.get(reportedVersion); 912 if (current != null && !current.isReleased()) { 913 errorMessage( 914 "The current sdk " 915 + current.getShortCode() 916 + " has been released. Please update the contents of " 917 + AndroidVersions.class.getName() 918 + " to mark sdk " 919 + current.getShortCode() 920 + " as released.", 921 null); 922 } 923 } else { 924 // Get known active code name letters 925 926 List<String> activeCodenameLetter = new ArrayList<>(); 927 for (String name : activeCodeNames) { 928 activeCodenameLetter.add(name.toUpperCase(Locale.getDefault()).substring(0, 1)); 929 } 930 931 // If the process is operating with a code name. 932 if (codename != null) { 933 StringBuilder detectedProblems = new StringBuilder(); 934 // This is safe for minor releases ( X.1 ) as long as they have added an entry 935 // corresponding to the sdk of that release and the prior major release is marked as 936 // "released" on its entry in this file. If not this class will fail to initialize. 937 // The assumption is that only one of the major or minor version of a code name 938 // is under development and unreleased at any give time (S or Sv2). 939 String foundCode = codename.toUpperCase(Locale.getDefault()).substring(0, 1); 940 int loc = activeCodenameLetter.indexOf(foundCode); 941 if (loc == -1) { 942 detectedProblems 943 .append("The current codename's (") 944 .append(codename) 945 .append(") first letter (") 946 .append(foundCode) 947 .append(") is not in the list of active code's first letters: ") 948 .append(activeCodenameLetter) 949 .append("\n"); 950 } else { 951 // attempt to find assume the fullname is the "shortCode", aka "Sv2", "OMR1". 952 current = shortCodeToAllReleases.get(codename); 953 // else, assume the fullname is the first letter is correct. 954 if (current == null) { 955 current = shortCodeToAllReleases.get(String.valueOf(foundCode)); 956 } 957 } 958 if (current == null) { 959 detectedProblems 960 .append("No known release is associated with the shortCode of \"") 961 .append(foundCode) 962 .append("\" or \"") 963 .append(codename) 964 .append("\"\n"); 965 } else if (current.isReleased()) { 966 StringBuilder problem = new StringBuilder(); 967 problem 968 .append("The current sdk ") 969 .append(current.getShortCode()) 970 .append(" has been been marked as released. Please update the ") 971 .append("contents of current sdk jar to the released version.\n"); 972 if (current.getSdkInt() < latestRelease.getSdkInt()) { 973 // If the current sdk is lower than the latest release it should never be reported as 974 // unreleased. 975 detectedProblems.append(problem); 976 } else { 977 // If the current sdk is the latest release and it is reporting itself as unreleased 978 // we simply log as this will occur when android build devs have not yet updated the 979 // branch's build definitions. (git/main and aosp/main still claim to be the 980 // unreleased version of the latest release) 981 System.err.println(problem); 982 } 983 } 984 if (detectedProblems.length() > 0) { 985 errorMessage(detectedProblems.toString(), null); 986 } 987 988 if (current == null) { // only possible in warning mode 989 current = 990 new AndroidUnreleased() { 991 @Override 992 public int getSdkInt() { 993 return 10000; // the super large unknown sdk value. 994 } 995 996 @Override 997 public String getShortCode() { 998 return codename.toUpperCase(Locale.getDefault()).substring(0, 1); 999 } 1000 1001 @Override 1002 public String getVersion() { 1003 return ""; 1004 } 1005 }; 1006 } 1007 } 1008 } 1009 1010 return current; 1011 } 1012 } 1013 1014 /** 1015 * Reads all AndroidReleases in this class and populates SdkInformation, checking for sanity in 1016 * the shortCode, sdkInt, and release information. 1017 * 1018 * <p>All errors are stored and can be reported at once by asking the SdkInformation to throw a 1019 * IllegalStateException after it has been populated. 1020 */ gatherStaticSdkInformationFromThisClass()1021 static SdkInformation gatherStaticSdkInformationFromThisClass() { 1022 List<AndroidRelease> allReleases = new ArrayList<>(); 1023 List<Class<? extends AndroidRelease>> classesWithIllegalNames = new ArrayList<>(); 1024 for (Class<?> clazz : AndroidVersions.class.getClasses()) { 1025 if (AndroidRelease.class.isAssignableFrom(clazz) 1026 && !clazz.isInterface() 1027 && !Modifier.isAbstract(clazz.getModifiers()) 1028 && clazz != Unbound.class) { 1029 try { 1030 AndroidRelease rel = (AndroidRelease) clazz.getDeclaredConstructor().newInstance(); 1031 allReleases.add(rel); 1032 // inspect field name - as this is our only chance to inspect it. 1033 if (!rel.getClass().getSimpleName().equals(rel.getShortCode())) { 1034 classesWithIllegalNames.add(rel.getClass()); 1035 } 1036 } catch (NoSuchMethodException 1037 | InstantiationException 1038 | IllegalArgumentException 1039 | IllegalAccessException 1040 | InvocationTargetException ex) { 1041 errorMessage( 1042 "Classes " 1043 + clazz.getName() 1044 + "should be accessible via " 1045 + AndroidVersions.class.getCanonicalName() 1046 + " and have a default public no-op constructor ", 1047 ex); 1048 } 1049 } 1050 } 1051 Collections.sort(allReleases, AndroidRelease::compareTo); 1052 1053 SdkInformation sdkInformation = new SdkInformation(allReleases, classesWithIllegalNames); 1054 sdkInformation.handleStaticErrors(); 1055 return sdkInformation; 1056 } 1057 computeReleaseVersion(JarFile jarFile)1058 static AndroidRelease computeReleaseVersion(JarFile jarFile) throws IOException { 1059 ZipEntry buildProp = jarFile.getEntry("build.prop"); 1060 Properties buildProps = new Properties(); 1061 buildProps.load(jarFile.getInputStream(buildProp)); 1062 return computeCurrentSdkFromBuildProps(buildProps); 1063 } 1064 computeCurrentSdkFromBuildProps(Properties buildProps)1065 static AndroidRelease computeCurrentSdkFromBuildProps(Properties buildProps) { 1066 // 33, 34, 35 .... 1067 String sdkVersionString = buildProps.getProperty("ro.build.version.sdk"); 1068 int sdk = sdkVersionString == null ? 0 : Integer.parseInt(sdkVersionString); 1069 // "REL" 1070 String release = buildProps.getProperty("ro.build.version.release"); 1071 // "Tiramasu", "UpsideDownCake" 1072 String codename = buildProps.getProperty("ro.build.version.codename"); 1073 // "Tiramasu,UpsideDownCake", "UpsideDownCake", "REL" 1074 String codenames = buildProps.getProperty("ro.build.version.all_codenames"); 1075 String[] allCodeNames = codenames == null ? new String[0] : codenames.split(","); 1076 String[] activeCodeNames = 1077 allCodeNames.length > 0 && allCodeNames[0].equals("REL") ? new String[0] : allCodeNames; 1078 return information.computeCurrentSdk(sdk, release, codename, asList(activeCodeNames)); 1079 } 1080 1081 private static final SdkInformation information; 1082 errorMessage(String errorMessage, @Nullable Exception ex)1083 private static final void errorMessage(String errorMessage, @Nullable Exception ex) { 1084 if (warnOnly) { 1085 System.err.println(errorMessage); 1086 } else { 1087 throw new IllegalStateException(errorMessage, ex); 1088 } 1089 } 1090 1091 static { 1092 // We shouldn't break in annotation processors, only test runs. 1093 String cmd = System.getProperty("sun.java.command"); 1094 // We appear to be in an annotation processor, so only warn users. 1095 if (cmd.contains("-Aorg.robolectric.annotation.processing.")) { 1096 System.err.println( 1097 "Robolectric's AndroidVersions is running in warning mode," 1098 + " no errors will be thrown."); 1099 warnOnly = true; 1100 } else { 1101 warnOnly = false; 1102 } 1103 AndroidRelease currentRelease = null; 1104 information = gatherStaticSdkInformationFromThisClass(); 1105 try { 1106 InputStream is = AndroidVersions.class.getClassLoader().getResourceAsStream("build.prop"); 1107 if (is != null) { 1108 Properties buildProps = new Properties(); 1109 buildProps.load(is); 1110 currentRelease = computeCurrentSdkFromBuildProps(buildProps); 1111 } 1112 } catch (IOException ioe) { 1113 // No op, this class should be usable outside of a Robolectric sandbox. 1114 } 1115 CURRENT = currentRelease; 1116 } 1117 } 1118