1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bedstead.nene.utils;
18 
19 import static android.os.Build.VERSION.CODENAME;
20 
21 import android.os.Build;
22 import android.os.Build.VERSION;
23 import android.util.Log;
24 
25 import com.google.common.collect.ImmutableSet;
26 
27 import java.lang.reflect.Field;
28 
29 /** SDK Version checks. */
30 public final class Versions {
31 
32     private static final String TAG = "Versions";
33 
34     public static final int R = Build.VERSION_CODES.R;
35     public static final int T = Build.VERSION_CODES.TIRAMISU;
36     public static final int U = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
37     public static final int V = Build.VERSION_CODES.VANILLA_ICE_CREAM;
38     public static final int W = Build.VERSION_CODES.CUR_DEVELOPMENT;
39 
40     /** Any version. */
41     public static final int ANY = -1;
42 
43     private static final ImmutableSet<String> DEVELOPMENT_CODENAMES =
44             ImmutableSet.of("UpsideDownCake", "VanillaIceCream");
45 
Versions()46     private Versions() {
47 
48     }
49 
50     /**
51      * Throw a {@link UnsupportedOperationException} if the minimum version requirement is not met.
52      */
requireMinimumVersion(int min)53     public static void requireMinimumVersion(int min) {
54         if (!meetsSdkVersionRequirements(min, ANY)) {
55             String currentVersion = meetsMinimumSdkVersionRequirement(R)
56                     ? Build.VERSION.RELEASE_OR_CODENAME : Integer.toString(Build.VERSION.SDK_INT);
57             throw new UnsupportedOperationException(
58                     "This feature is only available on "
59                             + versionToLetter(min)
60                             + "+ (currently " + currentVersion + ")");
61         }
62     }
63 
versionToLetter(int version)64     private static String versionToLetter(int version) {
65         for (Field field : Build.VERSION_CODES.class.getFields()) {
66             if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
67                 continue;
68             }
69             if (!field.getType().equals(int.class)) {
70                 continue;
71             }
72             try {
73                 int fieldValue = (int) field.get(null);
74 
75                 if (fieldValue == version) {
76                     return field.getName();
77                 }
78             } catch (IllegalAccessException e) {
79                 // Couldn't access this variable - ignore
80             }
81         }
82 
83         return Integer.toString(version);
84     }
85 
86     /**
87      * {@code true} if the minimum version requirement is met.
88      */
meetsMinimumSdkVersionRequirement(int min)89     public static boolean meetsMinimumSdkVersionRequirement(int min) {
90         return meetsSdkVersionRequirements(min, ANY);
91     }
92 
93     /**
94      * {@code true} if the minimum and maximum version requirements are met.
95      *
96      * <p>Use {@link #ANY} to accept any version.
97      */
meetsSdkVersionRequirements(int min, int max)98     public static boolean meetsSdkVersionRequirements(int min, int max) {
99         if (min != ANY) {
100             if (min == Build.VERSION_CODES.CUR_DEVELOPMENT) {
101                 if (!DEVELOPMENT_CODENAMES.contains(CODENAME)) {
102                     Log.e(TAG, "meetsSdkVersionRequirements(" + min + "," + max
103                             + "): false1 (Current: " + CODENAME + ", sdk: "
104                             + VERSION.SDK_INT + ")");
105                     return false;
106                 }
107             } else if (min > Build.VERSION.SDK_INT) {
108                 Log.e(TAG, "meetsSdkVersionRequirements(" + min + ","
109                         + max + "): false2 (Current: " + CODENAME + ", sdk: "
110                         + VERSION.SDK_INT + ")");
111                 return false;
112             }
113         }
114 
115         if (max != ANY && max != Integer.MAX_VALUE
116                 && max != Build.VERSION_CODES.CUR_DEVELOPMENT) {
117             if (max < Build.VERSION.SDK_INT) {
118                 Log.e(TAG, "meetsSdkVersionRequirements(" + min + ","
119                         + max + "): false3 (Current: " + CODENAME + ", sdk: "
120                         + VERSION.SDK_INT + ")");
121                 return false;
122             }
123             if (DEVELOPMENT_CODENAMES.contains(CODENAME)) {
124                 Log.e(TAG, "meetsSdkVersionRequirements(" + min + ","
125                         + max + "): false4 (Current: " + CODENAME + ", sdk: "
126                         + VERSION.SDK_INT + ")");
127                 return false;
128             }
129         }
130 
131         Log.e(TAG, "meetsSdkVersionRequirements(" + min + "," + max
132                 + "): true (Current: " + CODENAME + ", sdk: " + VERSION.SDK_INT + ")");
133         return true;
134     }
135 
136     /**
137      * {@code true} if the current running version is the latest in-development version.
138      */
isDevelopmentVersion()139     public static boolean isDevelopmentVersion() {
140         return Build.VERSION.SDK_INT == Build.VERSION_CODES.CUR_DEVELOPMENT
141                 && DEVELOPMENT_CODENAMES.contains(CODENAME);
142     }
143 }
144