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