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