| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.modules.utils.build; |
| |
| import android.os.Build; |
| |
| import androidx.annotation.NonNull; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.util.Collections; |
| import java.util.Set; |
| |
| /** |
| * Utility class to check SDK level on a device. |
| * |
| * <p>Prefer using {@link SdkLevel} if the version is known at build time. This should only be used |
| * when a dynamic runtime check is needed. |
| */ |
| public final class UnboundedSdkLevel { |
| |
| /** |
| * Checks if the device is running on a given or newer version of Android. |
| */ |
| public static boolean isAtLeast(@NonNull String version) { |
| return sInstance.isAtLeastInternal(version); |
| } |
| |
| /** |
| * Checks if the device is running on a given or older version of Android. |
| */ |
| public static boolean isAtMost(@NonNull String version) { |
| return sInstance.isAtMostInternal(version); |
| } |
| |
| private static final UnboundedSdkLevel sInstance = |
| new UnboundedSdkLevel( |
| Build.VERSION.SDK_INT, |
| Build.VERSION.CODENAME, |
| SdkLevel.isAtLeastT() |
| ? Build.VERSION.KNOWN_CODENAMES |
| : Collections.emptySet()); |
| |
| private final int mSdkInt; |
| private final String mCodename; |
| private final boolean mIsReleaseBuild; |
| private final Set<String> mKnownCodenames; |
| |
| @VisibleForTesting |
| UnboundedSdkLevel(int sdkInt, String codename, Set<String> knownCodenames) { |
| mSdkInt = sdkInt; |
| mCodename = codename; |
| mIsReleaseBuild = "REL".equals(codename); |
| mKnownCodenames = knownCodenames; |
| } |
| |
| @VisibleForTesting |
| boolean isAtLeastInternal(@NonNull String version) { |
| if (mIsReleaseBuild) { |
| if (isCodename(version)) { |
| // On release builds only accept future codenames |
| if (mKnownCodenames.contains(version)) { |
| throw new IllegalArgumentException("Artifact with a known codename " + version |
| + " must be recompiled with a finalized integer version."); |
| } |
| // mSdkInt is always less than future codenames |
| return false; |
| } |
| return mSdkInt >= Integer.parseInt(version); |
| } |
| if (isCodename(version)) { |
| return mKnownCodenames.contains(version); |
| } |
| // Never assume what the next SDK level is until SDK finalization completes. |
| // SDK_INT is always assigned the latest finalized value of the SDK. |
| return mSdkInt >= Integer.parseInt(version); |
| } |
| |
| @VisibleForTesting |
| boolean isAtMostInternal(@NonNull String version) { |
| if (mIsReleaseBuild) { |
| if (isCodename(version)) { |
| // On release builds only accept future codenames |
| if (mKnownCodenames.contains(version)) { |
| throw new IllegalArgumentException("Artifact with a known codename " + version |
| + " must be recompiled with a finalized integer version."); |
| } |
| // mSdkInt is always less than future codenames |
| return true; |
| } |
| return mSdkInt <= Integer.parseInt(version); |
| } |
| if (isCodename(version)) { |
| return !mKnownCodenames.contains(version) || mCodename.equals(version); |
| } |
| // Never assume what the next SDK level is until SDK finalization completes. |
| // SDK_INT is always assigned the latest finalized value of the SDK. |
| // |
| // Note: multiple releases can be in development at the same time. For example, during |
| // Sv2 and Tiramisu development, both builds have SDK_INT=31 which is not sufficient |
| // information to differentiate between them. Also, "31" at that point already corresponds |
| // to a previously finalized API level, meaning that the current build is not at most "31". |
| // This is why the comparison is strict, instead of <=. |
| return mSdkInt < Integer.parseInt(version); |
| } |
| |
| private boolean isCodename(String version) { |
| if (version.length() == 0) { |
| throw new IllegalArgumentException(); |
| } |
| // assume Android codenames start with upper case letters. |
| return Character.isUpperCase((version.charAt(0))); |
| } |
| |
| } |