Home | History | Annotate | Download | only in sdklib
      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