blob: 76653e0e02250933eb22a57ca0980a3a6378e122 [file] [log] [blame]
/*
* 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)));
}
}