1 /* 2 * Copyright (C) 2009 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.sdklib; 18 19 import com.android.sdklib.repository.PkgProps; 20 21 import java.util.Properties; 22 23 /** 24 * Represents the version of a target or device. 25 * <p/> 26 * A version is defined by an API level and an optional code name. 27 * <ul><li>Release versions of the Android platform are identified by their API level (integer), 28 * (technically the code name for release version is "REL" but this class will return 29 * <code>null<code> instead.)</li> 30 * <li>Preview versions of the platform are identified by a code name. Their API level 31 * is usually set to the value of the previous platform.</li></ul> 32 * <p/> 33 * While this class contains both values, its goal is to abstract them, so that code comparing 2+ 34 * versions doesn't have to deal with the logic of handle both values. 35 * <p/> 36 * There are some cases where ones may want to access the values directly. This can be done 37 * with {@link #getApiLevel()} and {@link #getCodename()}. 38 * <p/> 39 * For generic UI display of the API version, {@link #getApiString()} is to be used. 40 */ 41 public final class AndroidVersion implements Comparable<AndroidVersion> { 42 43 private final int mApiLevel; 44 private final String mCodename; 45 46 /** 47 * Thrown when an {@link AndroidVersion} object could not be created. 48 * @see AndroidVersion#AndroidVersion(Properties) 49 */ 50 public final static class AndroidVersionException extends Exception { 51 private static final long serialVersionUID = 1L; 52 53 AndroidVersionException(String message, Throwable cause) { 54 super(message, cause); 55 } 56 } 57 58 /** 59 * Creates an {@link AndroidVersion} with the given api level and codename. 60 * Codename should be null for a release version, otherwise it's a preview codename. 61 */ 62 public AndroidVersion(int apiLevel, String codename) { 63 mApiLevel = apiLevel; 64 mCodename = codename; 65 } 66 67 /** 68 * Creates an {@link AndroidVersion} from {@link Properties}, with default values if the 69 * {@link Properties} object doesn't contain the expected values. 70 * <p/>The {@link Properties} is expected to have been filled with 71 * {@link #saveProperties(Properties)}. 72 */ 73 public AndroidVersion(Properties properties, int defaultApiLevel, String defaultCodeName) { 74 if (properties == null) { 75 mApiLevel = defaultApiLevel; 76 mCodename = defaultCodeName; 77 } else { 78 mApiLevel = Integer.parseInt(properties.getProperty(PkgProps.VERSION_API_LEVEL, 79 Integer.toString(defaultApiLevel))); 80 mCodename = properties.getProperty(PkgProps.VERSION_CODENAME, defaultCodeName); 81 } 82 } 83 84 /** 85 * Creates an {@link AndroidVersion} from {@link Properties}. The properties must contain 86 * android version information, or an exception will be thrown. 87 * @throws AndroidVersionException if no Android version information have been found 88 * 89 * @see #saveProperties(Properties) 90 */ 91 public AndroidVersion(Properties properties) throws AndroidVersionException { 92 Exception error = null; 93 94 String apiLevel = properties.getProperty(PkgProps.VERSION_API_LEVEL, null/*defaultValue*/); 95 if (apiLevel != null) { 96 try { 97 mApiLevel = Integer.parseInt(apiLevel); 98 mCodename = properties.getProperty(PkgProps.VERSION_CODENAME, null/*defaultValue*/); 99 return; 100 } catch (NumberFormatException e) { 101 error = e; 102 } 103 } 104 105 // reaching here means the Properties object did not contain the apiLevel which is required. 106 throw new AndroidVersionException(PkgProps.VERSION_API_LEVEL + " not found!", error); 107 } 108 109 public void saveProperties(Properties props) { 110 props.setProperty(PkgProps.VERSION_API_LEVEL, Integer.toString(mApiLevel)); 111 if (mCodename != null) { 112 props.setProperty(PkgProps.VERSION_CODENAME, mCodename); 113 } 114 } 115 116 /** 117 * Returns the api level as an integer. 118 * <p/>For target that are in preview mode, this can be superseded by 119 * {@link #getCodename()}. 120 * <p/>To display the API level in the UI, use {@link #getApiString()}, which will use the 121 * codename if applicable. 122 * @see #getCodename() 123 * @see #getApiString() 124 */ 125 public int getApiLevel() { 126 return mApiLevel; 127 } 128 129 /** 130 * Returns the version code name if applicable, null otherwise. 131 * <p/>If the codename is non null, then the API level should be ignored, and this should be 132 * used as a unique identifier of the target instead. 133 */ 134 public String getCodename() { 135 return mCodename; 136 } 137 138 /** 139 * Returns a string representing the API level and/or the code name. 140 */ 141 public String getApiString() { 142 if (mCodename != null) { 143 return mCodename; 144 } 145 146 return Integer.toString(mApiLevel); 147 } 148 149 /** 150 * Returns whether or not the version is a preview version. 151 */ 152 public boolean isPreview() { 153 return mCodename != null; 154 } 155 156 /** 157 * Checks whether a device running a version similar to the receiver can run a project compiled 158 * for the given <var>version</var>. 159 * <p/> 160 * Be aware that this is not a perfect test, as other properties could break compatibility 161 * despite this method returning true. For a more comprehensive test, see 162 * {@link IAndroidTarget#canRunOn(IAndroidTarget)}. 163 * <p/> 164 * Nevertheless, when testing if an application can run on a device (where there is no 165 * access to the list of optional libraries), this method can give a good indication of whether 166 * there is a chance the application could run, or if there's a direct incompatibility. 167 */ 168 public boolean canRun(AndroidVersion appVersion) { 169 // if the application is compiled for a preview version, the device must be running exactly 170 // the same. 171 if (appVersion.mCodename != null) { 172 return appVersion.mCodename.equals(mCodename); 173 } 174 175 // otherwise, we check the api level (note that a device running a preview version 176 // will have the api level of the previous platform). 177 return mApiLevel >= appVersion.mApiLevel; 178 } 179 180 /** 181 * Returns <code>true</code> if the AndroidVersion is an API level equals to 182 * <var>apiLevel</var>. 183 */ 184 public boolean equals(int apiLevel) { 185 return mCodename == null && apiLevel == mApiLevel; 186 } 187 188 /** 189 * Compares the receiver with either an {@link AndroidVersion} object or a {@link String} 190 * object. 191 * <p/>If <var>obj</var> is a {@link String}, then the method will first check if it's a string 192 * representation of a number, in which case it'll compare it to the api level. Otherwise, it'll 193 * compare it against the code name. 194 * <p/>For all other type of object give as parameter, this method will return 195 * <code>false</code>. 196 */ 197 @Override 198 public boolean equals(Object obj) { 199 if (obj instanceof AndroidVersion) { 200 AndroidVersion version = (AndroidVersion)obj; 201 202 if (mCodename == null) { 203 return version.mCodename == null && 204 mApiLevel == version.mApiLevel; 205 } else { 206 return mCodename.equals(version.mCodename) && 207 mApiLevel == version.mApiLevel; 208 } 209 210 } else if (obj instanceof String) { 211 // if we have a code name, this must match. 212 if (mCodename != null) { 213 return mCodename.equals(obj); 214 } 215 216 // else we try to convert to a int and compare to the api level 217 try { 218 int value = Integer.parseInt((String)obj); 219 return value == mApiLevel; 220 } catch (NumberFormatException e) { 221 // not a number? we'll return false below. 222 } 223 } 224 225 return false; 226 } 227 228 @Override 229 public int hashCode() { 230 if (mCodename != null) { 231 return mCodename.hashCode(); 232 } 233 234 // there may be some collisions between the hashcode of the codename and the api level 235 // but it's acceptable. 236 return mApiLevel; 237 } 238 239 /** 240 * Returns a string with the API Level and optional codename. 241 * Useful for debugging. 242 * For display purpose, please use {@link #getApiString()} instead. 243 */ 244 @Override 245 public String toString() { 246 String s = String.format("API %1$d", mApiLevel); //$NON-NLS-1$ 247 if (isPreview()) { 248 s += String.format(", %1$s preview", mCodename); //$NON-NLS-1$ 249 } 250 return s; 251 } 252 253 /** 254 * Compares this object with the specified object for order. Returns a 255 * negative integer, zero, or a positive integer as this object is less 256 * than, equal to, or greater than the specified object. 257 * 258 * @param o the Object to be compared. 259 * @return a negative integer, zero, or a positive integer as this object is 260 * less than, equal to, or greater than the specified object. 261 */ 262 public int compareTo(AndroidVersion o) { 263 return compareTo(o.mApiLevel, o.mCodename); 264 } 265 266 private int compareTo(int apiLevel, String codename) { 267 if (mCodename == null) { 268 if (codename == null) { 269 return mApiLevel - apiLevel; 270 } else { 271 if (mApiLevel == apiLevel) { 272 return -1; // same api level but argument is a preview for next version 273 } 274 275 return mApiLevel - apiLevel; 276 } 277 } else { 278 // 'this' is a preview 279 if (mApiLevel == apiLevel) { 280 if (codename == null) { 281 return +1; 282 } else { 283 return mCodename.compareTo(codename); // strange case where the 2 previews 284 // have different codename? 285 } 286 } else { 287 return mApiLevel - apiLevel; 288 } 289 } 290 } 291 292 /** 293 * Compares this version with the specified API and returns true if this version 294 * is greater or equal than the requested API -- that is the current version is a 295 * suitable min-api-level for the argument API. 296 */ 297 public boolean isGreaterOrEqualThan(int api) { 298 return compareTo(api, null /*codename*/) >= 0; 299 } 300 } 301