• 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.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