• 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.lang.reflect.Field;
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.Properties;
33 import java.util.jar.JarFile;
34 import java.util.zip.ZipEntry;
35 import javax.annotation.Nullable;
36 import org.robolectric.util.Logger;
37 import org.robolectric.util.ReflectionHelpers;
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 
AndroidVersions()57   private AndroidVersions() {}
58 
59   /** Representation of an android release, one that has occurred, or is expected. */
60   public abstract static class AndroidRelease implements Comparable<AndroidRelease> {
61 
62     /**
63      * true if this release has already occurred, false otherwise. If unreleased, the getSdkInt may
64      * still be that of the prior release.
65      */
getSdkInt()66     public int getSdkInt() {
67       return ReflectionHelpers.getStaticField(this.getClass(), "SDK_INT");
68     }
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 String getShortCode() {
75       return ReflectionHelpers.getStaticField(this.getClass(), "SHORT_CODE");
76     }
77 
78     /**
79      * true if this release has already occurred, false otherwise. If unreleased, the getSdkInt will
80      * guess at the likely sdk number. Your code will need to recompile if this value changes -
81      * including most modern build tools; bazle, soong all are full build systems - and as such
82      * organizations using them have no concerns.
83      */
isReleased()84     public boolean isReleased() {
85       return ReflectionHelpers.getStaticField(this.getClass(), "RELEASED");
86     }
87 
88     /** major.minor version number as String. */
getVersion()89     public String getVersion() {
90       return ReflectionHelpers.getStaticField(this.getClass(), "VERSION");
91     }
92 
93     /**
94      * Implements comparable.
95      *
96      * @param other the object to be compared.
97      * @return 1 if this is greater than other, 0 if equal, -1 if less
98      * @throws RuntimeException if other is not an instance of AndroidRelease.
99      */
100     @Override
compareTo(AndroidRelease other)101     public int compareTo(AndroidRelease other) {
102       if (other == null) {
103         throw new RuntimeException(
104             "Only "
105                 + AndroidVersions.class.getName()
106                 + " should define Releases, illegal class "
107                 + other.getClass());
108       }
109       return Integer.compare(this.getSdkInt(), other.getSdkInt());
110     }
111 
112     @Override
toString()113     public String toString() {
114       return "Android "
115           + (this.isReleased() ? "" : "Future ")
116           + "Release: "
117           + this.getVersion()
118           + " ( sdk: "
119           + this.getSdkInt()
120           + " code: "
121           + this.getShortCode()
122           + " )";
123     }
124   }
125 
126   /**
127    * Version: 4.1 <br>
128    * ShortCode: J <br>
129    * SDK API Level: 16 <br>
130    * release: true <br>
131    */
132   public static final class J extends AndroidRelease {
133 
134     public static final int SDK_INT = 16;
135 
136     public static final boolean RELEASED = true;
137 
138     public static final String SHORT_CODE = "J";
139 
140     public static final String VERSION = "4.1";
141   }
142 
143   /**
144    * Version: 4.2 <br>
145    * ShortCode: JMR1 <br>
146    * SDK API Level: 17 <br>
147    * release: true <br>
148    */
149   public static final class JMR1 extends AndroidRelease {
150 
151     public static final int SDK_INT = 17;
152 
153     public static final boolean RELEASED = true;
154 
155     public static final String SHORT_CODE = "JMR1";
156 
157     public static final String VERSION = "4.2";
158   }
159 
160   /**
161    * Version: 4.3 <br>
162    * ShortCode: JMR2 <br>
163    * SDK API Level: 18 <br>
164    * release: true <br>
165    */
166   public static final class JMR2 extends AndroidRelease {
167 
168     public static final int SDK_INT = 18;
169 
170     public static final boolean RELEASED = true;
171 
172     public static final String SHORT_CODE = "JMR2";
173 
174     public static final String VERSION = "4.3";
175   }
176 
177   /**
178    * Version: 4.4 <br>
179    * ShortCode: K <br>
180    * SDK API Level: 19 <br>
181    * release: true <br>
182    */
183   public static final class K extends AndroidRelease {
184 
185     public static final int SDK_INT = 19;
186 
187     public static final boolean RELEASED = true;
188 
189     public static final String SHORT_CODE = "K";
190 
191     public static final String VERSION = "4.4";
192   }
193 
194   // Skipping K Watch release, which was 20.
195 
196   /**
197    * Version: 5.0 <br>
198    * ShortCode: L <br>
199    * SDK API Level: 21 <br>
200    * release: true <br>
201    */
202   public static final class L extends AndroidRelease {
203 
204     public static final int SDK_INT = 21;
205 
206     public static final boolean RELEASED = true;
207 
208     public static final String SHORT_CODE = "L";
209 
210     public static final String VERSION = "5.0";
211   }
212 
213   /**
214    * Version: 5.1 <br>
215    * ShortCode: LMR1 <br>
216    * SDK API Level: 22 <br>
217    * release: true <br>
218    */
219   public static final class LMR1 extends AndroidRelease {
220 
221     public static final int SDK_INT = 22;
222 
223     public static final boolean RELEASED = true;
224 
225     public static final String SHORT_CODE = "LMR1";
226 
227     public static final String VERSION = "5.1";
228   }
229 
230   /**
231    * Version: 6.0 <br>
232    * ShortCode: M <br>
233    * SDK API Level: 23 <br>
234    * release: true <br>
235    */
236   public static final class M extends AndroidRelease {
237 
238     public static final int SDK_INT = 23;
239 
240     public static final boolean RELEASED = true;
241 
242     public static final String SHORT_CODE = "M";
243 
244     public static final String VERSION = "6.0";
245   }
246 
247   /**
248    * Version: 7.0 <br>
249    * ShortCode: N <br>
250    * SDK API Level: 24 <br>
251    * release: true <br>
252    */
253   public static final class N extends AndroidRelease {
254 
255     public static final int SDK_INT = 24;
256 
257     public static final boolean RELEASED = true;
258 
259     public static final String SHORT_CODE = "N";
260 
261     public static final String VERSION = "7.0";
262   }
263 
264   /**
265    * Release: 7.1 <br>
266    * ShortCode: NMR1 <br>
267    * SDK Framework: 25 <br>
268    * release: true <br>
269    */
270   public static final class NMR1 extends AndroidRelease {
271 
272     public static final int SDK_INT = 25;
273 
274     public static final boolean RELEASED = true;
275 
276     public static final String SHORT_CODE = "NMR1";
277 
278     private static final String VERSION = "7.1";
279   }
280 
281   /**
282    * Release: 8.0 <br>
283    * ShortCode: O <br>
284    * SDK API Level: 26 <br>
285    * release: true <br>
286    */
287   public static final class O extends AndroidRelease {
288 
289     public static final int SDK_INT = 26;
290 
291     public static final boolean RELEASED = true;
292 
293     public static final String SHORT_CODE = "O";
294 
295     public static final String VERSION = "8.0";
296   }
297 
298   /**
299    * Release: 8.1 <br>
300    * ShortCode: OMR1 <br>
301    * SDK API Level: 27 <br>
302    * release: true <br>
303    */
304   public static final class OMR1 extends AndroidRelease {
305 
306     public static final int SDK_INT = 27;
307 
308     public static final boolean RELEASED = true;
309 
310     public static final String SHORT_CODE = "OMR1";
311 
312     public static final String VERSION = "8.1";
313   }
314 
315   /**
316    * Release: 9.0 <br>
317    * ShortCode: P <br>
318    * SDK API Level: 28 <br>
319    * release: true <br>
320    */
321   public static final class P extends AndroidRelease {
322 
323     public static final int SDK_INT = 28;
324 
325     public static final boolean RELEASED = true;
326 
327     public static final String SHORT_CODE = "P";
328 
329     public static final String VERSION = "9.0";
330   }
331 
332   /**
333    * Release: 10.0 <br>
334    * ShortCode: Q <br>
335    * SDK API Level: 29 <br>
336    * release: true <br>
337    */
338   public static final class Q extends AndroidRelease {
339 
340     public static final int SDK_INT = 29;
341 
342     public static final boolean RELEASED = true;
343 
344     public static final String SHORT_CODE = "Q";
345 
346     public static final String VERSION = "10.0";
347   }
348 
349   /**
350    * Release: 11.0 <br>
351    * ShortCode: R <br>
352    * SDK API Level: 30 <br>
353    * release: true <br>
354    */
355   public static final class R extends AndroidRelease {
356 
357     public static final int SDK_INT = 30;
358 
359     public static final boolean RELEASED = true;
360 
361     public static final String SHORT_CODE = "R";
362 
363     public static final String VERSION = "11.0";
364   }
365 
366   /**
367    * Release: 12.0 <br>
368    * ShortCode: S <br>
369    * SDK API Level: 31 <br>
370    * release: true <br>
371    */
372   public static final class S extends AndroidRelease {
373 
374     public static final int SDK_INT = 31;
375 
376     public static final boolean RELEASED = true;
377 
378     public static final String SHORT_CODE = "S";
379 
380     public static final String VERSION = "12.0";
381   }
382 
383   /**
384    * Release: 12.1 <br>
385    * ShortCode: Sv2 <br>
386    * SDK API Level: 32 <br>
387    * release: true <br>
388    */
389   @SuppressWarnings("UPPER_SNAKE_CASE")
390   public static final class Sv2 extends AndroidRelease {
391 
392     public static final int SDK_INT = 32;
393 
394     public static final boolean RELEASED = true;
395 
396     public static final String SHORT_CODE = "Sv2";
397 
398     public static final String VERSION = "12.1";
399   }
400 
401   /**
402    * Release: 13.0 <br>
403    * ShortCode: T <br>
404    * SDK API Level: 33 <br>
405    * release: true <br>
406    */
407   public static final class T extends AndroidRelease {
408 
409     public static final int SDK_INT = 33;
410 
411     public static final boolean RELEASED = true;
412 
413     public static final String SHORT_CODE = "T";
414 
415     public static final String VERSION = "13.0";
416   }
417 
418   /**
419    * Potential Release: 14.0 <br>
420    * ShortCode: U <br>
421    * SDK API Level: 34 <br>
422    * release: false <br>
423    */
424   public static final class U extends AndroidRelease {
425 
426     public static final int SDK_INT = 34;
427 
428     public static final boolean RELEASED = true;
429 
430     public static final String SHORT_CODE = "U";
431 
432     public static final String VERSION = "14.0";
433   }
434 
435   /**
436    * Potential Release: 15.0 <br>
437    * ShortCode: V <br>
438    * SDK API Level: 34+ <br>
439    * release: false <br>
440    */
441   public static final class V extends AndroidRelease {
442 
443     public static final int SDK_INT = 35;
444 
445     public static final boolean RELEASED = false;
446 
447     public static final String SHORT_CODE = "V";
448 
449     public static final String VERSION = "15";
450   }
451 
452   /** The current release this process is running on. */
453   public static final AndroidRelease CURRENT;
454 
455   @Nullable
getReleaseForSdkInt(@ullable Integer sdkInt)456   public static AndroidRelease getReleaseForSdkInt(@Nullable Integer sdkInt) {
457     if (sdkInt == null) {
458       return null;
459     } else {
460       return information.sdkIntToAllReleases.get(sdkInt);
461     }
462   }
463 
getReleases()464   public static List<AndroidRelease> getReleases() {
465     List<AndroidRelease> output = new ArrayList<>();
466     for (AndroidRelease release : information.allReleases) {
467       if (release.isReleased()) {
468         output.add(release);
469       }
470     }
471     return output;
472   }
473 
getUnreleased()474   public static List<AndroidRelease> getUnreleased() {
475     List<AndroidRelease> output = new ArrayList<>();
476     for (AndroidRelease release : information.allReleases) {
477       if (!release.isReleased()) {
478         output.add(release);
479       }
480     }
481     return output;
482   }
483 
484   /**
485    * Responsible for aggregating and interpreting the static state representing the current
486    * AndroidReleases known to AndroidVersions class.
487    */
488   static class SdkInformation {
489     final List<AndroidRelease> allReleases;
490     final List<Class<? extends AndroidRelease>> classesWithIllegalNames;
491     final AndroidRelease latestRelease;
492     final AndroidRelease earliestUnreleased;
493 
494     // In the future we may need a multimap for sdkInts should they stay static across releases.
495     final Map<Integer, AndroidRelease> sdkIntToAllReleases = new HashMap<>();
496     final Map<String, AndroidRelease> shortCodeToAllReleases = new HashMap<>();
497 
498     // detected errors
499     final List<Map.Entry<AndroidRelease, AndroidRelease>> sdkIntCollisions = new ArrayList<>();
500     Map.Entry<AndroidRelease, AndroidRelease> sdkApiMisordered = null;
501 
SdkInformation( List<AndroidRelease> releases, List<Class<? extends AndroidRelease>> classesWithIllegalNames)502     public SdkInformation(
503         List<AndroidRelease> releases,
504         List<Class<? extends AndroidRelease>> classesWithIllegalNames) {
505       this.allReleases = releases;
506       this.classesWithIllegalNames = classesWithIllegalNames;
507       AndroidRelease latestRelease = null;
508       AndroidRelease earliestUnreleased = null;
509       for (AndroidRelease release : allReleases) {
510         if (release.isReleased()) {
511           if (latestRelease == null || latestRelease.compareTo(release) > 0) {
512             latestRelease = release;
513           }
514         } else {
515           if (earliestUnreleased == null || earliestUnreleased.compareTo(release) < 0) {
516             earliestUnreleased = release;
517           }
518         }
519       }
520       this.latestRelease = latestRelease;
521       this.earliestUnreleased = earliestUnreleased;
522       verifyStaticInformation();
523     }
524 
verifyStaticInformation()525     private void verifyStaticInformation() {
526       for (AndroidRelease release : this.allReleases) {
527         // Construct a map of all sdkInts to releases and note duplicates
528         AndroidRelease sdkCollision = this.sdkIntToAllReleases.put(release.getSdkInt(), release);
529         if (sdkCollision != null) {
530           this.sdkIntCollisions.add(new AbstractMap.SimpleEntry<>(release, sdkCollision));
531         }
532         // Construct a map of all short codes to releases, and note duplicates
533         this.shortCodeToAllReleases.put(release.getShortCode(), release);
534         // There is no need to check for shortCode duplicates as the Field name must match the
535         // short code.
536       }
537       if (earliestUnreleased != null
538           && latestRelease != null
539           && latestRelease.getSdkInt() >= earliestUnreleased.getSdkInt()) {
540         sdkApiMisordered = new AbstractMap.SimpleEntry<>(latestRelease, earliestUnreleased);
541       }
542     }
543 
throwStaticErrors()544     private void throwStaticErrors() {
545       StringBuilder errors = new StringBuilder();
546       if (!this.classesWithIllegalNames.isEmpty()) {
547         errors
548             .append("The following classes do not follow the naming criteria for ")
549             .append("releases or do not have the short codes in ")
550             .append("their internal fields. Please correct them: ")
551             .append(this.classesWithIllegalNames)
552             .append("\n");
553       }
554       if (sdkApiMisordered != null) {
555         errors
556             .append("The latest released sdk ")
557             .append(sdkApiMisordered.getKey().getShortCode())
558             .append(" has a sdkInt greater than the earliest unreleased sdk ")
559             .append(sdkApiMisordered.getValue().getShortCode())
560             .append("this implies sdks were released out of order which is highly unlikely.\n");
561       }
562       if (!sdkIntCollisions.isEmpty()) {
563         errors.append(
564             "The following sdks have different shortCodes, but identical sdkInt " + "versions:\n");
565         for (Map.Entry<AndroidRelease, AndroidRelease> entry : sdkIntCollisions) {
566           errors
567               .append("Both ")
568               .append(entry.getKey().getShortCode())
569               .append(" and ")
570               .append(entry.getValue().getShortCode())
571               .append("have the same sdkInt value of ")
572               .append(entry.getKey().getSdkInt())
573               .append("\n");
574         }
575       }
576       if (errors.length() > 0) {
577         throw new RuntimeException(
578             errors
579                 .append("Please check the AndroidReleases defined ")
580                 .append("in ")
581                 .append(AndroidVersions.class.getName())
582                 .append("and ensure they are aligned with the versions of")
583                 .append(" Android.")
584                 .toString());
585       }
586     }
587 
computeCurrentSdk( int reportedVersion, String releaseName, String codename, List<String> activeCodeNames)588     public AndroidRelease computeCurrentSdk(
589         int reportedVersion, String releaseName, String codename, List<String> activeCodeNames) {
590       Logger.info("Reported Version: " + reportedVersion);
591       Logger.info("Release Name: " + releaseName);
592       Logger.info("Code Name: " + codename);
593       Logger.info("Active Code Names: " + String.join(",", activeCodeNames));
594 
595       AndroidRelease current = null;
596       // Special case "REL", which means the build is not a pre-release build.
597       if ("REL".equals(codename)) {
598         // the first letter of the code name equal to the release number.
599         current = sdkIntToAllReleases.get(reportedVersion);
600         if (current != null && !current.isReleased()) {
601           throw new RuntimeException(
602               "The current sdk "
603                   + current.getShortCode()
604                   + " has been released. Please update the contents of "
605                   + AndroidVersions.class.getName()
606                   + " to mark sdk "
607                   + current.getShortCode()
608                   + " as released.");
609         }
610       } else {
611         // Get known active code name letters
612 
613         List<String> activeCodenameLetter = new ArrayList<>();
614         for (String name : activeCodeNames) {
615           activeCodenameLetter.add(name.toUpperCase(Locale.getDefault()).substring(0, 1));
616         }
617 
618         // If the process is operating with a code name.
619         if (codename != null) {
620           StringBuilder detectedProblems = new StringBuilder();
621           // This is safe for minor releases ( X.1 ) as long as they have added an entry
622           // corresponding to the sdk of that release and the prior major release is marked as
623           // "released" on its entry in this file.  If not this class will fail to initialize.
624           // The assumption is that only one of the major or minor version of a code name
625           // is under development and unreleased at any give time (S or Sv2).
626           String foundCode = codename.toUpperCase(Locale.getDefault()).substring(0, 1);
627           int loc = activeCodenameLetter.indexOf(foundCode);
628           if (loc == -1) {
629             detectedProblems
630                 .append("The current codename's (")
631                 .append(codename)
632                 .append(") first letter (")
633                 .append(foundCode)
634                 .append(") is not in the list of active code's first letters: ")
635                 .append(activeCodenameLetter)
636                 .append("\n");
637           } else {
638             // attempt to find assume the fullname is the "shortCode", aka "Sv2", "OMR1".
639             current = shortCodeToAllReleases.get(codename);
640             // else, assume the fullname is the first letter is correct.
641             if (current == null) {
642               current = shortCodeToAllReleases.get(String.valueOf(foundCode));
643             }
644           }
645           if (current == null) {
646             detectedProblems
647                 .append("No known release is associated with the shortCode of \"")
648                 .append(foundCode)
649                 .append("\" or \"")
650                 .append(codename)
651                 .append("\"\n");
652           } else if (current.isReleased()) {
653             detectedProblems
654                 .append("The current sdk ")
655                 .append(current.getShortCode())
656                 .append(" has been been marked as released. Please update the ")
657                 .append("contents of current sdk jar to the released version.\n");
658           }
659           if (detectedProblems.length() > 0) {
660             throw new RuntimeException(detectedProblems.toString());
661           }
662         }
663       }
664       return current;
665     }
666   }
667 
668   /**
669    * Reads all AndroidReleases in this class and populates SdkInformation, checking for sanity in
670    * the shortCode, sdkInt, and release information.
671    *
672    * <p>All errors are stored and can be reported at once by asking the SdkInformation to throw a
673    * runtime exception after it has been populated.
674    */
gatherStaticSdkInformationFromThisClass()675   static SdkInformation gatherStaticSdkInformationFromThisClass() {
676     List<AndroidRelease> allReleases = new ArrayList<>();
677     List<Class<? extends AndroidRelease>> classesWithIllegalNames = new ArrayList<>();
678     for (Class<?> clazz : AndroidVersions.class.getClasses()) {
679       if (AndroidRelease.class.isAssignableFrom(clazz)
680           && !clazz.isInterface()
681           && !Modifier.isAbstract(clazz.getModifiers())) {
682         try {
683           AndroidRelease rel = (AndroidRelease) clazz.getDeclaredConstructor().newInstance();
684           allReleases.add(rel);
685           // inspect field name - as this is our only chance to inspect it.
686           if (!rel.getClass().getSimpleName().equals(rel.getShortCode())) {
687             classesWithIllegalNames.add(rel.getClass());
688           }
689         } catch (NoSuchMethodException
690             | InstantiationException
691             | IllegalArgumentException
692             | IllegalAccessException
693             | InvocationTargetException ex) {
694           throw new RuntimeException(
695               "Classes "
696                   + clazz.getName()
697                   + "should be accessible via "
698                   + AndroidVersions.class.getCanonicalName()
699                   + " and have a default public no-op constructor ",
700               ex);
701         }
702       }
703     }
704     Collections.sort(allReleases, AndroidRelease::compareTo);
705 
706     SdkInformation sdkInformation = new SdkInformation(allReleases, classesWithIllegalNames);
707     sdkInformation.throwStaticErrors();
708     return sdkInformation;
709   }
710 
computeReleaseVersion(JarFile jarFile)711   static AndroidRelease computeReleaseVersion(JarFile jarFile) throws IOException {
712     ZipEntry buildProp = jarFile.getEntry("build.prop");
713     Properties buildProps = new Properties();
714     buildProps.load(jarFile.getInputStream(buildProp));
715     return computeCurrentSdkFromBuildProps(buildProps);
716   }
717 
computeCurrentSdkFromBuildProps(Properties buildProps)718   static AndroidRelease computeCurrentSdkFromBuildProps(Properties buildProps) {
719     // 33, 34, 35 ....
720     String sdkVersionString = buildProps.getProperty("ro.build.version.sdk");
721     int sdk = sdkVersionString == null ? 0 : Integer.parseInt(sdkVersionString);
722     // "REL"
723     String release = buildProps.getProperty("ro.build.version.release");
724     // "Tiramasu", "UpsideDownCake"
725     String codename = buildProps.getProperty("ro.build.version.codename");
726     // "Tiramasu,UpsideDownCake", "UpsideDownCake", "REL"
727     String codenames = buildProps.getProperty("ro.build.version.all_codenames");
728     String[] allCodeNames = codenames == null ? new String[0] : codenames.split(",");
729     String[] activeCodeNames =
730         allCodeNames.length > 0 && allCodeNames[0].equals("REL") ? new String[0] : allCodeNames;
731     return information.computeCurrentSdk(sdk, release, codename, asList(activeCodeNames));
732   }
733 
734   /**
735    * If we are working in android source, this code detects the list of active code names if any.
736    */
getActiveCodeNamesIfAny(Class<?> targetClass)737   private static List<String> getActiveCodeNamesIfAny(Class<?> targetClass) {
738     try {
739       Field activeCodeFields = targetClass.getDeclaredField("ACTIVE_CODENAMES");
740       String[] activeCodeNames = (String[]) activeCodeFields.get(null);
741       if (activeCodeNames == null) {
742         return new ArrayList<>();
743       }
744       return asList(activeCodeNames);
745     } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException ex) {
746       return new ArrayList<>();
747     }
748   }
749 
750   private static final SdkInformation information;
751 
752   static {
753     AndroidRelease currentRelease = null;
754     information = gatherStaticSdkInformationFromThisClass();
755     try {
756       Class<?> buildClass =
757           Class.forName("android.os.Build", false, Thread.currentThread().getContextClassLoader());
758       System.out.println("build class " + buildClass);
759       Class<?> versionClass = null;
760       for (Class<?> c : buildClass.getClasses()) {
761         if (c.getSimpleName().equals("VERSION")) {
762           versionClass = c;
763           System.out.println("Version class " + versionClass);
764           break;
765         }
766       }
767       if (versionClass != null) {
768         // 33, 34, etc....
769         int sdkInt = (int) ReflectionHelpers.getStaticField(versionClass, "SDK_INT");
770         // Either unset, or 13, 14, etc....
771         String release = ReflectionHelpers.getStaticField(versionClass, "RELEASE");
772         // Either REL if release is set, or Tiramasu, UpsideDownCake, etc
773         String codename = ReflectionHelpers.getStaticField(versionClass, "CODENAME");
774         List<String> activeCodeNames = getActiveCodeNamesIfAny(versionClass);
775         currentRelease = information.computeCurrentSdk(sdkInt, release, codename, activeCodeNames);
776       }
777     } catch (ClassNotFoundException | IllegalArgumentException | UnsatisfiedLinkError e) {
778       // No op, this class should be usable outside of a Robolectric sandbox.
779     }
780     CURRENT = currentRelease;
781   }
782 }
783