• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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