OSDN Git Service

Load standalone system images in the SdkManager.
authorRaphael <raphael@google.com>
Wed, 7 Sep 2011 18:32:31 +0000 (11:32 -0700)
committerRaphael <raphael@google.com>
Thu, 8 Sep 2011 16:18:52 +0000 (09:18 -0700)
This revamps the way system images are handled
in the internal SdkManager class.

Before, a given IAndroidTarget could provide a list
of ABI strings it new about, discovered by parsing
the SDK/platform/images/xyz or SDK/addon/images/xyz
folder.

This introduces the notion of System Image to an
IAndroidTarget. A system image combines an ABI with
a location strategy (legacy images folder, images
sub-folder or standalone sdk/system-images folder)
and an actual location path.

Change-Id: If5b748aa9aef6788bc3c814818381c7918b40bca

18 files changed:
sdkmanager/app/src/com/android/sdkmanager/Main.java
sdkmanager/app/tests/com/android/sdkmanager/MainTest.java
sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java
sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java
sdkmanager/libs/sdklib/src/com/android/sdklib/ISystemImage.java [new file with mode: 0755]
sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
sdkmanager/libs/sdklib/src/com/android/sdklib/SystemImage.java [new file with mode: 0755]
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java
sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SystemImagePackage.java
sdkmanager/libs/sdklib/src/com/android/sdklib/io/FileOp.java
sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTest.java
sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTestCase.java
sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/LocalSdkParserTest.java [new file with mode: 0755]
sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java
sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformTarget.java
sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java

index 192e0a4..f699773 100644 (file)
@@ -23,6 +23,7 @@ import com.android.prefs.AndroidLocation;
 import com.android.prefs.AndroidLocation.AndroidLocationException;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.ISdkLog;
+import com.android.sdklib.ISystemImage;
 import com.android.sdklib.SdkConstants;
 import com.android.sdklib.SdkManager;
 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
@@ -775,6 +776,7 @@ public class Main {
 
         int index = 1;
         for (IAndroidTarget target : mSdkManager.getTargets()) {
+            mSdkLog.printf("----------\n");
             mSdkLog.printf("id: %1$d or \"%2$s\"\n", index, target.hashString());
             mSdkLog.printf("     Name: %s\n", target.getName());
             if (target.isPlatform()) {
@@ -803,8 +805,9 @@ public class Main {
                 }
             }
 
-            // get the target skins
+            // get the target skins & ABIs
             displaySkinList(target, "     Skins: ");
+            displayAbiList (target, "     ABIs : ");
 
             if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) {
                 mSdkLog.printf("     Adds USB support for devices (Vendor: 0x%04X)\n",
@@ -846,17 +849,17 @@ public class Main {
      * Displays the ABIs valid for the given target.
      */
     private void displayAbiList(IAndroidTarget target, String message) {
-        String[] abis = target.getAbiList();
+        ISystemImage[] systemImages = target.getSystemImages();
         mSdkLog.printf(message);
-        if (abis != null) {
+        if (systemImages.length > 0) {
             boolean first = true;
-            for (String skin : abis) {
+            for (ISystemImage si : systemImages) {
                 if (first == false) {
                     mSdkLog.printf(", ");
                 } else {
                     first = false;
                 }
-                mSdkLog.printf(skin);
+                mSdkLog.printf(si.getAbiType());
             }
             mSdkLog.printf("\n");
         } else {
@@ -906,6 +909,7 @@ public class Main {
                 mSdkLog.printf("          Based on Android %s (API level %s)\n",
                         target.getVersionName(), target.getVersion().getApiString());
             }
+            mSdkLog.printf("     ABI: %s\n", info.getAbiType());
 
             // display some extra values.
             Map<String, String> properties = info.getProperties();
@@ -1069,16 +1073,15 @@ public class Main {
 
             String abiType = mSdkCommandLine.getParamAbi();
             if (target != null && (abiType == null || abiType.length() == 0)) {
-                String[] abis = target.getAbiList();
-                if (abis != null && abis.length == 1) {
+                ISystemImage[] systemImages = target.getSystemImages();
+                if (systemImages != null && systemImages.length == 1) {
                     // Auto-select the single ABI available
-                    abiType = abis[0];
+                    abiType = systemImages[0].getAbiType();
                     mSdkLog.printf("Auto-selecting single ABI %1$s", abiType);
                 } else {
                     displayAbiList(target, "Valid ABIs: ");
                     errorAndExit("This platform has more than one ABI. Please specify one using --%1$s.",
                             SdkCommandLine.KEY_ABI);
-
                 }
             }
 
index a1f8853..19056b7 100644 (file)
@@ -80,6 +80,7 @@ public class MainTest extends SdkManagerTestCase {
                 + ", P     Name: " + this.getName() + "\n"
                 + ", P     Path: " + mAvdFolder + "\n"
                 + ", P   Target: Android 0.0 (API level 0)\n"
+                + ", P      ABI: armeabi\n"
                 + ", P     Skin: HVGA\n"
                 + "]",
                 getLog().toString());
@@ -109,6 +110,7 @@ public class MainTest extends SdkManagerTestCase {
                 + ", P     Name: " + this.getName() + "\n"
                 + ", P     Path: " + mAvdFolder + "\n"
                 + ", P   Target: Android 0.0 (API level 0)\n"
+                + ", P      ABI: armeabi\n"
                 + ", P     Skin: HVGA\n"
                 + ", P Snapshot: true\n"
                 + "]",
index 1c59720..06b1f81 100644 (file)
@@ -67,7 +67,7 @@ final class AddOnTarget implements IAndroidTarget {
     private final String mLocation;
     private final PlatformTarget mBasePlatform;
     private final String mName;
-    private String[] mAbis;
+    private final ISystemImage[] mSystemImages;
     private final String mVendor;
     private final int mRevision;
     private final String mDescription;
@@ -78,7 +78,6 @@ final class AddOnTarget implements IAndroidTarget {
     private String mDefaultSkin;
     private IOptionalLibrary[] mLibraries;
     private int mVendorId = NO_USB_ID;
-    private boolean mAbiCompatibilityMode;
 
     /**
      * Creates a new add-on
@@ -87,16 +86,23 @@ final class AddOnTarget implements IAndroidTarget {
      * @param vendor the vendor name of the add-on
      * @param revision the revision of the add-on
      * @param description the add-on description
-     * @param abis list of supported abis
+     * @param systemImages list of supported system images. Can be null or empty.
      * @param libMap A map containing the optional libraries. The map key is the fully-qualified
      * library name. The value is a 2 string array with the .jar filename, and the description.
      * @param hasRenderingLibrary whether the addon has a custom layoutlib.jar
      * @param hasRenderingResources whether the add has custom framework resources.
      * @param basePlatform the platform the add-on is extending.
      */
-    AddOnTarget(String location, String name, String vendor, int revision, String description,
-            String[] abis, Map<String, String[]> libMap,
-            boolean hasRenderingLibrary, boolean hasRenderingResources,
+    AddOnTarget(
+            String location,
+            String name,
+            String vendor,
+            int revision,
+            String description,
+            ISystemImage[] systemImages,
+            Map<String, String[]> libMap,
+            boolean hasRenderingLibrary,
+            boolean hasRenderingResources,
             PlatformTarget basePlatform) {
         if (location.endsWith(File.separator) == false) {
             location = location + File.separator;
@@ -111,13 +117,10 @@ final class AddOnTarget implements IAndroidTarget {
         mHasRenderingResources = hasRenderingResources;
         mBasePlatform = basePlatform;
 
-        //set compatibility mode
-        if (abis.length > 0) {
-            mAbis = abis;
-        } else {
-            mAbiCompatibilityMode = true;
-            mAbis = new String[] { SdkConstants.ABI_ARMEABI };
-        }
+        // If the add-on does not have any system-image of its own, the list here
+        // is empty and it's up to the callers to query the parent platform.
+        mSystemImages = systemImages == null ? new ISystemImage[0] : systemImages;
+        Arrays.sort(mSystemImages);
 
         // handle the optional libraries.
         if (libMap != null) {
@@ -141,23 +144,17 @@ final class AddOnTarget implements IAndroidTarget {
         return mName;
     }
 
-    /**
-    * Return the full path for images
-    * @param abiType type of the abi
-    * @return complete path where the image files are located
-    */
-    public String getImagePath(String abiType) {
-
-        if (mAbiCompatibilityMode) {
-        // Use legacy directory structure if only arm
-            return mLocation + SdkConstants.OS_IMAGES_FOLDER;
-        } else {
-            return mLocation + SdkConstants.OS_IMAGES_FOLDER + abiType + File.separator;
-          }
+    public ISystemImage getSystemImage(String abiType) {
+        for (ISystemImage sysImg : mSystemImages) {
+            if (sysImg.getAbiType().equals(abiType)) {
+                return sysImg;
+            }
+        }
+        return null;
     }
 
-    public String[] getAbiList() {
-        return mAbis;
+    public ISystemImage[] getSystemImages() {
+        return mSystemImages;
     }
 
     public String getVendor() {
@@ -387,6 +384,23 @@ final class AddOnTarget implements IAndroidTarget {
         return versionDiff;
     }
 
+    /**
+     * Returns a string representation suitable for debugging.
+     * The representation is not intended for display to the user.
+     *
+     * The representation is also purposely compact. It does not describe _all_ the properties
+     * of the target, only a few key ones.
+     *
+     * @see #getDescription()
+     */
+    @Override
+    public String toString() {
+        return String.format("AddonTarget %1$s rev %2$d (based on %3$s)",     //$NON-NLS-1$
+                getVersion(),
+                getRevision(),
+                getParent().toString());
+    }
+
     // ---- local methods.
 
     void setSkins(String[] skins, String defaultSkin) {
index 9c369ed..978165c 100644 (file)
@@ -239,14 +239,19 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> {
     int getUsbVendorId();
 
     /**
-     * Returns array of permitted processor architectures
+     * Returns an array of system images for this target.
+     * The array can be empty but not null.
      */
-    public String[] getAbiList();
+    public ISystemImage[] getSystemImages();
 
     /**
-     * Returns string to append to images directory for current ProcessorType
+     * Returns the system image information for the given {@code abiType}.
+     *
+     * @param abiType An ABI type string.
+     * @return An existing {@link ISystemImage} for the requested {@code abiType}
+     *         or null if none exists for this type.
      */
-    public String getImagePath(String abiType);
+    public ISystemImage getSystemImage(String abiType);
 
     /**
      * Returns whether the given target is compatible with the receiver.
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/ISystemImage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/ISystemImage.java
new file mode 100755 (executable)
index 0000000..da66e64
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib;
+
+import java.io.File;
+
+
+/**
+ * Describes a system image as used by an {@link IAndroidTarget}.
+ * A system image has an installation path, a location type and an ABI type.
+ */
+public interface ISystemImage extends Comparable<ISystemImage> {
+
+    /** Indicates the type of location for the system image folder in the SDK. */
+    public enum LocationType {
+        /**
+         * The system image is located in the legacy platform's {@link SdkConstants#FD_IMAGES}
+         * folder.
+         * <p/>
+         * Used by both platform and add-ons.
+         */
+        IN_PLATFORM_LEGACY,
+
+        /**
+         * The system image is located in a sub-directory of the platform's
+         * {@link SdkConstants#FD_IMAGES} folder, allowing for multiple system
+         * images within the platform.
+         * <p/>
+         * Used by both platform and add-ons.
+         */
+        IN_PLATFORM_SUBFOLDER,
+
+        /**
+         * The system image is located in the new SDK's {@link SdkConstants#FD_SYSTEM_IMAGES}
+         * folder. Supported as of Tools R14 and Repository XSD version 5.
+         * <p/>
+         * Used <em>only</em> by both platform. This is not supported for add-ons yet.
+         */
+        IN_SYSTEM_IMAGE,
+    }
+
+    /** Returns the actual location of an installed system image. */
+    public abstract File getLocation();
+
+    /** Indicates the location strategy for this system image in the SDK. */
+    public abstract LocationType getLocationType();
+
+    /**
+     * Returns the ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
+     * {@link SdkConstants#ABI_ARMEABI_V7A} or  {@link SdkConstants#ABI_INTEL_ATOM}.
+     * Cannot be null nor empty.
+     */
+    public abstract String getAbiType();
+}
index b8226c0..b7ddc2b 100644 (file)
@@ -20,6 +20,7 @@ import com.android.sdklib.SdkManager.LayoutlibVersion;
 import com.android.sdklib.util.SparseArray;
 
 import java.io.File;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Map;
 
@@ -44,8 +45,7 @@ final class PlatformTarget implements IAndroidTarget {
     private final Map<String, String> mProperties;
     private final SparseArray<String> mPaths = new SparseArray<String>();
     private String[] mSkins;
-    private String[] mAbis;
-    private boolean mAbiCompatibilityMode;
+    private final ISystemImage[] mSystemImages;
     private final LayoutlibVersion mLayoutlibVersion;
 
     /**
@@ -58,7 +58,7 @@ final class PlatformTarget implements IAndroidTarget {
      * @param versionName the version name of the platform.
      * @param revision the revision of the platform component.
      * @param layoutlibVersion The {@link LayoutlibVersion}. May be null.
-     * @param abis the list of supported abis
+     * @param systemImages list of supported system images
      * @param properties the platform properties
      */
     @SuppressWarnings("deprecation")
@@ -70,7 +70,7 @@ final class PlatformTarget implements IAndroidTarget {
             String versionName,
             int revision,
             LayoutlibVersion layoutlibVersion,
-            String[] abis,
+            ISystemImage[] systemImages,
             Map<String, String> properties) {
         if (platformOSPath.endsWith(File.separator) == false) {
             platformOSPath = platformOSPath + File.separator;
@@ -81,6 +81,8 @@ final class PlatformTarget implements IAndroidTarget {
         mVersionName = versionName;
         mRevision = revision;
         mLayoutlibVersion = layoutlibVersion;
+        mSystemImages = systemImages == null ? new ISystemImage[0] : systemImages;
+        Arrays.sort(mSystemImages);
 
         if (mVersion.isPreview()) {
             mName =  String.format(PLATFORM_NAME_PREVIEW, mVersionName);
@@ -126,14 +128,6 @@ final class PlatformTarget implements IAndroidTarget {
                 SdkConstants.FN_DX);
         mPaths.put(DX_JAR, sdkOsPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_LIB_FOLDER +
                 SdkConstants.FN_DX_JAR);
-
-        //set compatibility mode, abis length would be 0 for older APIs
-        if (abis.length > 0) {
-            mAbis = abis;
-        } else {
-            mAbiCompatibilityMode = true;
-            mAbis = new String[] { SdkConstants.ABI_ARMEABI };
-        }
     }
 
     /**
@@ -143,25 +137,17 @@ final class PlatformTarget implements IAndroidTarget {
         return mLayoutlibVersion;
     }
 
-    /**
-     * Return the full path for images
-     * @param abiType type of the abi
-     * @return complete path where the image files are located
-     */
-    public String getImagePath(String abiType) {
-        if (mAbiCompatibilityMode) {
-            // Use legacy directory structure if only arm is supported
-            return mRootFolderOsPath + SdkConstants.OS_IMAGES_FOLDER;
-        } else {
-            return mRootFolderOsPath + SdkConstants.OS_IMAGES_FOLDER + abiType + File.separator;
+    public ISystemImage getSystemImage(String abiType) {
+        for (ISystemImage sysImg : mSystemImages) {
+            if (sysImg.getAbiType().equals(abiType)) {
+                return sysImg;
+            }
         }
+        return null;
     }
 
-    /**
-     * Retrieve and return the list of abis
-     */
-    public String[] getAbiList() {
-        return mAbis;
+    public ISystemImage[] getSystemImages() {
+        return mSystemImages;
     }
 
     public String getLocation() {
@@ -353,6 +339,21 @@ final class PlatformTarget implements IAndroidTarget {
         return versionDiff;
     }
 
+    /**
+     * Returns a string representation suitable for debugging.
+     * The representation is not intended for display to the user.
+     *
+     * The representation is also purposely compact. It does not describe _all_ the properties
+     * of the target, only a few key ones.
+     *
+     * @see #getDescription()
+     */
+    @Override
+    public String toString() {
+        return String.format("PlatformTarget %1$s rev %2$d",     //$NON-NLS-1$
+                getVersion(),
+                getRevision());
+    }
 
     public String getProperty(String name) {
         return mProperties.get(name);
index 987ae21..61499f4 100644 (file)
@@ -22,6 +22,7 @@ import com.android.io.FileWrapper;
 import com.android.prefs.AndroidLocation;
 import com.android.prefs.AndroidLocation.AndroidLocationException;
 import com.android.sdklib.AndroidVersion.AndroidVersionException;
+import com.android.sdklib.ISystemImage.LocationType;
 import com.android.sdklib.internal.project.ProjectProperties;
 import com.android.util.Pair;
 
@@ -36,6 +37,8 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -444,7 +447,9 @@ public class SdkManager {
                     return null;
                 }
 
-                String[] abiList = getAbiList(platformFolder.getAbsolutePath());
+                ISystemImage[] systemImages =
+                    getPlatformSystemImages(sdkOsPath, platformFolder, apiNumber, apiCodename);
+
                 // create the target.
                 PlatformTarget target = new PlatformTarget(
                         sdkOsPath,
@@ -454,7 +459,7 @@ public class SdkManager {
                         apiName,
                         revision,
                         layoutlibVersion,
-                        abiList,
+                        systemImages,
                         map);
 
                 // need to parse the skins.
@@ -473,28 +478,132 @@ public class SdkManager {
     }
 
     /**
-    * Get all the abi types supported for a given target
-    * @param path Path where the images folder for a target is located
-    * @return an array of strings containing all the abi names for the target
+     * Get all the system images supported by an add-on target.
+     * For an add-on, we first look for sub-folders in the addon/images directory.
+     * If none are found but the directory exists and is not empty, assume it's a legacy
+     * arm eabi system image.
+     * <p/>
+     * Note that it's OK for an add-on to have no system-images at all, since it can always
+     * rely on the ones from its base platform.
+     *
+     * @param root Root of the add-on target being loaded.
+     * @return an array of ISystemImage containing all the system images for the target.
+     *              The list can be empty.
     */
-    private static String[] getAbiList(String path) {
-        ArrayList<String> list = new ArrayList<String>();
+    private static ISystemImage[] getAddonSystemImages(File root) {
+        Set<ISystemImage> found = new TreeSet<ISystemImage>();
 
-        File imagesFolder = new File(path + File.separator + SdkConstants.OS_IMAGES_FOLDER);
-        File[] files = imagesFolder.listFiles();
+        root = new File(root, SdkConstants.OS_IMAGES_FOLDER);
+        File[] files = root.listFiles();
+        boolean hasImgFiles = false;
 
         if (files != null) {
-            // Loop through Images directory.  If subdirectories exist, set multiprocessor mode
+            // Look for sub-directories
             for (File file : files) {
                 if (file.isDirectory()) {
-                    list.add(file.getName());
+                    found.add(new SystemImage(
+                            file,
+                            LocationType.IN_PLATFORM_SUBFOLDER,
+                            file.getName()));
+                } else if (!hasImgFiles && file.isFile()) {
+                    if (file.getName().endsWith(".img")) {      //$NON-NLS-1$
+                        hasImgFiles = true;
+                    }
                 }
             }
         }
-        String[] abis = new String[list.size()];
-        list.toArray(abis);
 
-        return abis;
+        if (found.size() == 0 && hasImgFiles && root.isDirectory()) {
+            // We found no sub-folder system images but it looks like the top directory
+            // has some img files in it. It must be a legacy ARM EABI system image folder.
+            found.add(new SystemImage(
+                    root,
+                    LocationType.IN_PLATFORM_LEGACY,
+                    SdkConstants.ABI_ARMEABI));
+        }
+
+        return found.toArray(new ISystemImage[found.size()]);
+    }
+
+    /**
+     * Get all the system images supported by a platform target.
+     * For a platform, we first look in the new sdk/system-images folders then we
+     * look for sub-folders in the platform/images directory and/or the one legacy
+     * folder.
+     * If any given API appears twice or more, the first occurrence wins.
+     *
+     * @param sdkOsPath The path to the SDK.
+     * @param root Root of the platform target being loaded.
+     * @param apiCodename
+     * @return an array of ISystemImage containing all the system images for the target.
+     *              The list can be empty.
+    */
+    private static ISystemImage[] getPlatformSystemImages(
+            String sdkOsPath,
+            File root,
+            int apiNumber,
+            String apiCodename) {
+        Set<ISystemImage> found = new TreeSet<ISystemImage>();
+        Set<String> abiFound = new HashSet<String>();
+
+        // First look in the SDK/system-image folder
+
+        AndroidVersion version = new AndroidVersion(apiNumber, apiCodename);
+        File siFolder = SystemImage.getCanonicalFolder(sdkOsPath, version, null /*abiType*/);
+        File[] files = siFolder.listFiles();
+
+        if (files != null) {
+            // Look for sub-directories
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    String abi = file.getName();
+                    found.add(new SystemImage(
+                            file,
+                            LocationType.IN_SYSTEM_IMAGE,
+                            abi));
+                    abiFound.add(abi);
+                }
+            }
+        }
+
+        // Then look in either the platform/images/abi or the legacy folder
+        root = new File(root, SdkConstants.OS_IMAGES_FOLDER);
+        files = root.listFiles();
+        boolean useLegacy = true;
+        boolean hasImgFiles = false;
+
+        if (files != null) {
+            // Look for sub-directories
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    useLegacy = false;
+                    String abi = file.getName();
+                    if (!abiFound.contains(abi)) {
+                        found.add(new SystemImage(
+                                file,
+                                LocationType.IN_PLATFORM_SUBFOLDER,
+                                abi));
+                        abiFound.add(abi);
+                    }
+                } else if (!hasImgFiles && file.isFile()) {
+                    if (file.getName().endsWith(".img")) {      //$NON-NLS-1$
+                        hasImgFiles = true;
+                    }
+                }
+            }
+        }
+
+        if (useLegacy && hasImgFiles && root.isDirectory() &&
+                !abiFound.contains(SdkConstants.ABI_ARMEABI)) {
+            // We found no sub-folder system images but it looks like the top directory
+            // has some img files in it. It must be a legacy ARM EABI system image folder.
+            found.add(new SystemImage(
+                    root,
+                    LocationType.IN_PLATFORM_LEGACY,
+                    SdkConstants.ABI_ARMEABI));
+        }
+
+        return found.toArray(new ISystemImage[found.size()]);
     }
 
     /**
@@ -638,7 +747,7 @@ public class SdkManager {
             }
 
             // get the abi list.
-            String[] abiList = getAbiList(addonDir.getAbsolutePath());
+            ISystemImage[] systemImages = getAddonSystemImages(addonDir);
 
             // check whether the add-on provides its own rendering info/library.
             boolean hasRenderingLibrary = false;
@@ -652,7 +761,7 @@ public class SdkManager {
             }
 
             AddOnTarget target = new AddOnTarget(addonDir.getAbsolutePath(), name, vendor,
-                    revisionValue, description, abiList, libMap,
+                    revisionValue, description, systemImages, libMap,
                     hasRenderingLibrary, hasRenderingResources,baseTarget);
 
             // need to parse the skins.
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SystemImage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SystemImage.java
new file mode 100755 (executable)
index 0000000..2188329
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib;
+
+import com.android.sdklib.io.FileOp;
+
+import java.io.File;
+
+
+/**
+ * Describes a system image as used by an {@link IAndroidTarget}.
+ * A system image has an installation path, a location type and an ABI type.
+ */
+public class SystemImage implements ISystemImage {
+
+    public static final String ANDROID_PREFIX = "android-";     //$NON-NLS-1$
+
+    private final LocationType mLocationtype;
+    private final String mAbiType;
+    private final File mLocation;
+
+    /**
+     * Creates a {@link SystemImage} description for an existing system image folder.
+     *
+     * @param location The location of an installed system image.
+     * @param locationType Where the system image folder is located for this ABI.
+     * @param abiType The ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
+     *          {@link SdkConstants#ABI_ARMEABI_V7A} or  {@link SdkConstants#ABI_INTEL_ATOM}.
+     */
+    public SystemImage(File location, LocationType locationType, String abiType) {
+        mLocation = location;
+        mLocationtype = locationType;
+        mAbiType = abiType;
+    }
+
+    /**
+     * Creates a {@link SystemImage} description for a non-existing system image folder.
+     * The actual location is computed based on the {@code locationtype}.
+     *
+     * @param sdkManager The current SDK manager.
+     * @param locationType Where the system image folder is located for this ABI.
+     * @param abiType The ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
+     *          {@link SdkConstants#ABI_ARMEABI_V7A} or  {@link SdkConstants#ABI_INTEL_ATOM}.
+     * @throws IllegalArgumentException if the {@code target} used for
+     *         {@link ISystemImage.LocationType#IN_SYSTEM_IMAGE} is not a {@link PlatformTarget}.
+     */
+    public SystemImage(
+            SdkManager sdkManager,
+            IAndroidTarget target,
+            LocationType locationType,
+            String abiType) {
+        mLocationtype = locationType;
+        mAbiType = abiType;
+
+        File location = null;
+        switch(locationType) {
+        case IN_PLATFORM_LEGACY:
+            location = new File(target.getLocation(), SdkConstants.OS_IMAGES_FOLDER);
+            break;
+
+        case IN_PLATFORM_SUBFOLDER:
+            location = FileOp.append(target.getLocation(), SdkConstants.OS_IMAGES_FOLDER, abiType);
+            break;
+
+        case IN_SYSTEM_IMAGE:
+            if (!target.isPlatform()) {
+                throw new IllegalArgumentException(
+                        "Add-ons do not support the system-image location type"); //$NON-NLS-1$
+            }
+
+            location = getCanonicalFolder(sdkManager.getLocation(), target.getVersion(), abiType);
+            break;
+        default:
+            // This is not supposed to happen unless LocationType is
+            // extended without adjusting this code.
+            assert false : "SystemImage used with an incorrect locationType";       //$NON-NLS-1$
+        }
+        mLocation = location;
+    }
+
+    /**
+     * Static helper method that returns the canonical path for a system-image that uses
+     * the {@link ISystemImage.LocationType#IN_SYSTEM_IMAGE} location type.
+     * <p/>
+     * Such an image is located in {@code SDK/system-images/android-N/abiType}.
+     * For this reason this method requires the root SDK as well as the platform and the ABI type.
+     *
+     * @param sdkOsPath The OS path to the SDK.
+     * @param platformVersion The platform version.
+     * @param abiType An optional ABI type. If null, the parent directory is returned.
+     * @return A file that represents the location of the canonical system-image folder
+     *         for this configuration.
+     */
+    public static File getCanonicalFolder(
+            String sdkOsPath,
+            AndroidVersion platformVersion,
+            String abiType) {
+        File root = FileOp.append(
+                sdkOsPath,
+                SdkConstants.FD_SYSTEM_IMAGES,
+                ANDROID_PREFIX + platformVersion.getApiString());
+        if (abiType == null) {
+            return root;
+        } else {
+            return FileOp.append(root, abiType);
+        }
+    }
+
+    /** Returns the actual location of an installed system image. */
+    public File getLocation() {
+        return mLocation;
+    }
+
+    /** Indicates the location strategy for this system image in the SDK. */
+    public LocationType getLocationType() {
+        return mLocationtype;
+    }
+
+    /**
+     * Returns the ABI type. For example, one of {@link SdkConstants#ABI_ARMEABI},
+     * {@link SdkConstants#ABI_ARMEABI_V7A} or  {@link SdkConstants#ABI_INTEL_ATOM}.
+     * Cannot be null nor empty.
+     */
+    public String getAbiType() {
+        return mAbiType;
+    }
+
+    public int compareTo(ISystemImage other) {
+        // Sort by ABI name only. This is what matters from a user point of view.
+        return this.getAbiType().compareToIgnoreCase(other.getAbiType());
+    }
+
+    /**
+     * Generates a string representation suitable for debug purposes.
+     * The string is not intended to be displayed to the user.
+     *
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return String.format("SystemImage ABI=%s, location %s='%s'",           //$NON-NLS-1$
+                mAbiType,
+                mLocationtype.toString().replace('_', ' ').toLowerCase(),
+                mLocation
+                );
+    }
+
+
+}
index 1ca7c07..5943183 100644 (file)
@@ -21,6 +21,7 @@ import com.android.prefs.AndroidLocation;
 import com.android.prefs.AndroidLocation.AndroidLocationException;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.ISdkLog;
+import com.android.sdklib.ISystemImage;
 import com.android.sdklib.SdkConstants;
 import com.android.sdklib.SdkManager;
 import com.android.sdklib.internal.avd.AvdInfo.AvdStatus;
@@ -522,22 +523,37 @@ public class AvdManager {
             iniFile = createAvdIniFile(avdName, avdFolder, target, removePrevious);
 
             // writes the userdata.img in it.
-            String imagePath = target.getImagePath(abiType);
 
-            File userdataSrc = new File(imagePath, USERDATA_IMG);
+            File userdataSrc = null;
 
-            if (userdataSrc.exists() == false && target.isPlatform() == false) {
-                imagePath = target.getParent().getImagePath(abiType);
-                userdataSrc = new File(imagePath, USERDATA_IMG);
+            // Look for a system image in the add-on.
+            // If we don't find one there, look in the base platform.
+            ISystemImage systemImage = target.getSystemImage(abiType);
+
+            if (systemImage != null) {
+                File imageFolder = systemImage.getLocation();
+                userdataSrc = new File(imageFolder, USERDATA_IMG);
             }
 
-            if (userdataSrc.exists() == false) {
+            if ((userdataSrc == null || !userdataSrc.exists()) && !target.isPlatform()) {
+                // If we don't find a system-image in the add-on, look into the platform.
+
+                systemImage = target.getParent().getSystemImage(abiType);
+                if (systemImage != null) {
+                    File imageFolder = systemImage.getLocation();
+                    userdataSrc = new File(imageFolder, USERDATA_IMG);
+                }
+            }
+
+            if (userdataSrc == null || !userdataSrc.exists()) {
                 log.error(null,
-                        "Unable to find a '%1$s' file of '%2$s' to copy into the AVD folder.",
-                        USERDATA_IMG, imagePath);
+                        "Unable to find a '%1$s' file for ABI %2$s to copy into the AVD folder.",
+                        USERDATA_IMG,
+                        abiType);
                 needCleanup = true;
                 return null;
             }
+
             File userdataDest = new File(avdFolder, USERDATA_IMG);
 
             copyImageFile(userdataSrc, userdataDest);
@@ -863,17 +879,26 @@ public class AvdManager {
      */
     private String getImageRelativePath(IAndroidTarget target, String abiType)
             throws InvalidTargetPathException {
-        String imageFullPath = target.getImagePath(abiType);
+
+        ISystemImage systemImage = target.getSystemImage(abiType);
+        if (systemImage == null) {
+            throw new IllegalArgumentException(String.format(
+                    "ABI Type %s is unknown for target %s",
+                    abiType,
+                    target.getDescription()));
+        }
+
+        File folder = systemImage.getLocation();
+        String imageFullPath = folder.getAbsolutePath();
 
         // make this path relative to the SDK location
         String sdkLocation = mSdkManager.getLocation();
-        if (imageFullPath.startsWith(sdkLocation) == false) {
+        if (!imageFullPath.startsWith(sdkLocation)) {
             // this really really should not happen.
             assert false;
             throw new InvalidTargetPathException("Target location is not inside the SDK.");
         }
 
-        File folder = new File(imageFullPath);
         if (folder.isDirectory()) {
             String[] list = folder.list(new FilenameFilter() {
                 public boolean accept(File dir, String name) {
@@ -882,10 +907,17 @@ public class AvdManager {
             });
 
             if (list.length > 0) {
+                // Remove the SDK root path, e.g. /sdk/dir1/dir2 => /dir1/dir2
                 imageFullPath = imageFullPath.substring(sdkLocation.length());
+                // The path is relative, so it must not start with a file separator
                 if (imageFullPath.charAt(0) == File.separatorChar) {
                     imageFullPath = imageFullPath.substring(1);
                 }
+                // For compatibility with previous versions, we denote folders
+                // by ending the path with file separator
+                if (!imageFullPath.endsWith(File.separator)) {
+                    imageFullPath += File.separator;
+                }
 
                 return imageFullPath;
             }
@@ -1573,6 +1605,7 @@ public class AvdManager {
 
     /**
      * Sets the paths to the system images in a properties map.
+     *
      * @param target the target in which to find the system images.
      * @param abiType the abi type of the avd to find
      *        the architecture-dependent system images.
@@ -1581,7 +1614,9 @@ public class AvdManager {
      * @return true if success, false if some path are missing.
      */
     private boolean setImagePathProperties(IAndroidTarget target,
-        String abiType, Map<String, String> properties, ISdkLog log) {
+            String abiType,
+            Map<String, String> properties,
+            ISdkLog log) {
         properties.remove(AVD_INI_IMAGES_1);
         properties.remove(AVD_INI_IMAGES_2);
 
index 0347dd5..040796c 100755 (executable)
@@ -18,8 +18,10 @@ package com.android.sdklib.internal.repository;
 \r
 import com.android.sdklib.IAndroidTarget;\r
 import com.android.sdklib.ISdkLog;\r
+import com.android.sdklib.ISystemImage;\r
 import com.android.sdklib.SdkConstants;\r
 import com.android.sdklib.SdkManager;\r
+import com.android.sdklib.ISystemImage.LocationType;\r
 import com.android.sdklib.internal.repository.Archive.Arch;\r
 import com.android.sdklib.internal.repository.Archive.Os;\r
 import com.android.util.Pair;\r
@@ -69,7 +71,7 @@ public class LocalSdkParser {
      * Store the packages internally. You can use {@link #getPackages()} to retrieve them\r
      * at any time later.\r
      *\r
-     * @param osSdkRoot The path to the SDK folder.\r
+     * @param osSdkRoot The path to the SDK folder, typically {@code sdkManager.getLocation()}.\r
      * @param sdkManager An existing SDK manager to list current platforms and addons.\r
      * @param monitor A monitor to track progress. Cannot be null.\r
      * @return The packages found. Can be retrieved later using {@link #getPackages()}.\r
@@ -136,6 +138,24 @@ public class LocalSdkParser {
                 } else {\r
                     pkg = AddonPackage.create(target, props);\r
                 }\r
+\r
+                for (ISystemImage systemImage : target.getSystemImages()) {\r
+                    if (systemImage.getLocationType() == LocationType.IN_SYSTEM_IMAGE) {\r
+                        File siDir = systemImage.getLocation();\r
+                        if (siDir.isDirectory()) {\r
+                            Properties siProps = parseProperties(\r
+                                    new File(siDir, SdkConstants.FN_SOURCE_PROP));\r
+                            Package pkg2 = new SystemImagePackage(\r
+                                    target.getVersion(),\r
+                                    0 /*revision*/,   // this will use the one from siProps if any\r
+                                    systemImage.getAbiType(),\r
+                                    siProps);\r
+                            packages.add(pkg2);\r
+                            visited.add(siDir);\r
+                        }\r
+                    }\r
+                }\r
+\r
             } catch (Exception e) {\r
                 monitor.error(e, null);\r
             }\r
@@ -147,6 +167,8 @@ public class LocalSdkParser {
         }\r
         monitor.incProgress(1);\r
 \r
+        scanMissingSystemImages(sdkManager, visited, packages, monitor);\r
+        monitor.incProgress(1);\r
         scanMissingAddons(sdkManager, visited, packages, monitor);\r
         monitor.incProgress(1);\r
         scanMissingSamples(osSdkRoot, visited, packages, monitor);\r
@@ -275,12 +297,12 @@ public class LocalSdkParser {
             ISdkLog log) {\r
         File addons = new File(new File(sdkManager.getLocation()), SdkConstants.FD_ADDONS);\r
 \r
-        if (!addons.isDirectory()) {\r
-            // It makes listFiles() return null so let's avoid it.\r
+        File[] files = addons.listFiles();\r
+        if (files == null) {\r
             return;\r
         }\r
 \r
-        for (File dir : addons.listFiles()) {\r
+        for (File dir : files) {\r
             if (dir.isDirectory() && !visited.contains(dir)) {\r
                 Pair<Map<String, String>, String> infos =\r
                     SdkManager.parseAddonProperties(dir, sdkManager.getTargets(), log);\r
@@ -299,6 +321,54 @@ public class LocalSdkParser {
     }\r
 \r
     /**\r
+     * The sdk manager only lists valid system image via its addons or platform targets.\r
+     * However here we also want to find "broken" system images, that is system images\r
+     * that are located in the sdk/system-images folder but somehow not loaded properly.\r
+     */\r
+    private void scanMissingSystemImages(SdkManager sdkManager,\r
+            HashSet<File> visited,\r
+            ArrayList<Package> packages,\r
+            ISdkLog log) {\r
+        File siRoot = new File(sdkManager.getLocation(), SdkConstants.FD_SYSTEM_IMAGES);\r
+\r
+        File[] files = siRoot.listFiles();\r
+        if (files == null) {\r
+            return;\r
+        }\r
+\r
+        // The system-images folder contains a list of platform folders.\r
+        for (File platformDir : files) {\r
+            if (platformDir.isDirectory() && !visited.contains(platformDir)) {\r
+                visited.add(platformDir);\r
+\r
+                // In the platform directory, we expect a list of abi folders\r
+                File[] platformFiles = platformDir.listFiles();\r
+                if (platformFiles != null) {\r
+                    for (File abiDir : platformFiles) {\r
+                        if (abiDir.isDirectory() && !visited.contains(abiDir)) {\r
+                            visited.add(abiDir);\r
+\r
+                            // Ignore empty directories\r
+                            File[] abiFiles = abiDir.listFiles();\r
+                            if (abiFiles != null && abiFiles.length > 0) {\r
+                                Properties props =\r
+                                    parseProperties(new File(abiDir, SdkConstants.FN_SOURCE_PROP));\r
+\r
+                                try {\r
+                                    Package pkg = SystemImagePackage.createBroken(abiDir, props);\r
+                                    packages.add(pkg);\r
+                                } catch (Exception e) {\r
+                                    log.error(e, null);\r
+                                }\r
+                            }\r
+                        }\r
+                    }\r
+                }\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
      * Try to find a tools package at the given location.\r
      * Returns null if not found.\r
      */\r
index dcc157a..00a9067 100755 (executable)
@@ -19,9 +19,9 @@ package com.android.sdklib.internal.repository;
 import com.android.annotations.VisibleForTesting;\r
 import com.android.annotations.VisibleForTesting.Visibility;\r
 import com.android.sdklib.AndroidVersion;\r
-import com.android.sdklib.IAndroidTarget;\r
 import com.android.sdklib.SdkConstants;\r
 import com.android.sdklib.SdkManager;\r
+import com.android.sdklib.SystemImage;\r
 import com.android.sdklib.internal.repository.Archive.Arch;\r
 import com.android.sdklib.internal.repository.Archive.Os;\r
 import com.android.sdklib.repository.SdkRepoConstants;\r
@@ -39,7 +39,7 @@ public class SystemImagePackage extends Package
         implements IPackageVersion, IPlatformDependency {\r
 \r
     @VisibleForTesting(visibility=Visibility.PRIVATE)\r
-    static final String PROP_ABI      = "System.Image.Abi";      //$NON-NLS-1$\r
+    static final String PROP_ABI = "SystemImage.Abi";      //$NON-NLS-1$\r
 \r
     /** The package version, for platform, add-on and doc packages. */\r
     private final AndroidVersion mVersion;\r
@@ -73,21 +73,6 @@ public class SystemImagePackage extends Package
         mAbi = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_ABI);\r
     }\r
 \r
-    /**\r
-     * TODO OBSOLETE DOC\r
-     * Creates and returns a new system image package based on an actual {@link IAndroidTarget}\r
-     * (which {@link IAndroidTarget#isPlatform()} is true) from the {@link SdkManager}.\r
-     * This is used to list local SDK folders in which case there is one archive which\r
-     * URL is the actual target location.\r
-     * <p/>\r
-     * By design, this creates a package with one and only one archive.\r
-     * @deprecated TODO CHANGE AS ACTUALLY NEEDED\r
-     */\r
-    @Deprecated\r
-    static Package create(AndroidVersion platformVersion, int revision, String abi, Properties props) {\r
-        return new SystemImagePackage(platformVersion, revision, abi, props);\r
-    }\r
-\r
     @VisibleForTesting(visibility=Visibility.PRIVATE)\r
     protected SystemImagePackage(\r
             AndroidVersion platformVersion,\r
@@ -123,6 +108,45 @@ public class SystemImagePackage extends Package
     }\r
 \r
     /**\r
+     * Creates a {@link BrokenPackage} representing a system image that failed to load\r
+     * with the regular {@link SdkManager} workflow.\r
+     *\r
+     * @param abiDir The SDK/system-images/android-N/abi folder\r
+     * @param props The properties located in {@code abiDir} or null if not found.\r
+     * @return A new {@link BrokenPackage} that represents this installed package.\r
+     */\r
+    public static Package createBroken(File abiDir, Properties props) {\r
+\r
+        String abiType = abiDir.getName();\r
+\r
+        StringBuilder sb = new StringBuilder(String.format("Broken %1$s System Image",\r
+                getAbiDisplayNameInternal(abiType)));\r
+\r
+        int apiLevel = IExactApiLevelDependency.API_LEVEL_INVALID;\r
+        try {\r
+            // Try to parse the first number out of the platform folder name.\r
+            String platform = abiDir.getParentFile().getName();\r
+            platform = platform.replaceAll("[^0-9]+", " ").trim();     //$NON-NLS-1$ //$NON-NLS-2$\r
+            int pos = platform.indexOf(' ');\r
+            if (pos >= 0) {\r
+                platform = platform.substring(0, pos);\r
+            }\r
+\r
+            apiLevel = Integer.parseInt(platform);\r
+\r
+            sb.append(String.format(", API %1$d", apiLevel));\r
+        } catch (Exception ignore) {\r
+        }\r
+\r
+        String shortDesc = sb.toString();\r
+\r
+        return new BrokenPackage(props, shortDesc, shortDesc /*longDesc*/,\r
+                IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,\r
+                apiLevel,\r
+                abiDir.getAbsolutePath());\r
+    }\r
+\r
+    /**\r
      * Save the properties of the current packages in the given {@link Properties} object.\r
      * These properties will later be given to a constructor that takes a {@link Properties} object.\r
      */\r
@@ -141,11 +165,13 @@ public class SystemImagePackage extends Package
 \r
     /** Returns a display-friendly name for the ABI of the system-image. */\r
     public String getAbiDisplayName() {\r
-        String abi = mAbi\r
-                        .replace("armeabi", "ARM EABI")         //$NON-NLS-1$  //$NON-NLS-2$\r
-                        .replace("x86",     "Intel x86 Atom")   //$NON-NLS-1$  //$NON-NLS-2$\r
-                        .replace("-", " ");                     //$NON-NLS-1$  //$NON-NLS-2$\r
-        return abi;\r
+        return getAbiDisplayNameInternal(mAbi);\r
+    }\r
+\r
+    private static String getAbiDisplayNameInternal(String abi) {\r
+        return abi.replace("armeabi", "ARM EABI")         //$NON-NLS-1$  //$NON-NLS-2$\r
+                  .replace("x86",     "Intel x86 Atom")   //$NON-NLS-1$  //$NON-NLS-2$\r
+                  .replace("-", " ");                     //$NON-NLS-1$  //$NON-NLS-2$\r
     }\r
 \r
     /**\r
@@ -219,7 +245,7 @@ public class SystemImagePackage extends Package
     @Override\r
     public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {\r
         File folder = new File(osSdkRoot, SdkConstants.FD_SYSTEM_IMAGES);\r
-        folder = new File(folder, "android-" + mVersion.getApiString());    //$NON-NLS-1$\r
+        folder = new File(folder, SystemImage.ANDROID_PREFIX + mVersion.getApiString());\r
 \r
         // Computes a folder directory using the sanitized abi string.\r
         String abi = mAbi;\r
index f8765e6..4600e68 100755 (executable)
@@ -61,6 +61,31 @@ public class FileOp implements IFileOp {
     }\r
 \r
     /**\r
+     * Appends the given {@code segments} to the {@code base} file.\r
+     *\r
+     * @param base A base file, non-null.\r
+     * @param segments Individual folder or filename segments to append to the base file.\r
+     * @return A new file representing the concatenation of the base path with all the segments.\r
+     */\r
+    public static File append(File base, String...segments) {\r
+        for (String segment : segments) {\r
+            base = new File(base, segment);\r
+        }\r
+        return base;\r
+    }\r
+\r
+    /**\r
+     * Appends the given {@code segments} to the {@code base} file.\r
+     *\r
+     * @param base A base file path, non-empty and non-null.\r
+     * @param segments Individual folder or filename segments to append to the base path.\r
+     * @return A new file representing the concatenation of the base path with all the segments.\r
+     */\r
+    public static File append(String base, String...segments) {\r
+        return append(new File(base), segments);\r
+    }\r
+\r
+    /**\r
      * Helper to delete a file or a directory.\r
      * For a directory, recursively deletes all of its content.\r
      * Files that cannot be deleted right away are marked for deletion on exit.\r
index ebdb918..8df9627 100755 (executable)
 package com.android.sdklib;
 
 
+import com.android.sdklib.ISystemImage.LocationType;
 import com.android.sdklib.SdkManager.LayoutlibVersion;
 
-public class SdkManagerTest extends SdkManagerTestCase {
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-    }
+import java.util.Arrays;
+import java.util.regex.Pattern;
 
-    @Override
-    public void tearDown() throws Exception {
-        super.tearDown();
-    }
+public class SdkManagerTest extends SdkManagerTestCase {
 
     @SuppressWarnings("deprecation")
     public void testSdkManager_LayoutlibVersion() {
@@ -45,4 +39,113 @@ public class SdkManagerTest extends SdkManagerTestCase {
 
         assertSame(lv, sdkman.getMaxLayoutlibVersion());
     }
+
+    public void testSdkManager_SystemImage() throws Exception {
+        SdkManager sdkman = getSdkManager();
+        assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
+        IAndroidTarget t = sdkman.getTargets()[0];
+
+        // By default SdkManagerTestCase creates an SDK with one platform containing
+        // a legacy armeabi system image.
+        assertEquals(
+                "[SystemImage ABI=armeabi, location in platform legacy='$SDK/platforms/v0_0/images']",
+                cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
+
+        // Now add a few "platform subfolders" system images and reload the SDK.
+        // This disables the "legacy" mode, which means that although the armeabi
+        // target from above is present, it is no longer visible.
+
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_PLATFORM_SUBFOLDER, SdkConstants.ABI_ARMEABI_V7A));
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_PLATFORM_SUBFOLDER, SdkConstants.ABI_INTEL_ATOM));
+
+        sdkman.reloadSdk(getLog());
+        assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
+        t = sdkman.getTargets()[0];
+
+        assertEquals(
+                "[SystemImage ABI=armeabi-v7a, location in platform subfolder='$SDK/platforms/v0_0/images/armeabi-v7a', " +
+                 "SystemImage ABI=x86, location in platform subfolder='$SDK/platforms/v0_0/images/x86']",
+                cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
+
+        // Now add arm + arm v7a images using the new SDK/system-images.
+        // The x86 one from the platform subfolder is still visible.
+        // The armeabi one from the legacy folder is overridden by the new one.
+        // The armeabi-v7a one from the platform subfolder is overridden by the new one.
+
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_ARMEABI));
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_ARMEABI_V7A));
+
+        sdkman.reloadSdk(getLog());
+        assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
+        t = sdkman.getTargets()[0];
+
+        assertEquals(
+                "[SystemImage ABI=armeabi, location in system image='$SDK/system-images/android-0/armeabi', " +
+                 "SystemImage ABI=armeabi-v7a, location in system image='$SDK/system-images/android-0/armeabi-v7a', " +
+                 "SystemImage ABI=x86, location in platform subfolder='$SDK/platforms/v0_0/images/x86']",
+                cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
+    }
+
+    public void testSdkManager_SystemImage_LegacyOverride() throws Exception {
+        SdkManager sdkman = getSdkManager();
+        assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
+        IAndroidTarget t = sdkman.getTargets()[0];
+
+        // By default SdkManagerTestCase creates an SDK with one platform containing
+        // a legacy armeabi system image.
+        assertEquals(
+                "[SystemImage ABI=armeabi, location in platform legacy='$SDK/platforms/v0_0/images']",
+                cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
+
+        // Now add a different ABI system image in the new system-images folder.
+        // This does not hide the legacy one as long as the ABI type is different
+        // (to contrast: having at least one sub-folder in the platform's legacy images folder
+        //  will hide the legacy system image. Whereas this does not happen with the new type.)
+
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_INTEL_ATOM));
+
+        sdkman.reloadSdk(getLog());
+        assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
+        t = sdkman.getTargets()[0];
+
+        assertEquals(
+                "[SystemImage ABI=armeabi, location in platform legacy='$SDK/platforms/v0_0/images', " +
+                 "SystemImage ABI=x86, location in system image='$SDK/system-images/android-0/x86']",
+                cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
+
+        // Now if we have one new system-image using the same ABI type, it will override the
+        // legacy one. This gives us a good path for updates.
+
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_ARMEABI));
+
+
+        sdkman.reloadSdk(getLog());
+        assertEquals("[PlatformTarget API 0 rev 1]", Arrays.toString(sdkman.getTargets()));
+        t = sdkman.getTargets()[0];
+
+        assertEquals(
+                "[SystemImage ABI=armeabi, location in system image='$SDK/system-images/android-0/armeabi', " +
+                 "SystemImage ABI=x86, location in system image='$SDK/system-images/android-0/x86']",
+                cleanPath(sdkman, Arrays.toString(t.getSystemImages())));
+    }
+
+    /**
+     * Sanitizes the paths used when testing results.
+     * <p/>
+     * The system image text representation contains the absolute path to the SDK.
+     * However the SDK path is actually a randomized location.
+     * We clean it by replacing it by the constant '$SDK'.
+     * Also all the Windows path separators are converted to unix-like / separators.
+     */
+    private String cleanPath(SdkManager sdkman, String string) {
+        return string
+            .replaceAll(Pattern.quote(sdkman.getLocation()), "\\$SDK")      //$NON-NLS-1$
+            .replace('\\', '/');
+    }
 }
index 05143d8..2d2668f 100755 (executable)
@@ -83,7 +83,7 @@ public class SdkManagerTestCase extends TestCase {
      * A empty test method to placate the JUnit test runner, which doesn't
      * like TestCase classes with no test methods.
      */
-    public void testSetup() {
+    public void testPlaceholder() {
     }
 
     /**
@@ -132,7 +132,6 @@ public class SdkManagerTestCase extends TestCase {
      * @throws IOException
      */
     private File makeFakeSdk() throws IOException {
-
         // First we create a temp file to "reserve" the temp directory name we want to use.
         File tmpFile = File.createTempFile(
                 this.getClass().getSimpleName() + '_' + this.getName(), null);
@@ -171,8 +170,8 @@ public class SdkManagerTestCase extends TestCase {
 
         File imagesDir = new File(targetDir, "images");
         imagesDir.mkdirs();
-
         new File(imagesDir, "userdata.img").createNewFile();
+
         File skinsDir = new File(targetDir, "skins");
         File hvgaDir = new File(skinsDir, "HVGA");
         hvgaDir.mkdirs();
@@ -180,6 +179,18 @@ public class SdkManagerTestCase extends TestCase {
     }
 
     /**
+     * Creates the system image folder and places a fake userdata.img in it.
+     *
+     * @param systemImage A system image with a valid location.
+     * @throws IOException if the file fails to be created.
+     */
+    protected void makeSystemImageFolder(ISystemImage systemImage) throws IOException {
+        File imagesDir = systemImage.getLocation();
+        imagesDir.mkdirs();
+        new File(imagesDir, "userdata.img").createNewFile();
+    }
+
+    /**
      * Recursive delete directory. Mostly for fake SDKs.
      *
      * @param root directory to delete
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/LocalSdkParserTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/LocalSdkParserTest.java
new file mode 100755 (executable)
index 0000000..de5309a
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository;
+
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.SdkManagerTestCase;
+import com.android.sdklib.SystemImage;
+import com.android.sdklib.ISystemImage.LocationType;
+
+import java.util.Arrays;
+
+public class LocalSdkParserTest extends SdkManagerTestCase {
+
+    public void testLocalSdkParser_SystemImages() throws Exception {
+        SdkManager sdkman = getSdkManager();
+        LocalSdkParser parser = new LocalSdkParser();
+        MockMonitor monitor = new MockMonitor();
+
+        // By default SdkManagerTestCase creates an SDK with one platform containing
+        // a legacy armeabi system image (this is not a separate system image package)
+
+        assertEquals(
+                "[SDK Platform Android 0.0, API 0, revision 1]",
+                Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
+
+        // Now add a few "platform subfolders" system images and reload the SDK.
+        // This disables the "legacy" mode but it still doesn't create any system image package
+
+        IAndroidTarget t = sdkman.getTargets()[0];
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_PLATFORM_SUBFOLDER, SdkConstants.ABI_ARMEABI_V7A));
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_PLATFORM_SUBFOLDER, SdkConstants.ABI_INTEL_ATOM));
+
+        sdkman.reloadSdk(getLog());
+        t = sdkman.getTargets()[0];
+
+        assertEquals(
+                "[SDK Platform Android 0.0, API 0, revision 1]",
+                Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
+
+        // Now add arm + arm v7a images using the new SDK/system-images.
+        // The local parser will find the 2 system image packages which are associated
+        // with the PlatformTarger in the SdkManager.
+
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_ARMEABI));
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_ARMEABI_V7A));
+
+        sdkman.reloadSdk(getLog());
+
+        assertEquals(
+                "[SDK Platform Android 0.0, API 0, revision 1, " +
+                 "ARM EABI System Image, Android API 0, revision 0, " +
+                 "ARM EABI v7a System Image, Android API 0, revision 0]",
+                Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
+
+        // Now add an x86 image using the new SDK/system-images.
+        // How this time we do NOT reload the SdkManager instance. Instead the parser
+        // will find an unused system image and load it as a "broken package".
+
+        makeSystemImageFolder(new SystemImage(
+                sdkman, t, LocationType.IN_SYSTEM_IMAGE, SdkConstants.ABI_INTEL_ATOM));
+
+        assertEquals(
+                "[SDK Platform Android 0.0, API 0, revision 1, " +
+                 "ARM EABI System Image, Android API 0, revision 0, " +
+                 "ARM EABI v7a System Image, Android API 0, revision 0, " +
+                 "Broken Intel x86 Atom System Image, API 0]",
+                Arrays.toString(parser.parseSdk(sdkman.getLocation(), sdkman, monitor)));
+    }
+
+}
+
index 61febb1..3b742ac 100755 (executable)
@@ -18,7 +18,11 @@ package com.android.sdklib.internal.repository;
 \r
 import com.android.sdklib.AndroidVersion;\r
 import com.android.sdklib.IAndroidTarget;\r
+import com.android.sdklib.ISystemImage;\r
 import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.SystemImage;\r
+import com.android.sdklib.ISystemImage.LocationType;\r
+import com.android.sdklib.io.FileOp;\r
 \r
 import java.util.Map;\r
 \r
@@ -68,6 +72,7 @@ public class MockAddonPackage extends AddonPackage {
         private final IAndroidTarget mParentTarget;\r
         private final int mRevision;\r
         private final String mName;\r
+        private ISystemImage[] mSystemImages;\r
 \r
         public MockAddonTarget(String name, IAndroidTarget parentTarget, int revision) {\r
             mName = name;\r
@@ -95,16 +100,26 @@ public class MockAddonPackage extends AddonPackage {
             return getName();\r
         }\r
 \r
-        public String[] getAbiList() {\r
-            return new String[] { SdkConstants.ABI_ARMEABI };\r
+        public ISystemImage[] getSystemImages() {\r
+            if (mSystemImages == null) {\r
+                SystemImage si = new SystemImage(\r
+                        FileOp.append(getLocation(), SdkConstants.OS_IMAGES_FOLDER),\r
+                        LocationType.IN_PLATFORM_LEGACY,\r
+                        SdkConstants.ABI_ARMEABI);\r
+                mSystemImages = new SystemImage[] { si };\r
+            }\r
+            return mSystemImages;\r
         }\r
 \r
-        public String getImagePath(String abiType) {\r
-            return SdkConstants.OS_IMAGES_FOLDER;\r
+        public ISystemImage getSystemImage(String abiType) {\r
+            if (SdkConstants.ABI_ARMEABI.equals(abiType)) {\r
+                return getSystemImages()[0];\r
+            }\r
+            return null;\r
         }\r
 \r
         public String getLocation() {\r
-            return "";\r
+            return "/sdk/add-ons/addon-" + mName;\r
         }\r
 \r
         public IOptionalLibrary[] getOptionalLibraries() {\r
@@ -116,7 +131,7 @@ public class MockAddonPackage extends AddonPackage {
         }\r
 \r
         public String getPath(int pathId) {\r
-            return null;\r
+            throw new UnsupportedOperationException("Implement this as needed for tests");\r
         }\r
 \r
         public String[] getPlatformLibraries() {\r
index 9b48e20..28026ed 100755 (executable)
@@ -18,7 +18,11 @@ package com.android.sdklib.internal.repository;
 \r
 import com.android.sdklib.AndroidVersion;\r
 import com.android.sdklib.IAndroidTarget;\r
+import com.android.sdklib.ISystemImage;\r
 import com.android.sdklib.SdkConstants;\r
+import com.android.sdklib.SystemImage;\r
+import com.android.sdklib.ISystemImage.LocationType;\r
+import com.android.sdklib.io.FileOp;\r
 \r
 import java.util.Map;\r
 \r
@@ -30,6 +34,7 @@ class MockPlatformTarget implements IAndroidTarget {
 \r
     private final int mApiLevel;\r
     private final int mRevision;\r
+    private ISystemImage[] mSystemImages;\r
 \r
     public MockPlatformTarget(int apiLevel, int revision) {\r
         mApiLevel = apiLevel;\r
@@ -56,16 +61,26 @@ class MockPlatformTarget implements IAndroidTarget {
         return getName();\r
     }\r
 \r
-    public String[] getAbiList() {\r
-        return new String[] { SdkConstants.ABI_ARMEABI };\r
+    public ISystemImage[] getSystemImages() {\r
+        if (mSystemImages == null) {\r
+            SystemImage si = new SystemImage(\r
+                    FileOp.append(getLocation(), SdkConstants.OS_IMAGES_FOLDER),\r
+                    LocationType.IN_PLATFORM_LEGACY,\r
+                    SdkConstants.ABI_ARMEABI);\r
+            mSystemImages = new SystemImage[] { si };\r
+        }\r
+        return mSystemImages;\r
     }\r
 \r
-    public String getImagePath(String abiType) {\r
-        return SdkConstants.OS_IMAGES_FOLDER;\r
+    public ISystemImage getSystemImage(String abiType) {\r
+        if (SdkConstants.ABI_ARMEABI.equals(abiType)) {\r
+            return getSystemImages()[0];\r
+        }\r
+        return null;\r
     }\r
 \r
     public String getLocation() {\r
-        return "";\r
+        return "/sdk/platforms/android-" + getVersion().getApiString();\r
     }\r
 \r
     public IOptionalLibrary[] getOptionalLibraries() {\r
@@ -77,7 +92,7 @@ class MockPlatformTarget implements IAndroidTarget {
     }\r
 \r
     public String getPath(int pathId) {\r
-        return null;\r
+        throw new UnsupportedOperationException("Implement this as needed for tests");\r
     }\r
 \r
     public String[] getPlatformLibraries() {\r
index 98a7286..2359add 100644 (file)
@@ -20,6 +20,7 @@ import com.android.io.FileWrapper;
 import com.android.prefs.AndroidLocation.AndroidLocationException;
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.ISdkLog;
+import com.android.sdklib.ISystemImage;
 import com.android.sdklib.SdkConstants;
 import com.android.sdklib.SdkManager;
 import com.android.sdklib.internal.avd.AvdInfo;
@@ -709,8 +710,9 @@ final class AvdCreationDialog extends GridDialog {
         }
 
         // select the abi type
-        if (target != null && target.getAbiList().length > 0) {
-            mAbiTypeCombo.setEnabled(target.getAbiList().length > 1);
+        ISystemImage[] systemImages = getSystemImages(target);
+        if (target != null && systemImages.length > 0) {
+            mAbiTypeCombo.setEnabled(systemImages.length > 1);
             String abiType = AvdInfo.getPrettyAbiType(mEditAvdInfo.getAbiType());
             int n = mAbiTypeCombo.getItemCount();
             for (int i = 0; i < n; i++) {
@@ -954,9 +956,10 @@ final class AvdCreationDialog extends GridDialog {
        if (index >= 0) {
            String targetName = mTargetCombo.getItem(index);
            IAndroidTarget target = mCurrentTargets.get(targetName);
-           String[] arches = target.getAbiList();
 
-           mAbiTypeCombo.setEnabled(arches.length > 1);
+           ISystemImage[] systemImages = getSystemImages(target);
+
+           mAbiTypeCombo.setEnabled(systemImages.length > 1);
 
            // If user explicitly selected an ABI before, preserve that option
            // If user did not explicitly select before (only one option before)
@@ -969,8 +972,8 @@ final class AvdCreationDialog extends GridDialog {
            mAbiTypeCombo.removeAll();
 
            int i;
-           for ( i = 0; i < arches.length ; i++ ) {
-               String prettyAbiType = AvdInfo.getPrettyAbiType(arches[i]);
+           for ( i = 0; i < systemImages.length ; i++ ) {
+               String prettyAbiType = AvdInfo.getPrettyAbiType(systemImages[i].getAbiType());
                mAbiTypeCombo.add(prettyAbiType);
                if (!found) {
                    found = prettyAbiType.equals(selected);
@@ -980,7 +983,7 @@ final class AvdCreationDialog extends GridDialog {
                }
            }
 
-           if (arches.length == 1) {
+           if (systemImages.length == 1) {
                mAbiTypeCombo.select(0);
            }
        }
@@ -1015,8 +1018,9 @@ final class AvdCreationDialog extends GridDialog {
             int index = mTargetCombo.getSelectionIndex();
             String targetName = mTargetCombo.getItem(index);
             IAndroidTarget target = mCurrentTargets.get(targetName);
-            if (target.getAbiList().length > 1 && mAbiTypeCombo.getSelectionIndex() < 0) {
-               error = "An abi type must be selected in order to create an AVD.";
+            ISystemImage[] systemImages = getSystemImages(target);
+            if (systemImages.length > 1 && mAbiTypeCombo.getSelectionIndex() < 0) {
+               error = "An ABI type must be selected in order to create an AVD.";
             }
         }
 
@@ -1253,7 +1257,8 @@ final class AvdCreationDialog extends GridDialog {
 
         // get the abi type
         mAbiType = SdkConstants.ABI_ARMEABI;
-        if (target.getAbiList().length > 0) {
+        ISystemImage[] systemImages = getSystemImages(target);
+        if (systemImages.length > 0) {
             int abiIndex = mAbiTypeCombo.getSelectionIndex();
             if (abiIndex >= 0) {
                 String prettyname = mAbiTypeCombo.getItem(abiIndex);
@@ -1349,6 +1354,32 @@ final class AvdCreationDialog extends GridDialog {
         return success;
     }
 
+    /**
+     * Returns the list of system images of a target.
+     * <p/>
+     * If target is null, returns an empty list.
+     * If target is an add-on with no system images, return the list from its parent platform.
+     *
+     * @param target An IAndroidTarget. Can be null.
+     * @return A non-null ISystemImage array. Can be empty.
+     */
+    private ISystemImage[] getSystemImages(IAndroidTarget target) {
+        if (target != null) {
+            ISystemImage[] images = target.getSystemImages();
+
+            if ((images == null || images.length == 0) && !target.isPlatform()) {
+                // If an add-on does not provide any system images, use the ones from the parent.
+                images = target.getParent().getSystemImages();
+            }
+
+            if (images != null) {
+                return images;
+            }
+        }
+
+        return new ISystemImage[0];
+    }
+
     // End of hiding from SWT Designer
     //$hide<<$
 }