Home | History | Annotate | Download | only in sdklib
      1 /*
      2  * Copyright (C) 2008 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.prefs.AndroidLocation;
     20 import com.android.prefs.AndroidLocation.AndroidLocationException;
     21 import com.android.sdklib.AndroidVersion.AndroidVersionException;
     22 import com.android.sdklib.internal.project.ProjectProperties;
     23 
     24 import java.io.File;
     25 import java.io.FileInputStream;
     26 import java.io.FileNotFoundException;
     27 import java.io.FileWriter;
     28 import java.io.IOException;
     29 import java.util.ArrayList;
     30 import java.util.Collections;
     31 import java.util.HashMap;
     32 import java.util.HashSet;
     33 import java.util.Map;
     34 import java.util.Properties;
     35 import java.util.regex.Matcher;
     36 import java.util.regex.Pattern;
     37 
     38 /**
     39  * The SDK manager parses the SDK folder and gives access to the content.
     40  * @see PlatformTarget
     41  * @see AddOnTarget
     42  */
     43 public final class SdkManager {
     44 
     45     public final static String PROP_VERSION_SDK = "ro.build.version.sdk";
     46     public final static String PROP_VERSION_CODENAME = "ro.build.version.codename";
     47     public final static String PROP_VERSION_RELEASE = "ro.build.version.release";
     48 
     49     private final static String ADDON_NAME = "name";
     50     private final static String ADDON_VENDOR = "vendor";
     51     private final static String ADDON_API = "api";
     52     private final static String ADDON_DESCRIPTION = "description";
     53     private final static String ADDON_LIBRARIES = "libraries";
     54     private final static String ADDON_DEFAULT_SKIN = "skin";
     55     private final static String ADDON_USB_VENDOR = "usb-vendor";
     56     private final static String ADDON_REVISION = "revision";
     57     private final static String ADDON_REVISION_OLD = "version";
     58 
     59 
     60     private final static Pattern PATTERN_LIB_DATA = Pattern.compile(
     61             "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE);
     62 
     63      // usb ids are 16-bit hexadecimal values.
     64     private final static Pattern PATTERN_USB_IDS = Pattern.compile(
     65             "^0x[a-f0-9]{4}$", Pattern.CASE_INSENSITIVE);
     66 
     67     /** List of items in the platform to check when parsing it. These paths are relative to the
     68      * platform root folder. */
     69     private final static String[] sPlatformContentList = new String[] {
     70         SdkConstants.FN_FRAMEWORK_LIBRARY,
     71         SdkConstants.FN_FRAMEWORK_AIDL,
     72         SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_AAPT,
     73         SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_AIDL,
     74         SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_DX,
     75         SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + SdkConstants.FN_DX_JAR,
     76     };
     77 
     78     /** Preference file containing the usb ids for adb */
     79     private final static String ADB_INI_FILE = "adb_usb.ini";
     80        //0--------90--------90--------90--------90--------90--------90--------90--------9
     81     private final static String ADB_INI_HEADER =
     82         "# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.\n" +
     83         "# USE 'android update adb' TO GENERATE.\n" +
     84         "# 1 USB VENDOR ID PER LINE.\n";
     85 
     86     /** the location of the SDK */
     87     private final String mSdkLocation;
     88     private IAndroidTarget[] mTargets;
     89 
     90     /**
     91      * Create a new {@link SdkManager} instance.
     92      * External users should use {@link #createManager(String, ISdkLog)}.
     93      *
     94      * @param sdkLocation the location of the SDK.
     95      */
     96     private SdkManager(String sdkLocation) {
     97         mSdkLocation = sdkLocation;
     98     }
     99 
    100     /**
    101      * Creates an {@link SdkManager} for a given sdk location.
    102      * @param sdkLocation the location of the SDK.
    103      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    104      * @return the created {@link SdkManager} or null if the location is not valid.
    105      */
    106     public static SdkManager createManager(String sdkLocation, ISdkLog log) {
    107         try {
    108             SdkManager manager = new SdkManager(sdkLocation);
    109             ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
    110             loadPlatforms(sdkLocation, list, log);
    111             loadAddOns(sdkLocation, list, log);
    112 
    113             // sort the targets/add-ons
    114             Collections.sort(list);
    115 
    116             manager.setTargets(list.toArray(new IAndroidTarget[list.size()]));
    117 
    118             // load the samples, after the targets have been set.
    119             manager.loadSamples(log);
    120 
    121             return manager;
    122         } catch (IllegalArgumentException e) {
    123             log.error(e, "Error parsing the sdk.");
    124         }
    125 
    126         return null;
    127     }
    128 
    129     /**
    130      * Returns the location of the SDK.
    131      */
    132     public String getLocation() {
    133         return mSdkLocation;
    134     }
    135 
    136     /**
    137      * Returns the targets that are available in the SDK.
    138      * <p/>
    139      * The array can be empty but not null.
    140      */
    141     public IAndroidTarget[] getTargets() {
    142         return mTargets;
    143     }
    144 
    145     /**
    146      * Sets the targets that are available in the SDK.
    147      * <p/>
    148      * The array can be empty but not null.
    149      */
    150     private void setTargets(IAndroidTarget[] targets) {
    151         assert targets != null;
    152         mTargets = targets;
    153     }
    154 
    155     /**
    156      * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
    157      *
    158      * @param hash the {@link IAndroidTarget} hash string.
    159      * @return The matching {@link IAndroidTarget} or null.
    160      */
    161     public IAndroidTarget getTargetFromHashString(String hash) {
    162         if (hash != null) {
    163             for (IAndroidTarget target : mTargets) {
    164                 if (hash.equals(target.hashString())) {
    165                     return target;
    166                 }
    167             }
    168         }
    169 
    170         return null;
    171     }
    172 
    173     /**
    174      * Updates adb with the USB devices declared in the SDK add-ons.
    175      * @throws AndroidLocationException
    176      * @throws IOException
    177      */
    178     public void updateAdb() throws AndroidLocationException, IOException {
    179         FileWriter writer = null;
    180         try {
    181             // get the android prefs location to know where to write the file.
    182             File adbIni = new File(AndroidLocation.getFolder(), ADB_INI_FILE);
    183             writer = new FileWriter(adbIni);
    184 
    185             // first, put all the vendor id in an HashSet to remove duplicate.
    186             HashSet<Integer> set = new HashSet<Integer>();
    187             IAndroidTarget[] targets = getTargets();
    188             for (IAndroidTarget target : targets) {
    189                 if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) {
    190                     set.add(target.getUsbVendorId());
    191                 }
    192             }
    193 
    194             // write file header.
    195             writer.write(ADB_INI_HEADER);
    196 
    197             // now write the Id in a text file, one per line.
    198             for (Integer i : set) {
    199                 writer.write(String.format("0x%04x\n", i));
    200             }
    201         } finally {
    202             if (writer != null) {
    203                 writer.close();
    204             }
    205         }
    206     }
    207 
    208     /**
    209      * Reloads the content of the SDK.
    210      *
    211      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    212      */
    213     public void reloadSdk(ISdkLog log) {
    214         // get the current target list.
    215         ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
    216         loadPlatforms(mSdkLocation, list, log);
    217         loadAddOns(mSdkLocation, list, log);
    218 
    219         // For now replace the old list with the new one.
    220         // In the future we may want to keep the current objects, so that ADT doesn't have to deal
    221         // with new IAndroidTarget objects when a target didn't actually change.
    222 
    223         // sort the targets/add-ons
    224         Collections.sort(list);
    225         setTargets(list.toArray(new IAndroidTarget[list.size()]));
    226 
    227         // load the samples, after the targets have been set.
    228         loadSamples(log);
    229     }
    230 
    231     /**
    232      * Loads the Platforms from the SDK.
    233      * @param location Location of the SDK
    234      * @param list the list to fill with the platforms.
    235      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    236      */
    237     private static void loadPlatforms(String location, ArrayList<IAndroidTarget> list,
    238             ISdkLog log) {
    239         File platformFolder = new File(location, SdkConstants.FD_PLATFORMS);
    240         if (platformFolder.isDirectory()) {
    241             File[] platforms  = platformFolder.listFiles();
    242 
    243             for (File platform : platforms) {
    244                 if (platform.isDirectory()) {
    245                     PlatformTarget target = loadPlatform(platform, log);
    246                     if (target != null) {
    247                         list.add(target);
    248                     }
    249                 } else {
    250                     log.warning("Ignoring platform '%1$s', not a folder.", platform.getName());
    251                 }
    252             }
    253 
    254             return;
    255         }
    256 
    257         String message = null;
    258         if (platformFolder.exists() == false) {
    259             message = "%s is missing.";
    260         } else {
    261             message = "%s is not a folder.";
    262         }
    263 
    264         throw new IllegalArgumentException(String.format(message,
    265                 platformFolder.getAbsolutePath()));
    266     }
    267 
    268     /**
    269      * Loads a specific Platform at a given location.
    270      * @param platform the location of the platform.
    271      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    272      */
    273     private static PlatformTarget loadPlatform(File platform, ISdkLog log) {
    274         File buildProp = new File(platform, SdkConstants.FN_BUILD_PROP);
    275 
    276         if (buildProp.isFile()) {
    277             Map<String, String> map = ProjectProperties.parsePropertyFile(buildProp, log);
    278 
    279             if (map != null) {
    280                 // look for some specific values in the map.
    281 
    282                 // version string
    283                 String apiName = map.get(PROP_VERSION_RELEASE);
    284                 if (apiName == null) {
    285                     log.warning(
    286                             "Ignoring platform '%1$s': %2$s is missing from '%3$s'",
    287                             platform.getName(), PROP_VERSION_RELEASE,
    288                             SdkConstants.FN_BUILD_PROP);
    289                     return null;
    290                 }
    291 
    292                 // api level
    293                 int apiNumber;
    294                 String stringValue = map.get(PROP_VERSION_SDK);
    295                 if (stringValue == null) {
    296                     log.warning(
    297                             "Ignoring platform '%1$s': %2$s is missing from '%3$s'",
    298                             platform.getName(), PROP_VERSION_SDK,
    299                             SdkConstants.FN_BUILD_PROP);
    300                     return null;
    301                 } else {
    302                     try {
    303                          apiNumber = Integer.parseInt(stringValue);
    304                     } catch (NumberFormatException e) {
    305                         // looks like apiNumber does not parse to a number.
    306                         // Ignore this platform.
    307                         log.warning(
    308                                 "Ignoring platform '%1$s': %2$s is not a valid number in %3$s.",
    309                                 platform.getName(), PROP_VERSION_SDK,
    310                                 SdkConstants.FN_BUILD_PROP);
    311                         return null;
    312                     }
    313                 }
    314 
    315                 // codename (optional)
    316                 String apiCodename = map.get(PROP_VERSION_CODENAME);
    317                 if (apiCodename != null && apiCodename.equals("REL")) {
    318                     apiCodename = null; // REL means it's a release version and therefore the
    319                                         // codename is irrelevant at this point.
    320                 }
    321 
    322                 // platform rev number
    323                 int revision = 1;
    324                 File sourcePropFile = new File(platform, SdkConstants.FN_SOURCE_PROP);
    325                 Map<String, String> sourceProp = ProjectProperties.parsePropertyFile(sourcePropFile,
    326                         log);
    327                 if (sourceProp != null) {
    328                     try {
    329                         revision = Integer.parseInt(sourceProp.get("Pkg.Revision"));
    330                     } catch (NumberFormatException e) {
    331                         // do nothing, we'll keep the default value of 1.
    332                     }
    333                     map.putAll(sourceProp);
    334                 }
    335 
    336                 // Ant properties
    337                 File sdkPropFile = new File(platform, SdkConstants.FN_SDK_PROP);
    338                 Map<String, String> antProp = ProjectProperties.parsePropertyFile(sdkPropFile, log);
    339                 if (antProp != null) {
    340                     map.putAll(antProp);
    341                 }
    342 
    343                 // api number and name look valid, perform a few more checks
    344                 if (checkPlatformContent(platform, log) == false) {
    345                     return null;
    346                 }
    347                 // create the target.
    348                 PlatformTarget target = new PlatformTarget(
    349                         platform.getAbsolutePath(),
    350                         map,
    351                         apiNumber,
    352                         apiCodename,
    353                         apiName,
    354                         revision);
    355 
    356                 // need to parse the skins.
    357                 String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
    358                 target.setSkins(skins);
    359 
    360                 return target;
    361             }
    362         } else {
    363             log.warning("Ignoring platform '%1$s': %2$s is missing.", platform.getName(),
    364                     SdkConstants.FN_BUILD_PROP);
    365         }
    366 
    367         return null;
    368     }
    369 
    370 
    371     /**
    372      * Loads the Add-on from the SDK.
    373      * @param location Location of the SDK
    374      * @param list the list to fill with the add-ons.
    375      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    376      */
    377     private static void loadAddOns(String location, ArrayList<IAndroidTarget> list, ISdkLog log) {
    378         File addonFolder = new File(location, SdkConstants.FD_ADDONS);
    379         if (addonFolder.isDirectory()) {
    380             File[] addons  = addonFolder.listFiles();
    381 
    382             for (File addon : addons) {
    383                 // Add-ons have to be folders. Ignore files and no need to warn about them.
    384                 if (addon.isDirectory()) {
    385                     AddOnTarget target = loadAddon(addon, list, log);
    386                     if (target != null) {
    387                         list.add(target);
    388                     }
    389                 }
    390             }
    391 
    392             return;
    393         }
    394 
    395         String message = null;
    396         if (addonFolder.exists() == false) {
    397             message = "%s is missing.";
    398         } else {
    399             message = "%s is not a folder.";
    400         }
    401 
    402         throw new IllegalArgumentException(String.format(message,
    403                 addonFolder.getAbsolutePath()));
    404     }
    405 
    406     /**
    407      * Loads a specific Add-on at a given location.
    408      * @param addon the location of the addon.
    409      * @param targetList The list of Android target that were already loaded from the SDK.
    410      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    411      */
    412     private static AddOnTarget loadAddon(File addon, ArrayList<IAndroidTarget> targetList,
    413             ISdkLog log) {
    414         File addOnManifest = new File(addon, SdkConstants.FN_MANIFEST_INI);
    415 
    416         if (addOnManifest.isFile()) {
    417             Map<String, String> propertyMap = ProjectProperties.parsePropertyFile(addOnManifest,
    418                     log);
    419 
    420             if (propertyMap != null) {
    421                 // look for some specific values in the map.
    422                 // we require name, vendor, and api
    423                 String name = propertyMap.get(ADDON_NAME);
    424                 if (name == null) {
    425                     displayAddonManifestWarning(log, addon.getName(), ADDON_NAME);
    426                     return null;
    427                 }
    428 
    429                 String vendor = propertyMap.get(ADDON_VENDOR);
    430                 if (vendor == null) {
    431                     displayAddonManifestWarning(log, addon.getName(), ADDON_VENDOR);
    432                     return null;
    433                 }
    434 
    435                 String api = propertyMap.get(ADDON_API);
    436                 PlatformTarget baseTarget = null;
    437                 if (api == null) {
    438                     displayAddonManifestWarning(log, addon.getName(), ADDON_API);
    439                     return null;
    440                 } else {
    441                     // Look for a platform that has a matching api level or codename.
    442                     for (IAndroidTarget target : targetList) {
    443                         if (target.isPlatform() && target.getVersion().equals(api)) {
    444                             baseTarget = (PlatformTarget)target;
    445                             break;
    446                         }
    447                     }
    448 
    449                     if (baseTarget == null) {
    450                         // Ignore this add-on.
    451                         log.warning(
    452                                 "Ignoring add-on '%1$s': Unable to find base platform with API level '%2$s'",
    453                                 addon.getName(), api);
    454                         return null;
    455                     }
    456                 }
    457 
    458                 // get the optional description
    459                 String description = propertyMap.get(ADDON_DESCRIPTION);
    460 
    461                 // get the add-on revision
    462                 int revisionValue = 1;
    463                 String revision = propertyMap.get(ADDON_REVISION);
    464                 if (revision == null) {
    465                     revision = propertyMap.get(ADDON_REVISION_OLD);
    466                 }
    467                 if (revision != null) {
    468                     try {
    469                         revisionValue = Integer.parseInt(revision);
    470                     } catch (NumberFormatException e) {
    471                         // looks like apiNumber does not parse to a number.
    472                         // Ignore this add-on.
    473                         log.warning(
    474                                 "Ignoring add-on '%1$s': %2$s is not a valid number in %3$s.",
    475                                 addon.getName(), ADDON_REVISION, SdkConstants.FN_BUILD_PROP);
    476                         return null;
    477                     }
    478                 }
    479 
    480                 // get the optional libraries
    481                 String librariesValue = propertyMap.get(ADDON_LIBRARIES);
    482                 Map<String, String[]> libMap = null;
    483 
    484                 if (librariesValue != null) {
    485                     librariesValue = librariesValue.trim();
    486                     if (librariesValue.length() > 0) {
    487                         // split in the string into the libraries name
    488                         String[] libraries = librariesValue.split(";");
    489                         if (libraries.length > 0) {
    490                             libMap = new HashMap<String, String[]>();
    491                             for (String libName : libraries) {
    492                                 libName = libName.trim();
    493 
    494                                 // get the library data from the properties
    495                                 String libData = propertyMap.get(libName);
    496 
    497                                 if (libData != null) {
    498                                     // split the jar file from the description
    499                                     Matcher m = PATTERN_LIB_DATA.matcher(libData);
    500                                     if (m.matches()) {
    501                                         libMap.put(libName, new String[] {
    502                                                 m.group(1), m.group(2) });
    503                                     } else {
    504                                         log.warning(
    505                                                 "Ignoring library '%1$s', property value has wrong format\n\t%2$s",
    506                                                 libName, libData);
    507                                     }
    508                                 } else {
    509                                     log.warning(
    510                                             "Ignoring library '%1$s', missing property value",
    511                                             libName, libData);
    512                                 }
    513                             }
    514                         }
    515                     }
    516                 }
    517 
    518                 AddOnTarget target = new AddOnTarget(addon.getAbsolutePath(), name, vendor,
    519                         revisionValue, description, libMap, baseTarget);
    520 
    521                 // need to parse the skins.
    522                 String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
    523 
    524                 // get the default skin, or take it from the base platform if needed.
    525                 String defaultSkin = propertyMap.get(ADDON_DEFAULT_SKIN);
    526                 if (defaultSkin == null) {
    527                     if (skins.length == 1) {
    528                         defaultSkin = skins[0];
    529                     } else {
    530                         defaultSkin = baseTarget.getDefaultSkin();
    531                     }
    532                 }
    533 
    534                 // get the USB ID (if available)
    535                 int usbVendorId = convertId(propertyMap.get(ADDON_USB_VENDOR));
    536                 if (usbVendorId != IAndroidTarget.NO_USB_ID) {
    537                     target.setUsbVendorId(usbVendorId);
    538                 }
    539 
    540                 target.setSkins(skins, defaultSkin);
    541 
    542                 return target;
    543             }
    544         } else {
    545             log.warning("Ignoring add-on '%1$s': %2$s is missing.", addon.getName(),
    546                     SdkConstants.FN_MANIFEST_INI);
    547         }
    548 
    549         return null;
    550     }
    551 
    552     /**
    553      * Converts a string representation of an hexadecimal ID into an int.
    554      * @param value the string to convert.
    555      * @return the int value, or {@link IAndroidTarget#NO_USB_ID} if the convertion failed.
    556      */
    557     private static int convertId(String value) {
    558         if (value != null && value.length() > 0) {
    559             if (PATTERN_USB_IDS.matcher(value).matches()) {
    560                 String v = value.substring(2);
    561                 try {
    562                     return Integer.parseInt(v, 16);
    563                 } catch (NumberFormatException e) {
    564                     // this shouldn't happen since we check the pattern above, but this is safer.
    565                     // the method will return 0 below.
    566                 }
    567             }
    568         }
    569 
    570         return IAndroidTarget.NO_USB_ID;
    571     }
    572 
    573     /**
    574      * Displays a warning in the log about the addon being ignored due to a missing manifest value.
    575      *
    576      * @param log The logger object. Cannot be null.
    577      * @param addonName The addon name, for display.
    578      * @param valueName The missing manifest value, for display.
    579      */
    580     private static void displayAddonManifestWarning(ISdkLog log, String addonName, String valueName) {
    581         log.warning("Ignoring add-on '%1$s': '%2$s' is missing from %3$s.",
    582                 addonName, valueName, SdkConstants.FN_MANIFEST_INI);
    583     }
    584 
    585     /**
    586      * Checks the given platform has all the required files, and returns true if they are all
    587      * present.
    588      * <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe),
    589      * aidl(.exe), dx(.bat), and dx.jar
    590      *
    591      * @param platform The folder containing the platform.
    592      * @param log Logger. Cannot be null.
    593      */
    594     private static boolean checkPlatformContent(File platform, ISdkLog log) {
    595         for (String relativePath : sPlatformContentList) {
    596             File f = new File(platform, relativePath);
    597             if (!f.exists()) {
    598                 log.warning("Ignoring platform '%1$s': %2$s is missing.",
    599                         platform.getName(), relativePath);
    600                 return false;
    601             }
    602         }
    603         return true;
    604     }
    605 
    606 
    607 
    608     /**
    609      * Parses the skin folder and builds the skin list.
    610      * @param osPath The path of the skin root folder.
    611      */
    612     private static String[] parseSkinFolder(String osPath) {
    613         File skinRootFolder = new File(osPath);
    614 
    615         if (skinRootFolder.isDirectory()) {
    616             ArrayList<String> skinList = new ArrayList<String>();
    617 
    618             File[] files = skinRootFolder.listFiles();
    619 
    620             for (File skinFolder : files) {
    621                 if (skinFolder.isDirectory()) {
    622                     // check for layout file
    623                     File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT);
    624 
    625                     if (layout.isFile()) {
    626                         // for now we don't parse the content of the layout and
    627                         // simply add the directory to the list.
    628                         skinList.add(skinFolder.getName());
    629                     }
    630                 }
    631             }
    632 
    633             return skinList.toArray(new String[skinList.size()]);
    634         }
    635 
    636         return new String[0];
    637     }
    638 
    639     /**
    640      * Loads all samples from the {@link SdkConstants#FD_SAMPLES} directory.
    641      *
    642      * @param log Logger. Cannot be null.
    643      */
    644     private void loadSamples(ISdkLog log) {
    645         File sampleFolder = new File(mSdkLocation, SdkConstants.FD_SAMPLES);
    646         if (sampleFolder.isDirectory()) {
    647             File[] platforms  = sampleFolder.listFiles();
    648 
    649             for (File platform : platforms) {
    650                 if (platform.isDirectory()) {
    651                     // load the source.properties file and get an AndroidVersion object from it.
    652                     AndroidVersion version = getSamplesVersion(platform, log);
    653 
    654                     if (version != null) {
    655                         // locate the platform matching this version
    656                         for (IAndroidTarget target : mTargets) {
    657                             if (target.isPlatform() && target.getVersion().equals(version)) {
    658                                 ((PlatformTarget)target).setSamplesPath(platform.getAbsolutePath());
    659                                 break;
    660                             }
    661                         }
    662                     }
    663                 }
    664             }
    665         }
    666     }
    667 
    668     /**
    669      * Returns the {@link AndroidVersion} of the sample in the given folder.
    670      *
    671      * @param folder The sample's folder.
    672      * @param log Logger for errors. Cannot be null.
    673      * @return An {@link AndroidVersion} or null on error.
    674      */
    675     private AndroidVersion getSamplesVersion(File folder, ISdkLog log) {
    676         File sourceProp = new File(folder, SdkConstants.FN_SOURCE_PROP);
    677         try {
    678             Properties p = new Properties();
    679             p.load(new FileInputStream(sourceProp));
    680 
    681             return new AndroidVersion(p);
    682         } catch (FileNotFoundException e) {
    683             log.warning("Ignoring sample '%1$s': does not contain %2$s.", //$NON-NLS-1$
    684                     folder.getName(), SdkConstants.FN_SOURCE_PROP);
    685         } catch (IOException e) {
    686             log.warning("Ignoring sample '%1$s': failed reading %2$s.", //$NON-NLS-1$
    687                     folder.getName(), SdkConstants.FN_SOURCE_PROP);
    688         } catch (AndroidVersionException e) {
    689             log.warning("Ignoring sample '%1$s': no android version found in %2$s.", //$NON-NLS-1$
    690                     folder.getName(), SdkConstants.FN_SOURCE_PROP);
    691         }
    692 
    693         return null;
    694     }
    695 
    696 }
    697