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