| /* |
| * Copyright (C) 2009 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.sdklib; |
| |
| import com.android.SdkConstants; |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.sdklib.repository.PkgProps; |
| |
| import java.util.Properties; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Represents the version of a target or device. |
| * <p/> |
| * A version is defined by an API level and an optional code name. |
| * <ul><li>Release versions of the Android platform are identified by their API level (integer), |
| * (technically the code name for release version is "REL" but this class will return |
| * <code>null<code> instead.)</li> |
| * <li>Preview versions of the platform are identified by a code name. Their API level |
| * is usually set to the value of the previous platform.</li></ul> |
| * <p/> |
| * While this class contains both values, its goal is to abstract them, so that code comparing 2+ |
| * versions doesn't have to deal with the logic of handle both values. |
| * <p/> |
| * There are some cases where ones may want to access the values directly. This can be done |
| * with {@link #getApiLevel()} and {@link #getCodename()}. |
| * <p/> |
| * For generic UI display of the API version, {@link #getApiString()} is to be used. |
| */ |
| public final class AndroidVersion implements Comparable<AndroidVersion> { |
| |
| private final int mApiLevel; |
| private final String mCodename; |
| |
| /** The default AndroidVersion for minSdkVersion and targetSdkVersion if not specified */ |
| public static final AndroidVersion DEFAULT = new AndroidVersion(1, null); |
| |
| /** |
| * Thrown when an {@link AndroidVersion} object could not be created. |
| * @see AndroidVersion#AndroidVersion(Properties) |
| */ |
| public static final class AndroidVersionException extends Exception { |
| private static final long serialVersionUID = 1L; |
| |
| AndroidVersionException(String message, Throwable cause) { |
| super(message, cause); |
| } |
| } |
| |
| /** |
| * Creates an {@link AndroidVersion} with the given api level and codename. |
| * Codename should be null for a release version, otherwise it's a preview codename. |
| */ |
| public AndroidVersion(int apiLevel, @Nullable String codename) { |
| mApiLevel = apiLevel; |
| mCodename = sanitizeCodename(codename); |
| } |
| |
| /** |
| * Creates an {@link AndroidVersion} from {@link Properties}, with default values if the |
| * {@link Properties} object doesn't contain the expected values. |
| * <p/>The {@link Properties} is expected to have been filled with |
| * {@link #saveProperties(Properties)}. |
| */ |
| public AndroidVersion(@Nullable Properties properties, |
| int defaultApiLevel, |
| @Nullable String defaultCodeName) { |
| if (properties == null) { |
| mApiLevel = defaultApiLevel; |
| mCodename = sanitizeCodename(defaultCodeName); |
| } else { |
| mApiLevel = Integer.parseInt(properties.getProperty(PkgProps.VERSION_API_LEVEL, |
| Integer.toString(defaultApiLevel))); |
| mCodename = sanitizeCodename( |
| properties.getProperty(PkgProps.VERSION_CODENAME, defaultCodeName)); |
| } |
| } |
| |
| /** |
| * Creates an {@link AndroidVersion} from {@link Properties}. The properties must contain |
| * android version information, or an exception will be thrown. |
| * @throws AndroidVersionException if no Android version information have been found |
| * |
| * @see #saveProperties(Properties) |
| */ |
| public AndroidVersion(@NonNull Properties properties) throws AndroidVersionException { |
| Exception error = null; |
| |
| String apiLevel = properties.getProperty(PkgProps.VERSION_API_LEVEL, null/*defaultValue*/); |
| if (apiLevel != null) { |
| try { |
| mApiLevel = Integer.parseInt(apiLevel); |
| mCodename = sanitizeCodename(properties.getProperty(PkgProps.VERSION_CODENAME, |
| null/*defaultValue*/)); |
| return; |
| } catch (NumberFormatException e) { |
| error = e; |
| } |
| } |
| |
| // reaching here means the Properties object did not contain the apiLevel which is required. |
| throw new AndroidVersionException(PkgProps.VERSION_API_LEVEL + " not found!", error); |
| } |
| |
| /** |
| * Creates an {@link AndroidVersion} from a string that may be an integer API |
| * level or a string codename. |
| * <p/> |
| * <Em>Important</em>: An important limitation of this method is that cannot possible |
| * recreate the API level integer from a pure string codename. This is only OK to use |
| * if the caller can guarantee that only {@link #getApiString()} will be used later. |
| * Wrong things will happen if the caller then tries to resolve the numeric |
| * {@link #getApiLevel()}. |
| * |
| * @param apiOrCodename A non-null API integer or a codename in its "ALL_CAPS" format. |
| * "REL" is notable not a valid codename. |
| * @throws AndroidVersionException if the input isn't a pure integer or doesn't look like |
| * a valid string codename. |
| */ |
| public AndroidVersion(@NonNull String apiOrCodename) throws AndroidVersionException { |
| int apiLevel = 0; |
| String codename = null; |
| try { |
| apiLevel = Integer.parseInt(apiOrCodename); |
| } catch (NumberFormatException ignore) { |
| // We don't know the API level. Android platform codenames are all caps. |
| // REL is a release-reserved keyword which we can use here. |
| |
| if (!SdkConstants.CODENAME_RELEASE.equals(apiOrCodename)) { |
| if (Pattern.matches("[A-Z_]+", apiOrCodename)) { |
| codename = apiOrCodename; |
| } |
| } |
| } |
| |
| mApiLevel = apiLevel; |
| mCodename = sanitizeCodename(codename); |
| |
| if (mApiLevel <= 0 && codename == null) { |
| throw new AndroidVersionException( |
| "Invalid android API or codename " + apiOrCodename, //$NON-NLS-1$ |
| null); |
| } |
| } |
| |
| public void saveProperties(@NonNull Properties props) { |
| props.setProperty(PkgProps.VERSION_API_LEVEL, Integer.toString(mApiLevel)); |
| if (mCodename != null) { |
| props.setProperty(PkgProps.VERSION_CODENAME, mCodename); |
| } |
| } |
| |
| /** |
| * Returns the api level as an integer. |
| * <p/>For target that are in preview mode, this can be superseded by |
| * {@link #getCodename()}. |
| * <p/>To display the API level in the UI, use {@link #getApiString()}, which will use the |
| * codename if applicable. |
| * @see #getCodename() |
| * @see #getApiString() |
| */ |
| public int getApiLevel() { |
| return mApiLevel; |
| } |
| |
| /** |
| * Returns the API level as an integer. If this is a preview platform, it |
| * will return the expected final version of the API rather than the current API |
| * level. This is the "feature level" as opposed to the "release level" returned by |
| * {@link #getApiLevel()} in the sense that it is useful when you want |
| * to check the presence of a given feature from an API, and we consider the feature |
| * present in preview platforms as well. |
| * |
| * @return the API level of this version, +1 for preview platforms |
| */ |
| public int getFeatureLevel() { |
| //noinspection VariableNotUsedInsideIf |
| return mCodename != null ? mApiLevel + 1 : mApiLevel; |
| } |
| |
| /** |
| * Returns the version code name if applicable, null otherwise. |
| * <p/>If the codename is non null, then the API level should be ignored, and this should be |
| * used as a unique identifier of the target instead. |
| */ |
| @Nullable |
| public String getCodename() { |
| return mCodename; |
| } |
| |
| /** |
| * Returns a string representing the API level and/or the code name. |
| */ |
| @NonNull |
| public String getApiString() { |
| if (mCodename != null) { |
| return mCodename; |
| } |
| |
| return Integer.toString(mApiLevel); |
| } |
| |
| /** |
| * Returns whether or not the version is a preview version. |
| */ |
| public boolean isPreview() { |
| return mCodename != null; |
| } |
| |
| /** |
| * Checks whether a device running a version similar to the receiver can run a project compiled |
| * for the given <var>version</var>. |
| * <p/> |
| * Be aware that this is not a perfect test, as other properties could break compatibility |
| * despite this method returning true. For a more comprehensive test, see |
| * {@link IAndroidTarget#canRunOn(IAndroidTarget)}. |
| * <p/> |
| * Nevertheless, when testing if an application can run on a device (where there is no |
| * access to the list of optional libraries), this method can give a good indication of whether |
| * there is a chance the application could run, or if there's a direct incompatibility. |
| */ |
| public boolean canRun(@NonNull AndroidVersion appVersion) { |
| // if the application is compiled for a preview version, the device must be running exactly |
| // the same. |
| if (appVersion.mCodename != null) { |
| return appVersion.mCodename.equals(mCodename); |
| } |
| |
| // otherwise, we check the api level (note that a device running a preview version |
| // will have the api level of the previous platform). |
| return mApiLevel >= appVersion.mApiLevel; |
| } |
| |
| /** |
| * Returns <code>true</code> if the AndroidVersion is an API level equals to |
| * <var>apiLevel</var>. |
| */ |
| public boolean equals(int apiLevel) { |
| return mCodename == null && apiLevel == mApiLevel; |
| } |
| |
| /** |
| * Compares the receiver with either an {@link AndroidVersion} object or a {@link String} |
| * object. |
| * <p/>If <var>obj</var> is a {@link String}, then the method will first check if it's a string |
| * representation of a number, in which case it'll compare it to the api level. Otherwise, it'll |
| * compare it against the code name. |
| * <p/>For all other type of object give as parameter, this method will return |
| * <code>false</code>. |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof AndroidVersion) { |
| AndroidVersion version = (AndroidVersion)obj; |
| |
| if (mCodename == null) { |
| return version.mCodename == null && |
| mApiLevel == version.mApiLevel; |
| } else { |
| return mCodename.equals(version.mCodename) && |
| mApiLevel == version.mApiLevel; |
| } |
| |
| } else if (obj instanceof String) { |
| // if we have a code name, this must match. |
| if (mCodename != null) { |
| return mCodename.equals(obj); |
| } |
| |
| // else we try to convert to a int and compare to the api level |
| try { |
| int value = Integer.parseInt((String)obj); |
| return value == mApiLevel; |
| } catch (NumberFormatException e) { |
| // not a number? we'll return false below. |
| } |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| if (mCodename != null) { |
| return mCodename.hashCode(); |
| } |
| |
| // there may be some collisions between the hashcode of the codename and the api level |
| // but it's acceptable. |
| return mApiLevel; |
| } |
| |
| /** |
| * Returns a string with the API Level and optional codename. |
| * Useful for debugging. |
| * For display purpose, please use {@link #getApiString()} instead. |
| */ |
| @Override |
| public String toString() { |
| String s = String.format("API %1$d", mApiLevel); //$NON-NLS-1$ |
| if (isPreview()) { |
| s += String.format(", %1$s preview", mCodename); //$NON-NLS-1$ |
| } |
| return s; |
| } |
| |
| /** |
| * Compares this object with the specified object for order. Returns a |
| * negative integer, zero, or a positive integer as this object is less |
| * than, equal to, or greater than the specified object. |
| * |
| * @param o the Object to be compared. |
| * @return a negative integer, zero, or a positive integer as this object is |
| * less than, equal to, or greater than the specified object. |
| */ |
| @Override |
| public int compareTo(@NonNull AndroidVersion o) { |
| return compareTo(o.mApiLevel, o.mCodename); |
| } |
| |
| public int compareTo(int apiLevel, @Nullable String codename) { |
| if (mCodename == null) { |
| if (codename == null) { |
| return mApiLevel - apiLevel; |
| } else { |
| if (mApiLevel == apiLevel) { |
| return -1; // same api level but argument is a preview for next version |
| } |
| |
| return mApiLevel - apiLevel; |
| } |
| } else { |
| // 'this' is a preview |
| if (mApiLevel == apiLevel) { |
| if (codename == null) { |
| return +1; |
| } else { |
| return mCodename.compareTo(codename); // strange case where the 2 previews |
| // have different codename? |
| } |
| } else { |
| return mApiLevel - apiLevel; |
| } |
| } |
| } |
| |
| /** |
| * Compares this version with the specified API and returns true if this version |
| * is greater or equal than the requested API -- that is the current version is a |
| * suitable min-api-level for the argument API. |
| */ |
| public boolean isGreaterOrEqualThan(int api) { |
| return compareTo(api, null /*codename*/) >= 0; |
| } |
| |
| /** |
| * Sanitizes the codename string according to the following rules: |
| * - A codename should be {@code null} for a release version or it should be a non-empty |
| * string for an actual preview. |
| * - In input, spacing is trimmed since it is irrelevant. |
| * - An empty string or the special codename "REL" means a release version |
| * and is converted to {@code null}. |
| * |
| * @param codename A possible-null codename. |
| * @return Null for a release version or a non-empty codename. |
| */ |
| @Nullable |
| private static String sanitizeCodename(@Nullable String codename) { |
| if (codename != null) { |
| codename = codename.trim(); |
| if (codename.isEmpty() || SdkConstants.CODENAME_RELEASE.equals(codename)) { |
| codename = null; |
| } |
| } |
| return codename; |
| } |
| } |