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.annotations.VisibleForTesting;
     20 import com.android.annotations.VisibleForTesting.Visibility;
     21 import com.android.io.FileWrapper;
     22 import com.android.prefs.AndroidLocation;
     23 import com.android.prefs.AndroidLocation.AndroidLocationException;
     24 import com.android.sdklib.AndroidVersion.AndroidVersionException;
     25 import com.android.sdklib.ISystemImage.LocationType;
     26 import com.android.sdklib.internal.project.ProjectProperties;
     27 import com.android.sdklib.repository.PkgProps;
     28 import com.android.util.Pair;
     29 
     30 import java.io.File;
     31 import java.io.FileInputStream;
     32 import java.io.FileNotFoundException;
     33 import java.io.FileWriter;
     34 import java.io.IOException;
     35 import java.util.ArrayList;
     36 import java.util.Collections;
     37 import java.util.HashMap;
     38 import java.util.HashSet;
     39 import java.util.Map;
     40 import java.util.Properties;
     41 import java.util.Set;
     42 import java.util.TreeSet;
     43 import java.util.regex.Matcher;
     44 import java.util.regex.Pattern;
     45 
     46 /**
     47  * The SDK manager parses the SDK folder and gives access to the content.
     48  * @see PlatformTarget
     49  * @see AddOnTarget
     50  */
     51 public class SdkManager {
     52 
     53     public final static String PROP_VERSION_SDK = "ro.build.version.sdk";              //$NON-NLS-1$
     54     public final static String PROP_VERSION_CODENAME = "ro.build.version.codename";    //$NON-NLS-1$
     55     public final static String PROP_VERSION_RELEASE = "ro.build.version.release";      //$NON-NLS-1$
     56 
     57     public final static String ADDON_NAME = "name";                                    //$NON-NLS-1$
     58     public final static String ADDON_VENDOR = "vendor";                                //$NON-NLS-1$
     59     public final static String ADDON_API = "api";                                      //$NON-NLS-1$
     60     public final static String ADDON_DESCRIPTION = "description";                      //$NON-NLS-1$
     61     public final static String ADDON_LIBRARIES = "libraries";                          //$NON-NLS-1$
     62     public final static String ADDON_DEFAULT_SKIN = "skin";                            //$NON-NLS-1$
     63     public final static String ADDON_USB_VENDOR = "usb-vendor";                        //$NON-NLS-1$
     64     public final static String ADDON_REVISION = "revision";                            //$NON-NLS-1$
     65     public final static String ADDON_REVISION_OLD = "version";                         //$NON-NLS-1$
     66 
     67 
     68     private final static Pattern PATTERN_LIB_DATA = Pattern.compile(
     69             "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE);               //$NON-NLS-1$
     70 
     71      // usb ids are 16-bit hexadecimal values.
     72     private final static Pattern PATTERN_USB_IDS = Pattern.compile(
     73             "^0x[a-f0-9]{4}$", Pattern.CASE_INSENSITIVE);                              //$NON-NLS-1$
     74 
     75     /** List of items in the platform to check when parsing it. These paths are relative to the
     76      * platform root folder. */
     77     private final static String[] sPlatformContentList = new String[] {
     78         SdkConstants.FN_FRAMEWORK_LIBRARY,
     79         SdkConstants.FN_FRAMEWORK_AIDL,
     80     };
     81 
     82     /** Preference file containing the usb ids for adb */
     83     private final static String ADB_INI_FILE = "adb_usb.ini";                          //$NON-NLS-1$
     84        //0--------90--------90--------90--------90--------90--------90--------90--------9
     85     private final static String ADB_INI_HEADER =
     86         "# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.\n" +                   //$NON-NLS-1$
     87         "# USE 'android update adb' TO GENERATE.\n" +                                  //$NON-NLS-1$
     88         "# 1 USB VENDOR ID PER LINE.\n";                                               //$NON-NLS-1$
     89 
     90     /** The location of the SDK as an OS path */
     91     private final String mOsSdkPath;
     92     /** Valid targets that have been loaded. Can be empty but not null. */
     93     private IAndroidTarget[] mTargets = new IAndroidTarget[0];
     94 
     95     public static class LayoutlibVersion implements Comparable<LayoutlibVersion> {
     96         private final int mApi;
     97         private final int mRevision;
     98 
     99         public static final int NOT_SPECIFIED = 0;
    100 
    101         public LayoutlibVersion(int api, int revision) {
    102             mApi = api;
    103             mRevision = revision;
    104         }
    105 
    106         public int getApi() {
    107             return mApi;
    108         }
    109 
    110         public int getRevision() {
    111             return mRevision;
    112         }
    113 
    114         public int compareTo(LayoutlibVersion rhs) {
    115             boolean useRev = this.mRevision > NOT_SPECIFIED && rhs.mRevision > NOT_SPECIFIED;
    116             int lhsValue = (this.mApi << 16) + (useRev ? this.mRevision : 0);
    117             int rhsValue = (rhs.mApi  << 16) + (useRev ? rhs.mRevision  : 0);
    118             return lhsValue - rhsValue;
    119         }
    120     }
    121 
    122     /**
    123      * Create a new {@link SdkManager} instance.
    124      * External users should use {@link #createManager(String, ISdkLog)}.
    125      *
    126      * @param osSdkPath the location of the SDK.
    127      */
    128     @VisibleForTesting(visibility=Visibility.PRIVATE)
    129     protected SdkManager(String osSdkPath) {
    130         mOsSdkPath = osSdkPath;
    131     }
    132 
    133     /**
    134      * Creates an {@link SdkManager} for a given sdk location.
    135      * @param osSdkPath the location of the SDK.
    136      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    137      * @return the created {@link SdkManager} or null if the location is not valid.
    138      */
    139     public static SdkManager createManager(String osSdkPath, ISdkLog log) {
    140         try {
    141             SdkManager manager = new SdkManager(osSdkPath);
    142             ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
    143             loadPlatforms(osSdkPath, list, log);
    144             loadAddOns(osSdkPath, list, log);
    145 
    146             // sort the targets/add-ons
    147             Collections.sort(list);
    148 
    149             manager.setTargets(list.toArray(new IAndroidTarget[list.size()]));
    150 
    151             // load the samples, after the targets have been set.
    152             manager.loadSamples(log);
    153 
    154             return manager;
    155         } catch (IllegalArgumentException e) {
    156             log.error(e, "Error parsing the sdk.");
    157         }
    158 
    159         return null;
    160     }
    161 
    162     /**
    163      * Returns the location of the SDK.
    164      */
    165     public String getLocation() {
    166         return mOsSdkPath;
    167     }
    168 
    169     /**
    170      * Returns the targets that are available in the SDK.
    171      * <p/>
    172      * The array can be empty but not null.
    173      */
    174     public IAndroidTarget[] getTargets() {
    175         return mTargets;
    176     }
    177 
    178     /**
    179      * Sets the targets that are available in the SDK.
    180      * <p/>
    181      * The array can be empty but not null.
    182      */
    183     @VisibleForTesting(visibility=Visibility.PRIVATE)
    184     protected void setTargets(IAndroidTarget[] targets) {
    185         assert targets != null;
    186         mTargets = targets;
    187     }
    188 
    189     /**
    190      * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
    191      *
    192      * @param hash the {@link IAndroidTarget} hash string.
    193      * @return The matching {@link IAndroidTarget} or null.
    194      */
    195     public IAndroidTarget getTargetFromHashString(String hash) {
    196         if (hash != null) {
    197             for (IAndroidTarget target : mTargets) {
    198                 if (hash.equals(target.hashString())) {
    199                     return target;
    200                 }
    201             }
    202         }
    203 
    204         return null;
    205     }
    206 
    207     /**
    208      * Updates adb with the USB devices declared in the SDK add-ons.
    209      * @throws AndroidLocationException
    210      * @throws IOException
    211      */
    212     public void updateAdb() throws AndroidLocationException, IOException {
    213         FileWriter writer = null;
    214         try {
    215             // get the android prefs location to know where to write the file.
    216             File adbIni = new File(AndroidLocation.getFolder(), ADB_INI_FILE);
    217             writer = new FileWriter(adbIni);
    218 
    219             // first, put all the vendor id in an HashSet to remove duplicate.
    220             HashSet<Integer> set = new HashSet<Integer>();
    221             IAndroidTarget[] targets = getTargets();
    222             for (IAndroidTarget target : targets) {
    223                 if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) {
    224                     set.add(target.getUsbVendorId());
    225                 }
    226             }
    227 
    228             // write file header.
    229             writer.write(ADB_INI_HEADER);
    230 
    231             // now write the Id in a text file, one per line.
    232             for (Integer i : set) {
    233                 writer.write(String.format("0x%04x\n", i));                            //$NON-NLS-1$
    234             }
    235         } finally {
    236             if (writer != null) {
    237                 writer.close();
    238             }
    239         }
    240     }
    241 
    242     /**
    243      * Reloads the content of the SDK.
    244      *
    245      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    246      */
    247     public void reloadSdk(ISdkLog log) {
    248         // get the current target list.
    249         ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
    250         loadPlatforms(mOsSdkPath, list, log);
    251         loadAddOns(mOsSdkPath, list, log);
    252 
    253         // For now replace the old list with the new one.
    254         // In the future we may want to keep the current objects, so that ADT doesn't have to deal
    255         // with new IAndroidTarget objects when a target didn't actually change.
    256 
    257         // sort the targets/add-ons
    258         Collections.sort(list);
    259         setTargets(list.toArray(new IAndroidTarget[list.size()]));
    260 
    261         // load the samples, after the targets have been set.
    262         loadSamples(log);
    263     }
    264 
    265     /**
    266      * Returns the greatest {@link LayoutlibVersion} found amongst all platform
    267      * targets currently loaded in the SDK.
    268      * <p/>
    269      * We only started recording Layoutlib Versions recently in the platform meta data
    270      * so it's possible to have an SDK with many platforms loaded but no layoutlib
    271      * version defined.
    272      *
    273      * @return The greatest {@link LayoutlibVersion} or null if none is found.
    274      * @deprecated This does NOT solve the right problem and will be changed in Tools R15.
    275      */
    276     @Deprecated
    277     public LayoutlibVersion getMaxLayoutlibVersion() {
    278         LayoutlibVersion maxVersion = null;
    279 
    280         for (IAndroidTarget target : getTargets()) {
    281             if (target instanceof PlatformTarget) {
    282                 LayoutlibVersion lv = ((PlatformTarget) target).getLayoutlibVersion();
    283                 if (lv != null) {
    284                     if (maxVersion == null || lv.compareTo(maxVersion) > 0) {
    285                         maxVersion = lv;
    286                     }
    287                 }
    288             }
    289         }
    290 
    291         return maxVersion;
    292     }
    293 
    294     /**
    295      * Loads the Platforms from the SDK.
    296      * Creates the "platforms" folder if necessary.
    297      *
    298      * @param sdkOsPath Location of the SDK
    299      * @param list the list to fill with the platforms.
    300      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    301      * @throws RuntimeException when the "platforms" folder is missing and cannot be created.
    302      */
    303     private static void loadPlatforms(String sdkOsPath, ArrayList<IAndroidTarget> list,
    304             ISdkLog log) {
    305         File platformFolder = new File(sdkOsPath, SdkConstants.FD_PLATFORMS);
    306 
    307         if (platformFolder.isDirectory()) {
    308             File[] platforms  = platformFolder.listFiles();
    309 
    310             for (File platform : platforms) {
    311                 if (platform.isDirectory()) {
    312                     PlatformTarget target = loadPlatform(sdkOsPath, platform, log);
    313                     if (target != null) {
    314                         list.add(target);
    315                     }
    316                 } else {
    317                     log.warning("Ignoring platform '%1$s', not a folder.", platform.getName());
    318                 }
    319             }
    320 
    321             return;
    322         }
    323 
    324         // Try to create it or complain if something else is in the way.
    325         if (!platformFolder.exists()) {
    326             if (!platformFolder.mkdir()) {
    327                 throw new RuntimeException(
    328                         String.format("Failed to create %1$s.",
    329                                 platformFolder.getAbsolutePath()));
    330             }
    331         } else {
    332             throw new RuntimeException(
    333                     String.format("%1$s is not a folder.",
    334                             platformFolder.getAbsolutePath()));
    335         }
    336     }
    337 
    338     /**
    339      * Loads a specific Platform at a given location.
    340      * @param sdkOsPath Location of the SDK
    341      * @param platformFolder the root folder of the platform.
    342      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    343      */
    344     private static PlatformTarget loadPlatform(String sdkOsPath, File platformFolder,
    345             ISdkLog log) {
    346         FileWrapper buildProp = new FileWrapper(platformFolder, SdkConstants.FN_BUILD_PROP);
    347         FileWrapper sourcePropFile = new FileWrapper(platformFolder, SdkConstants.FN_SOURCE_PROP);
    348 
    349         if (buildProp.isFile() && sourcePropFile.isFile()) {
    350             Map<String, String> platformProp = new HashMap<String, String>();
    351 
    352             // add all the property files
    353             Map<String, String> map = ProjectProperties.parsePropertyFile(buildProp, log);
    354             if (map != null) {
    355                 platformProp.putAll(map);
    356             }
    357 
    358             map = ProjectProperties.parsePropertyFile(sourcePropFile, log);
    359             if (map != null) {
    360                 platformProp.putAll(map);
    361             }
    362 
    363             FileWrapper sdkPropFile = new FileWrapper(platformFolder, SdkConstants.FN_SDK_PROP);
    364             if (sdkPropFile.isFile()) { // obsolete platforms don't have this.
    365                 map = ProjectProperties.parsePropertyFile(sdkPropFile, log);
    366                 if (map != null) {
    367                     platformProp.putAll(map);
    368                 }
    369             }
    370 
    371             // look for some specific values in the map.
    372 
    373             // api level
    374             int apiNumber;
    375             String stringValue = platformProp.get(PROP_VERSION_SDK);
    376             if (stringValue == null) {
    377                 log.warning(
    378                         "Ignoring platform '%1$s': %2$s is missing from '%3$s'",
    379                         platformFolder.getName(), PROP_VERSION_SDK,
    380                         SdkConstants.FN_BUILD_PROP);
    381                 return null;
    382             } else {
    383                 try {
    384                      apiNumber = Integer.parseInt(stringValue);
    385                 } catch (NumberFormatException e) {
    386                     // looks like apiNumber does not parse to a number.
    387                     // Ignore this platform.
    388                     log.warning(
    389                             "Ignoring platform '%1$s': %2$s is not a valid number in %3$s.",
    390                             platformFolder.getName(), PROP_VERSION_SDK,
    391                             SdkConstants.FN_BUILD_PROP);
    392                     return null;
    393                 }
    394             }
    395 
    396             // codename (optional)
    397             String apiCodename = platformProp.get(PROP_VERSION_CODENAME);
    398             if (apiCodename != null && apiCodename.equals("REL")) {
    399                 apiCodename = null; // REL means it's a release version and therefore the
    400                                     // codename is irrelevant at this point.
    401             }
    402 
    403             // version string
    404             String apiName = platformProp.get(PkgProps.PLATFORM_VERSION);
    405             if (apiName == null) {
    406                 apiName = platformProp.get(PROP_VERSION_RELEASE);
    407             }
    408             if (apiName == null) {
    409                 log.warning(
    410                         "Ignoring platform '%1$s': %2$s is missing from '%3$s'",
    411                         platformFolder.getName(), PROP_VERSION_RELEASE,
    412                         SdkConstants.FN_BUILD_PROP);
    413                 return null;
    414             }
    415 
    416             // platform rev number & layoutlib version are extracted from the source.properties
    417             // saved by the SDK Manager when installing the package.
    418 
    419             int revision = 1;
    420             LayoutlibVersion layoutlibVersion = null;
    421             try {
    422                 revision = Integer.parseInt(platformProp.get(PkgProps.PKG_REVISION));
    423             } catch (NumberFormatException e) {
    424                 // do nothing, we'll keep the default value of 1.
    425             }
    426 
    427             try {
    428                 String propApi = platformProp.get(PkgProps.LAYOUTLIB_API);
    429                 String propRev = platformProp.get(PkgProps.LAYOUTLIB_REV);
    430                 int llApi = propApi == null ? LayoutlibVersion.NOT_SPECIFIED :
    431                                               Integer.parseInt(propApi);
    432                 int llRev = propRev == null ? LayoutlibVersion.NOT_SPECIFIED :
    433                                               Integer.parseInt(propRev);
    434                 if (llApi > LayoutlibVersion.NOT_SPECIFIED &&
    435                         llRev >= LayoutlibVersion.NOT_SPECIFIED) {
    436                     layoutlibVersion = new LayoutlibVersion(llApi, llRev);
    437                 }
    438             } catch (NumberFormatException e) {
    439                 // do nothing, we'll ignore the layoutlib version if it's invalid
    440             }
    441 
    442             // api number and name look valid, perform a few more checks
    443             if (checkPlatformContent(platformFolder, log) == false) {
    444                 return null;
    445             }
    446 
    447             ISystemImage[] systemImages =
    448                 getPlatformSystemImages(sdkOsPath, platformFolder, apiNumber, apiCodename);
    449 
    450             // create the target.
    451             PlatformTarget target = new PlatformTarget(
    452                     sdkOsPath,
    453                     platformFolder.getAbsolutePath(),
    454                     apiNumber,
    455                     apiCodename,
    456                     apiName,
    457                     revision,
    458                     layoutlibVersion,
    459                     systemImages,
    460                     platformProp);
    461 
    462             // need to parse the skins.
    463             String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
    464             target.setSkins(skins);
    465 
    466             return target;
    467         } else {
    468             log.warning("Ignoring platform '%1$s': %2$s is missing.",   //$NON-NLS-1$
    469                     platformFolder.getName(),
    470                     SdkConstants.FN_BUILD_PROP);
    471         }
    472 
    473         return null;
    474     }
    475 
    476     /**
    477      * Get all the system images supported by an add-on target.
    478      * For an add-on, we first look for sub-folders in the addon/images directory.
    479      * If none are found but the directory exists and is not empty, assume it's a legacy
    480      * arm eabi system image.
    481      * <p/>
    482      * Note that it's OK for an add-on to have no system-images at all, since it can always
    483      * rely on the ones from its base platform.
    484      *
    485      * @param root Root of the add-on target being loaded.
    486      * @return an array of ISystemImage containing all the system images for the target.
    487      *              The list can be empty.
    488     */
    489     private static ISystemImage[] getAddonSystemImages(File root) {
    490         Set<ISystemImage> found = new TreeSet<ISystemImage>();
    491 
    492         root = new File(root, SdkConstants.OS_IMAGES_FOLDER);
    493         File[] files = root.listFiles();
    494         boolean hasImgFiles = false;
    495 
    496         if (files != null) {
    497             // Look for sub-directories
    498             for (File file : files) {
    499                 if (file.isDirectory()) {
    500                     found.add(new SystemImage(
    501                             file,
    502                             LocationType.IN_PLATFORM_SUBFOLDER,
    503                             file.getName()));
    504                 } else if (!hasImgFiles && file.isFile()) {
    505                     if (file.getName().endsWith(".img")) {      //$NON-NLS-1$
    506                         hasImgFiles = true;
    507                     }
    508                 }
    509             }
    510         }
    511 
    512         if (found.size() == 0 && hasImgFiles && root.isDirectory()) {
    513             // We found no sub-folder system images but it looks like the top directory
    514             // has some img files in it. It must be a legacy ARM EABI system image folder.
    515             found.add(new SystemImage(
    516                     root,
    517                     LocationType.IN_PLATFORM_LEGACY,
    518                     SdkConstants.ABI_ARMEABI));
    519         }
    520 
    521         return found.toArray(new ISystemImage[found.size()]);
    522     }
    523 
    524     /**
    525      * Get all the system images supported by a platform target.
    526      * For a platform, we first look in the new sdk/system-images folders then we
    527      * look for sub-folders in the platform/images directory and/or the one legacy
    528      * folder.
    529      * If any given API appears twice or more, the first occurrence wins.
    530      *
    531      * @param sdkOsPath The path to the SDK.
    532      * @param root Root of the platform target being loaded.
    533      * @param apiNumber API level of platform being loaded
    534      * @param apiCodename Optional codename of platform being loaded
    535      * @return an array of ISystemImage containing all the system images for the target.
    536      *              The list can be empty.
    537     */
    538     private static ISystemImage[] getPlatformSystemImages(
    539             String sdkOsPath,
    540             File root,
    541             int apiNumber,
    542             String apiCodename) {
    543         Set<ISystemImage> found = new TreeSet<ISystemImage>();
    544         Set<String> abiFound = new HashSet<String>();
    545 
    546         // First look in the SDK/system-image/platform-n/abi folders.
    547         // We require/enforce the system image to have a valid properties file.
    548         // The actual directory names are irrelevant.
    549         // If we find multiple occurrences of the same platform/abi, the first one read wins.
    550 
    551         AndroidVersion version = new AndroidVersion(apiNumber, apiCodename);
    552 
    553         File[] firstLevelFiles = new File(sdkOsPath, SdkConstants.FD_SYSTEM_IMAGES).listFiles();
    554         if (firstLevelFiles != null) {
    555             for (File firstLevel : firstLevelFiles) {
    556                 File[] secondLevelFiles = firstLevel.listFiles();
    557                 if (secondLevelFiles != null) {
    558                     for (File secondLevel : secondLevelFiles) {
    559                         try {
    560                             File propFile = new File(secondLevel, SdkConstants.FN_SOURCE_PROP);
    561                             Properties props = new Properties();
    562                             FileInputStream fis = null;
    563                             try {
    564                                 fis = new FileInputStream(propFile);
    565                                 props.load(fis);
    566                             } finally {
    567                                 if (fis != null) {
    568                                     fis.close();
    569                                 }
    570                             }
    571 
    572                             AndroidVersion propsVersion = new AndroidVersion(props);
    573                             if (!propsVersion.equals(version)) {
    574                                 continue;
    575                             }
    576 
    577                             String abi = props.getProperty(PkgProps.SYS_IMG_ABI);
    578                             if (abi != null && !abiFound.contains(abi)) {
    579                                 found.add(new SystemImage(
    580                                         secondLevel,
    581                                         LocationType.IN_SYSTEM_IMAGE,
    582                                         abi));
    583                                 abiFound.add(abi);
    584                             }
    585                         } catch (Exception ignore) {
    586                         }
    587                     }
    588                 }
    589             }
    590         }
    591 
    592         // Then look in either the platform/images/abi or the legacy folder
    593         root = new File(root, SdkConstants.OS_IMAGES_FOLDER);
    594         File[] files = root.listFiles();
    595         boolean useLegacy = true;
    596         boolean hasImgFiles = false;
    597 
    598         if (files != null) {
    599             // Look for sub-directories
    600             for (File file : files) {
    601                 if (file.isDirectory()) {
    602                     useLegacy = false;
    603                     String abi = file.getName();
    604                     if (!abiFound.contains(abi)) {
    605                         found.add(new SystemImage(
    606                                 file,
    607                                 LocationType.IN_PLATFORM_SUBFOLDER,
    608                                 abi));
    609                         abiFound.add(abi);
    610                     }
    611                 } else if (!hasImgFiles && file.isFile()) {
    612                     if (file.getName().endsWith(".img")) {      //$NON-NLS-1$
    613                         hasImgFiles = true;
    614                     }
    615                 }
    616             }
    617         }
    618 
    619         if (useLegacy && hasImgFiles && root.isDirectory() &&
    620                 !abiFound.contains(SdkConstants.ABI_ARMEABI)) {
    621             // We found no sub-folder system images but it looks like the top directory
    622             // has some img files in it. It must be a legacy ARM EABI system image folder.
    623             found.add(new SystemImage(
    624                     root,
    625                     LocationType.IN_PLATFORM_LEGACY,
    626                     SdkConstants.ABI_ARMEABI));
    627         }
    628 
    629         return found.toArray(new ISystemImage[found.size()]);
    630     }
    631 
    632     /**
    633      * Loads the Add-on from the SDK.
    634      * Creates the "add-ons" folder if necessary.
    635      *
    636      * @param osSdkPath Location of the SDK
    637      * @param list the list to fill with the add-ons.
    638      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    639      * @throws RuntimeException when the "add-ons" folder is missing and cannot be created.
    640      */
    641     private static void loadAddOns(String osSdkPath, ArrayList<IAndroidTarget> list, ISdkLog log) {
    642         File addonFolder = new File(osSdkPath, SdkConstants.FD_ADDONS);
    643 
    644         if (addonFolder.isDirectory()) {
    645             File[] addons  = addonFolder.listFiles();
    646 
    647             IAndroidTarget[] targetList = list.toArray(new IAndroidTarget[list.size()]);
    648 
    649             for (File addon : addons) {
    650                 // Add-ons have to be folders. Ignore files and no need to warn about them.
    651                 if (addon.isDirectory()) {
    652                     AddOnTarget target = loadAddon(addon, targetList, log);
    653                     if (target != null) {
    654                         list.add(target);
    655                     }
    656                 }
    657             }
    658 
    659             return;
    660         }
    661 
    662         // Try to create it or complain if something else is in the way.
    663         if (!addonFolder.exists()) {
    664             if (!addonFolder.mkdir()) {
    665                 throw new RuntimeException(
    666                         String.format("Failed to create %1$s.",
    667                                 addonFolder.getAbsolutePath()));
    668             }
    669         } else {
    670             throw new RuntimeException(
    671                     String.format("%1$s is not a folder.",
    672                             addonFolder.getAbsolutePath()));
    673         }
    674     }
    675 
    676     /**
    677      * Loads a specific Add-on at a given location.
    678      * @param addonDir the location of the add-on directory.
    679      * @param targetList The list of Android target that were already loaded from the SDK.
    680      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    681      */
    682     private static AddOnTarget loadAddon(File addonDir,
    683             IAndroidTarget[] targetList,
    684             ISdkLog log) {
    685 
    686         // Parse the addon properties to ensure we can load it.
    687         Pair<Map<String, String>, String> infos = parseAddonProperties(addonDir, targetList, log);
    688 
    689         Map<String, String> propertyMap = infos.getFirst();
    690         String error = infos.getSecond();
    691 
    692         if (error != null) {
    693             log.warning("Ignoring add-on '%1$s': %2$s", addonDir.getName(), error);
    694             return null;
    695         }
    696 
    697         // Since error==null we're not supposed to encounter any issues loading this add-on.
    698         try {
    699             assert propertyMap != null;
    700 
    701             String api = propertyMap.get(ADDON_API);
    702             String name = propertyMap.get(ADDON_NAME);
    703             String vendor = propertyMap.get(ADDON_VENDOR);
    704 
    705             assert api != null;
    706             assert name != null;
    707             assert vendor != null;
    708 
    709             PlatformTarget baseTarget = null;
    710 
    711             // Look for a platform that has a matching api level or codename.
    712             for (IAndroidTarget target : targetList) {
    713                 if (target.isPlatform() && target.getVersion().equals(api)) {
    714                     baseTarget = (PlatformTarget)target;
    715                     break;
    716                 }
    717             }
    718 
    719             assert baseTarget != null;
    720 
    721             // get the optional description
    722             String description = propertyMap.get(ADDON_DESCRIPTION);
    723 
    724             // get the add-on revision
    725             int revisionValue = 1;
    726             String revision = propertyMap.get(ADDON_REVISION);
    727             if (revision == null) {
    728                 revision = propertyMap.get(ADDON_REVISION_OLD);
    729             }
    730             if (revision != null) {
    731                 revisionValue = Integer.parseInt(revision);
    732             }
    733 
    734             // get the optional libraries
    735             String librariesValue = propertyMap.get(ADDON_LIBRARIES);
    736             Map<String, String[]> libMap = null;
    737 
    738             if (librariesValue != null) {
    739                 librariesValue = librariesValue.trim();
    740                 if (librariesValue.length() > 0) {
    741                     // split in the string into the libraries name
    742                     String[] libraries = librariesValue.split(";");                    //$NON-NLS-1$
    743                     if (libraries.length > 0) {
    744                         libMap = new HashMap<String, String[]>();
    745                         for (String libName : libraries) {
    746                             libName = libName.trim();
    747 
    748                             // get the library data from the properties
    749                             String libData = propertyMap.get(libName);
    750 
    751                             if (libData != null) {
    752                                 // split the jar file from the description
    753                                 Matcher m = PATTERN_LIB_DATA.matcher(libData);
    754                                 if (m.matches()) {
    755                                     libMap.put(libName, new String[] {
    756                                             m.group(1), m.group(2) });
    757                                 } else {
    758                                     log.warning(
    759                                             "Ignoring library '%1$s', property value has wrong format\n\t%2$s",
    760                                             libName, libData);
    761                                 }
    762                             } else {
    763                                 log.warning(
    764                                         "Ignoring library '%1$s', missing property value",
    765                                         libName, libData);
    766                             }
    767                         }
    768                     }
    769                 }
    770             }
    771 
    772             // get the abi list.
    773             ISystemImage[] systemImages = getAddonSystemImages(addonDir);
    774 
    775             // check whether the add-on provides its own rendering info/library.
    776             boolean hasRenderingLibrary = false;
    777             boolean hasRenderingResources = false;
    778 
    779             File dataFolder = new File(addonDir, SdkConstants.FD_DATA);
    780             if (dataFolder.isDirectory()) {
    781                 hasRenderingLibrary = new File(dataFolder, SdkConstants.FN_LAYOUTLIB_JAR).isFile();
    782                 hasRenderingResources = new File(dataFolder, SdkConstants.FD_RES).isDirectory() &&
    783                         new File(dataFolder, SdkConstants.FD_FONTS).isDirectory();
    784             }
    785 
    786             AddOnTarget target = new AddOnTarget(addonDir.getAbsolutePath(), name, vendor,
    787                     revisionValue, description, systemImages, libMap,
    788                     hasRenderingLibrary, hasRenderingResources,baseTarget);
    789 
    790             // need to parse the skins.
    791             String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
    792 
    793             // get the default skin, or take it from the base platform if needed.
    794             String defaultSkin = propertyMap.get(ADDON_DEFAULT_SKIN);
    795             if (defaultSkin == null) {
    796                 if (skins.length == 1) {
    797                     defaultSkin = skins[0];
    798                 } else {
    799                     defaultSkin = baseTarget.getDefaultSkin();
    800                 }
    801             }
    802 
    803             // get the USB ID (if available)
    804             int usbVendorId = convertId(propertyMap.get(ADDON_USB_VENDOR));
    805             if (usbVendorId != IAndroidTarget.NO_USB_ID) {
    806                 target.setUsbVendorId(usbVendorId);
    807             }
    808 
    809             target.setSkins(skins, defaultSkin);
    810 
    811             return target;
    812         }
    813         catch (Exception e) {
    814             log.warning("Ignoring add-on '%1$s': error %2$s.",
    815                     addonDir.getName(), e.toString());
    816         }
    817 
    818         return null;
    819     }
    820 
    821     /**
    822      * Parses the add-on properties and decodes any error that occurs when loading an addon.
    823      *
    824      * @param addonDir the location of the addon directory.
    825      * @param targetList The list of Android target that were already loaded from the SDK.
    826      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
    827      * @return A pair with the property map and an error string. Both can be null but not at the
    828      *  same time. If a non-null error is present then the property map must be ignored. The error
    829      *  should be translatable as it might show up in the SdkManager UI.
    830      */
    831     public static Pair<Map<String, String>, String> parseAddonProperties(
    832             File addonDir,
    833             IAndroidTarget[] targetList,
    834             ISdkLog log) {
    835         Map<String, String> propertyMap = null;
    836         String error = null;
    837 
    838         FileWrapper addOnManifest = new FileWrapper(addonDir, SdkConstants.FN_MANIFEST_INI);
    839 
    840         do {
    841             if (!addOnManifest.isFile()) {
    842                 error = String.format("File not found: %1$s", SdkConstants.FN_MANIFEST_INI);
    843                 break;
    844             }
    845 
    846             propertyMap = ProjectProperties.parsePropertyFile(addOnManifest, log);
    847             if (propertyMap == null) {
    848                 error = String.format("Failed to parse properties from %1$s",
    849                         SdkConstants.FN_MANIFEST_INI);
    850                 break;
    851             }
    852 
    853             // look for some specific values in the map.
    854             // we require name, vendor, and api
    855             String name = propertyMap.get(ADDON_NAME);
    856             if (name == null) {
    857                 error = addonManifestWarning(ADDON_NAME);
    858                 break;
    859             }
    860 
    861             String vendor = propertyMap.get(ADDON_VENDOR);
    862             if (vendor == null) {
    863                 error = addonManifestWarning(ADDON_VENDOR);
    864                 break;
    865             }
    866 
    867             String api = propertyMap.get(ADDON_API);
    868             PlatformTarget baseTarget = null;
    869             if (api == null) {
    870                 error = addonManifestWarning(ADDON_API);
    871                 break;
    872             }
    873 
    874             // Look for a platform that has a matching api level or codename.
    875             for (IAndroidTarget target : targetList) {
    876                 if (target.isPlatform() && target.getVersion().equals(api)) {
    877                     baseTarget = (PlatformTarget)target;
    878                     break;
    879                 }
    880             }
    881 
    882             if (baseTarget == null) {
    883                 error = String.format("Unable to find base platform with API level '%1$s'", api);
    884                 break;
    885             }
    886 
    887             // get the add-on revision
    888             String revision = propertyMap.get(ADDON_REVISION);
    889             if (revision == null) {
    890                 revision = propertyMap.get(ADDON_REVISION_OLD);
    891             }
    892             if (revision != null) {
    893                 try {
    894                     Integer.parseInt(revision);
    895                 } catch (NumberFormatException e) {
    896                     // looks like revision does not parse to a number.
    897                     error = String.format("%1$s is not a valid number in %2$s.",
    898                                 ADDON_REVISION, SdkConstants.FN_BUILD_PROP);
    899                     break;
    900                 }
    901             }
    902 
    903         } while(false);
    904 
    905         return Pair.of(propertyMap, error);
    906     }
    907 
    908     /**
    909      * Converts a string representation of an hexadecimal ID into an int.
    910      * @param value the string to convert.
    911      * @return the int value, or {@link IAndroidTarget#NO_USB_ID} if the convertion failed.
    912      */
    913     private static int convertId(String value) {
    914         if (value != null && value.length() > 0) {
    915             if (PATTERN_USB_IDS.matcher(value).matches()) {
    916                 String v = value.substring(2);
    917                 try {
    918                     return Integer.parseInt(v, 16);
    919                 } catch (NumberFormatException e) {
    920                     // this shouldn't happen since we check the pattern above, but this is safer.
    921                     // the method will return 0 below.
    922                 }
    923             }
    924         }
    925 
    926         return IAndroidTarget.NO_USB_ID;
    927     }
    928 
    929     /**
    930      * Prepares a warning about the addon being ignored due to a missing manifest value.
    931      * This string will show up in the SdkManager UI.
    932      *
    933      * @param valueName The missing manifest value, for display.
    934      */
    935     private static String addonManifestWarning(String valueName) {
    936         return String.format("'%1$s' is missing from %2$s.",
    937                 valueName, SdkConstants.FN_MANIFEST_INI);
    938     }
    939 
    940     /**
    941      * Checks the given platform has all the required files, and returns true if they are all
    942      * present.
    943      * <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe),
    944      * aidl(.exe), dx(.bat), and dx.jar
    945      *
    946      * @param platform The folder containing the platform.
    947      * @param log Logger. Cannot be null.
    948      */
    949     private static boolean checkPlatformContent(File platform, ISdkLog log) {
    950         for (String relativePath : sPlatformContentList) {
    951             File f = new File(platform, relativePath);
    952             if (!f.exists()) {
    953                 log.warning(
    954                         "Ignoring platform '%1$s': %2$s is missing.",                  //$NON-NLS-1$
    955                         platform.getName(), relativePath);
    956                 return false;
    957             }
    958         }
    959         return true;
    960     }
    961 
    962 
    963 
    964     /**
    965      * Parses the skin folder and builds the skin list.
    966      * @param osPath The path of the skin root folder.
    967      */
    968     private static String[] parseSkinFolder(String osPath) {
    969         File skinRootFolder = new File(osPath);
    970 
    971         if (skinRootFolder.isDirectory()) {
    972             ArrayList<String> skinList = new ArrayList<String>();
    973 
    974             File[] files = skinRootFolder.listFiles();
    975 
    976             for (File skinFolder : files) {
    977                 if (skinFolder.isDirectory()) {
    978                     // check for layout file
    979                     File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT);
    980 
    981                     if (layout.isFile()) {
    982                         // for now we don't parse the content of the layout and
    983                         // simply add the directory to the list.
    984                         skinList.add(skinFolder.getName());
    985                     }
    986                 }
    987             }
    988 
    989             return skinList.toArray(new String[skinList.size()]);
    990         }
    991 
    992         return new String[0];
    993     }
    994 
    995     /**
    996      * Loads all samples from the {@link SdkConstants#FD_SAMPLES} directory.
    997      *
    998      * @param log Logger. Cannot be null.
    999      */
   1000     private void loadSamples(ISdkLog log) {
   1001         File sampleFolder = new File(mOsSdkPath, SdkConstants.FD_SAMPLES);
   1002         if (sampleFolder.isDirectory()) {
   1003             File[] platforms  = sampleFolder.listFiles();
   1004 
   1005             for (File platform : platforms) {
   1006                 if (platform.isDirectory()) {
   1007                     // load the source.properties file and get an AndroidVersion object from it.
   1008                     AndroidVersion version = getSamplesVersion(platform, log);
   1009 
   1010                     if (version != null) {
   1011                         // locate the platform matching this version
   1012                         for (IAndroidTarget target : mTargets) {
   1013                             if (target.isPlatform() && target.getVersion().equals(version)) {
   1014                                 ((PlatformTarget)target).setSamplesPath(platform.getAbsolutePath());
   1015                                 break;
   1016                             }
   1017                         }
   1018                     }
   1019                 }
   1020             }
   1021         }
   1022     }
   1023 
   1024     /**
   1025      * Returns the {@link AndroidVersion} of the sample in the given folder.
   1026      *
   1027      * @param folder The sample's folder.
   1028      * @param log Logger for errors. Cannot be null.
   1029      * @return An {@link AndroidVersion} or null on error.
   1030      */
   1031     private AndroidVersion getSamplesVersion(File folder, ISdkLog log) {
   1032         File sourceProp = new File(folder, SdkConstants.FN_SOURCE_PROP);
   1033         try {
   1034             Properties p = new Properties();
   1035             FileInputStream fis = null;
   1036             try {
   1037                 fis = new FileInputStream(sourceProp);
   1038                 p.load(fis);
   1039             } finally {
   1040                 if (fis != null) {
   1041                     fis.close();
   1042                 }
   1043             }
   1044 
   1045             return new AndroidVersion(p);
   1046         } catch (FileNotFoundException e) {
   1047             log.warning("Ignoring sample '%1$s': does not contain %2$s.",              //$NON-NLS-1$
   1048                     folder.getName(), SdkConstants.FN_SOURCE_PROP);
   1049         } catch (IOException e) {
   1050             log.warning("Ignoring sample '%1$s': failed reading %2$s.",                //$NON-NLS-1$
   1051                     folder.getName(), SdkConstants.FN_SOURCE_PROP);
   1052         } catch (AndroidVersionException e) {
   1053             log.warning("Ignoring sample '%1$s': no android version found in %2$s.",   //$NON-NLS-1$
   1054                     folder.getName(), SdkConstants.FN_SOURCE_PROP);
   1055         }
   1056 
   1057         return null;
   1058     }
   1059 
   1060 }
   1061