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 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