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