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 S = Build.VERSION_CODES.S; 36 public static final int T = Build.VERSION_CODES.TIRAMISU; 37 public static final int U = Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 38 public static final int V = Build.VERSION_CODES.VANILLA_ICE_CREAM; 39 public static final int B = Build.VERSION_CODES.CUR_DEVELOPMENT; 40 41 /** Any version. */ 42 public static final int ANY = -1; 43 44 private static final ImmutableSet<String> DEVELOPMENT_CODENAMES = 45 ImmutableSet.of("Baklava"); 46 47 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 48 Versions()49 private Versions() { 50 51 } 52 53 /** 54 * Throw a {@link UnsupportedOperationException} if the minimum version requirement is not met. 55 */ requireMinimumVersion(int min)56 public static void requireMinimumVersion(int min) { 57 if (!meetsSdkVersionRequirements(min, ANY)) { 58 String currentVersion = meetsMinimumSdkVersionRequirement(R) 59 ? Build.VERSION.RELEASE_OR_CODENAME : Integer.toString(Build.VERSION.SDK_INT); 60 throw new UnsupportedOperationException( 61 "This feature is only available on " 62 + versionToLetter(min) 63 + "+ (currently " + currentVersion + ")"); 64 } 65 } 66 versionToLetter(int version)67 private static String versionToLetter(int version) { 68 for (Field field : Build.VERSION_CODES.class.getFields()) { 69 if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) { 70 continue; 71 } 72 if (!field.getType().equals(int.class)) { 73 continue; 74 } 75 try { 76 int fieldValue = (int) field.get(null); 77 78 if (fieldValue == version) { 79 return field.getName(); 80 } 81 } catch (IllegalAccessException e) { 82 // Couldn't access this variable - ignore 83 } 84 } 85 86 return Integer.toString(version); 87 } 88 89 /** 90 * {@code true} if the minimum version requirement is met. 91 */ meetsMinimumSdkVersionRequirement(int min)92 public static boolean meetsMinimumSdkVersionRequirement(int min) { 93 return meetsSdkVersionRequirements(min, ANY); 94 } 95 96 /** 97 * {@code true} if the minimum and maximum version requirements are met. 98 * 99 * <p>Use {@link #ANY} to accept any version. 100 */ meetsSdkVersionRequirements(int min, int max)101 public static boolean meetsSdkVersionRequirements(int min, int max) { 102 if (min != ANY) { 103 if (min == Build.VERSION_CODES.CUR_DEVELOPMENT) { 104 if (!DEVELOPMENT_CODENAMES.contains(CODENAME)) { 105 if (VERBOSE) { 106 Log.v(TAG, "meetsSdkVersionRequirements(" + min + "," + max 107 + "): false1 (Current: " + CODENAME + ", sdk: " 108 + VERSION.SDK_INT + ")"); 109 } 110 return false; 111 } 112 } else if (min > Build.VERSION.SDK_INT) { 113 if (VERBOSE) { 114 Log.v(TAG, "meetsSdkVersionRequirements(" + min + "," 115 + max + "): false2 (Current: " + CODENAME + ", sdk: " 116 + VERSION.SDK_INT + ")"); 117 } 118 return false; 119 } 120 } 121 122 if (max != ANY && max != Integer.MAX_VALUE 123 && max != Build.VERSION_CODES.CUR_DEVELOPMENT) { 124 if (max < Build.VERSION.SDK_INT) { 125 if (VERBOSE) { 126 Log.v(TAG, "meetsSdkVersionRequirements(" + min + "," 127 + max + "): false3 (Current: " + CODENAME + ", sdk: " 128 + VERSION.SDK_INT + ")"); 129 } 130 return false; 131 } 132 if (DEVELOPMENT_CODENAMES.contains(CODENAME)) { 133 if (VERBOSE) { 134 Log.v(TAG, "meetsSdkVersionRequirements(" + min + "," 135 + max + "): false4 (Current: " + CODENAME + ", sdk: " 136 + VERSION.SDK_INT + ")"); 137 } 138 return false; 139 } 140 } 141 142 if (VERBOSE) { 143 Log.v(TAG, "meetsSdkVersionRequirements(" + min + "," + max 144 + "): true (Current: " + CODENAME + ", sdk: " + VERSION.SDK_INT + ")"); 145 } 146 return true; 147 } 148 149 /** 150 * {@code true} if the current running version is the latest in-development version. 151 */ isDevelopmentVersion()152 public static boolean isDevelopmentVersion() { 153 return Build.VERSION.SDK_INT == Build.VERSION_CODES.CUR_DEVELOPMENT 154 && DEVELOPMENT_CODENAMES.contains(CODENAME); 155 } 156 } 157