merge in studio-1.0-release history after reset to 8c142f811a8d2dfce7ecd6ced5c529b8737b9599
diff --git a/adt-branding/src/idea/AndroidStudioApplicationInfo.xml b/adt-branding/src/idea/AndroidStudioApplicationInfo.xml
index e3157a2..5e351c0 100755
--- a/adt-branding/src/idea/AndroidStudioApplicationInfo.xml
+++ b/adt-branding/src/idea/AndroidStudioApplicationInfo.xml
@@ -14,7 +14,7 @@
   ~ limitations under the License.
   -->
 <component>
-  <version codename="Beta" major="0" minor="9.1" eap="false"/>
+  <version codename="Beta" major="0" minor="9.2" eap="false"/>
   <company name="Google Inc." url="http://developer.android.com"/>
   <build number="__BUILD_NUMBER__" date="__BUILD_DATE__" apiVersion="135.1286"/>
   <install-over minbuild="0.1" maxbuild="999.999999" version="0"/>
diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/GuiTests.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/GuiTests.java
index c691f72..d1a51bc 100644
--- a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/GuiTests.java
+++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/GuiTests.java
@@ -99,7 +99,7 @@
         ApplicationManager.getApplication().runWriteAction(new Runnable() {
           @Override
           public void run() {
-            DefaultSdks.setDefaultAndroidHome(androidSdkPath);
+            DefaultSdks.setDefaultAndroidHome(androidSdkPath, null);
           }
         });
       }
diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/layout/RenderErrorPanelFixture.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/layout/RenderErrorPanelFixture.java
index 56d4fd4..9e6fc0b 100644
--- a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/layout/RenderErrorPanelFixture.java
+++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/layout/RenderErrorPanelFixture.java
@@ -21,6 +21,7 @@
 import com.android.tools.idea.rendering.RenderResult;
 import org.fest.swing.core.Robot;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import static org.junit.Assert.*;
 
@@ -83,4 +84,29 @@
       }
     }
   }
+
+  public boolean haveErrors(boolean includeWarnings) {
+    RenderResult lastResult = myRenderContext.getLastResult();
+    assertNotNull("No render result available", lastResult);
+    RenderLogger logger = lastResult.getLogger();
+
+    return includeWarnings ? logger.hasProblems() : logger.hasErrors();
+  }
+
+  @NotNull
+  public String getErrorHtml() {
+    RenderResult lastResult = myRenderContext.getLastResult();
+    assertNotNull("No render result available", lastResult);
+    RenderLogger logger = lastResult.getLogger();
+
+    if (logger.hasProblems()) {
+      RenderErrorPanel panel = new RenderErrorPanel();
+      String html = panel.showErrors(lastResult);
+      if (html != null) {
+        return html;
+      }
+    }
+
+    return "";
+  }
 }
diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/layout/NewProjectTest.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/layout/NewProjectTest.java
index 8d9cb41..b744006 100644
--- a/android/guiTestSrc/com/android/tools/idea/tests/gui/layout/NewProjectTest.java
+++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/layout/NewProjectTest.java
@@ -25,6 +25,7 @@
 import com.android.tools.idea.tests.gui.framework.fixture.IdeFrameFixture;
 import com.android.tools.idea.tests.gui.framework.fixture.InspectionsFixture;
 import com.android.tools.idea.tests.gui.framework.fixture.layout.LayoutEditorFixture;
+import com.android.tools.idea.tests.gui.framework.fixture.layout.RenderErrorPanelFixture;
 import com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard.ConfigureAndroidProjectStepFixture;
 import com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard.NewProjectWizardFixture;
 import com.intellij.openapi.module.Module;
@@ -39,6 +40,7 @@
 import java.io.IOException;
 
 import static com.android.tools.idea.wizard.FormFactorUtils.FormFactor.MOBILE;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -154,6 +156,26 @@
     }
   }
 
+  @Test
+  @IdeGuiTest(closeProjectBeforeExecution = true)
+  public void testStillBuildingMessage() throws Exception {
+    // Creates a new project with minSdk 15, which should use appcompat.
+    // Check that if there are render-error messages on first render,
+    // they don't include "Missing Styles" (should now talk about project building instead)
+    IdeFrameFixture projectFrame = newProject("Test Application").withBriefNames().withMinSdk("15").withoutSync().create();
+    EditorFixture editor = projectFrame.getEditor();
+    editor.open("app/src/main/res/layout/activity_a.xml", EditorFixture.Tab.DESIGN);
+    LayoutEditorFixture layoutEditor = editor.getLayoutEditor(true);
+    assertNotNull(layoutEditor);
+    layoutEditor.waitForNextRenderToFinish();
+
+    RenderErrorPanelFixture renderErrors = layoutEditor.getRenderErrors();
+    String html = renderErrors.getErrorHtml();
+    // We could be showing an error message, but if we do, it should *not* say missing styles
+    // (should only be showing project render errors)
+    assertFalse(html, html.contains("Missing styles"));
+  }
+
   @NotNull
   private NewProjectDescriptor newProject(@NotNull String name) {
     return new NewProjectDescriptor(name);
@@ -168,6 +190,7 @@
     private String myMinSdk = "19";
     private String myName = "TestProject";
     private String myDomain = "com.android";
+    private boolean myWaitForSync = true;
 
     private NewProjectDescriptor(@NotNull String name) {
       withName(name);
@@ -221,6 +244,12 @@
       return this;
     }
 
+    /** Turns off the automatic wait-for-sync that normally happens on {@link #create} */
+    NewProjectDescriptor withoutSync() {
+      myWaitForSync = false;
+      return this;
+    }
+
     /**
      * Creates a project fixture for this description
      */
@@ -245,7 +274,9 @@
       newProjectWizard.clickFinish();
 
       IdeFrameFixture projectFrame = findIdeFrame(myName, projectPath);
-      projectFrame.waitForGradleProjectSyncToFinish();
+      if (myWaitForSync) {
+        projectFrame.waitForGradleProjectSyncToFinish();
+      }
 
       return projectFrame;
     }
diff --git a/android/resources/icons/AndroidIcons.java b/android/resources/icons/AndroidIcons.java
index 0c8bd78..8d1ba0f 100755
--- a/android/resources/icons/AndroidIcons.java
+++ b/android/resources/icons/AndroidIcons.java
@@ -67,17 +67,17 @@
 
   // Form factors
   public static class FormFactors {
-    public static final Icon Wear_16 = load("/icons/wear.png"); // 16x16
-    public static final Icon Car_16 = load("/icons/car.png");   // 16x16
-    public static final Icon Glass_16 = load("/icons/glass.png"); // 16x16
-    public static final Icon Mobile_16 = load("/icons/mobile.png"); // 16x16
-    public static final Icon Tv_16 = load("/icons/tv.png"); // 16x16
+    public static final Icon Wear_16 = load("/icons/wear.png");                          // 16x16
+    public static final Icon Car_16 = load("/icons/car.png");                            // 16x16
+    public static final Icon Glass_16 = load("/icons/glass.png");                        // 16x16
+    public static final Icon Mobile_16 = load("/icons/mobile.png");                      // 16x16
+    public static final Icon Tv_16 = load("/icons/tv.png");                              // 16x16
 
-    public static final Icon Wear_32 = load("/icons/formfactors/wear_32.png");     // 32x32
-    public static final Icon Car_32 = load("/icons/formfactors/car_32.png");       // 32x32
-    public static final Icon Glass_32 = load("/icons/formfactors/glass_32.png");   // 32x32
+    public static final Icon Wear_32 = load("/icons/formfactors/wear_32.png");           // 32x32
+    public static final Icon Car_32 = load("/icons/formfactors/car_32.png");             // 32x32
+    public static final Icon Glass_32 = load("/icons/formfactors/glass_32.png");         // 32x32
     public static final Icon Mobile_32 = load("/icons/formfactors/phone_tablet_32.png"); // 32x32
-    public static final Icon Tv_32 = load("/icons/formfactors/tv_32.png");         // 32x32
+    public static final Icon Tv_32 = load("/icons/formfactors/tv_32.png");               // 32x32
 
     public static final Icon Wear_64 = load("/icons/formfactors/64/wear.png");           // 64x64
     public static final Icon Car_64 = load("/icons/formfactors/64/car.png");             // 64x64
@@ -85,11 +85,11 @@
     public static final Icon Mobile_64 = load("/icons/formfactors/64/phone_tablet.png"); // 64x64
     public static final Icon Tv_64 = load("/icons/formfactors/64/tv.png");               // 64x64
 
-    public static final Icon Wear_128 = load("/icons/wear_128.png");                     // 128x128
-    public static final Icon Car_128 = load("/icons/car_128.png");                       // 128x128
-    public static final Icon Glass_128 = load("/icons/glass_128.png");                   // 128x128
-    public static final Icon Mobile_128 = load("/icons/mobile_128.png");                 // 128x128
-    public static final Icon Tv_128 = load("/icons/tv_128.png");                         // 128x128
+    public static final Icon Wear_128 = load("/icons/formfactors/128/wear.png");         // 128x128
+    public static final Icon Car_128 = load("/icons/formfactors/128/car.png");           // 128x128
+    public static final Icon Glass_128 = load("/icons/formfactors/128/glass.png");       // 128x128
+    public static final Icon Mobile_128 = load("/icons/formfactors/128/mobile.png");     // 128x128
+    public static final Icon Tv_128 = load("/icons/formfactors/128/tv.png");             // 128x128
   }
 
   public static class Configs {
diff --git a/android/resources/icons/car_128.png b/android/resources/icons/formfactors/128/car.png
similarity index 100%
rename from android/resources/icons/car_128.png
rename to android/resources/icons/formfactors/128/car.png
Binary files differ
diff --git a/android/resources/icons/formfactors/128/car@2x.png b/android/resources/icons/formfactors/128/car@2x.png
new file mode 100755
index 0000000..728ce0a
--- /dev/null
+++ b/android/resources/icons/formfactors/128/car@2x.png
Binary files differ
diff --git a/android/resources/icons/glass_128.png b/android/resources/icons/formfactors/128/glass.png
similarity index 100%
rename from android/resources/icons/glass_128.png
rename to android/resources/icons/formfactors/128/glass.png
Binary files differ
diff --git a/android/resources/icons/formfactors/128/glass@2x.png b/android/resources/icons/formfactors/128/glass@2x.png
new file mode 100755
index 0000000..3ee9ded
--- /dev/null
+++ b/android/resources/icons/formfactors/128/glass@2x.png
Binary files differ
diff --git a/android/resources/icons/mobile_128.png b/android/resources/icons/formfactors/128/mobile.png
similarity index 100%
rename from android/resources/icons/mobile_128.png
rename to android/resources/icons/formfactors/128/mobile.png
Binary files differ
diff --git a/android/resources/icons/formfactors/128/mobile@2x.png b/android/resources/icons/formfactors/128/mobile@2x.png
new file mode 100755
index 0000000..2469efe
--- /dev/null
+++ b/android/resources/icons/formfactors/128/mobile@2x.png
Binary files differ
diff --git a/android/resources/icons/tv_128.png b/android/resources/icons/formfactors/128/tv.png
similarity index 100%
rename from android/resources/icons/tv_128.png
rename to android/resources/icons/formfactors/128/tv.png
Binary files differ
diff --git a/android/resources/icons/formfactors/128/tv@2x.png b/android/resources/icons/formfactors/128/tv@2x.png
new file mode 100755
index 0000000..496d2e9
--- /dev/null
+++ b/android/resources/icons/formfactors/128/tv@2x.png
Binary files differ
diff --git a/android/resources/icons/wear_128.png b/android/resources/icons/formfactors/128/wear.png
similarity index 100%
rename from android/resources/icons/wear_128.png
rename to android/resources/icons/formfactors/128/wear.png
Binary files differ
diff --git a/android/resources/icons/formfactors/128/wear@2x.png b/android/resources/icons/formfactors/128/wear@2x.png
new file mode 100755
index 0000000..4ac3a4d
--- /dev/null
+++ b/android/resources/icons/formfactors/128/wear@2x.png
Binary files differ
diff --git a/android/src/META-INF/plugin.xml b/android/src/META-INF/plugin.xml
index 88db329..21ece2d 100755
--- a/android/src/META-INF/plugin.xml
+++ b/android/src/META-INF/plugin.xml
@@ -4,7 +4,7 @@
   <description>
     Supports development of Open Handset Alliance Android applications with IntelliJ IDEA.
   </description>
-  <version>10.0.9.1</version>
+  <version>10.0.9.2</version>
   <vendor>JetBrains</vendor>
   <application-components>
     <component>
@@ -644,6 +644,7 @@
     <spellchecker.support language="Groovy" implementationClass="org.jetbrains.android.spellchecker.AndroidGradleSpellcheckingStrategy" order="first"/>
     <deadCode implementation="org.jetbrains.android.inspections.AndroidComponentEntryPoint"/>
     <testSrcLocator implementation="org.jetbrains.android.run.testing.AndroidTestLocationProvider"/>
+    <methodConflictResolver implementation="com.android.tools.idea.psi.resolve.AndroidMethodConflictResolver"/>
   </extensions>
 
   <extensions defaultExtensionNs="com.intellij.properties">
diff --git a/android/src/com/android/tools/idea/avdmanager/AvdActionPanel.java b/android/src/com/android/tools/idea/avdmanager/AvdActionPanel.java
index ce6aa97..e9d5179 100644
--- a/android/src/com/android/tools/idea/avdmanager/AvdActionPanel.java
+++ b/android/src/com/android/tools/idea/avdmanager/AvdActionPanel.java
@@ -109,7 +109,7 @@
     myOverflowMenuButton.addMouseListener(new MouseAdapter() {
       @Override
       public void mouseClicked(MouseEvent e) {
-        myOverflowMenu.show(myOverflowMenuButton, e.getX(), e.getY());
+        myOverflowMenu.show(myOverflowMenuButton, e.getX() - myOverflowMenu.getPreferredSize().width, e.getY());
       }
     });
   }
diff --git a/android/src/com/android/tools/idea/avdmanager/AvdConfigurationOptionHelpPanel.java b/android/src/com/android/tools/idea/avdmanager/AvdConfigurationOptionHelpPanel.java
index f208915..19d6bde 100644
--- a/android/src/com/android/tools/idea/avdmanager/AvdConfigurationOptionHelpPanel.java
+++ b/android/src/com/android/tools/idea/avdmanager/AvdConfigurationOptionHelpPanel.java
@@ -153,6 +153,7 @@
       put(USE_SNAPSHOT_KEY, "Enable Snapshot").
       put(CUSTOM_SKIN_FILE_KEY, "Custom Hardware Skin").
       put(DISPLAY_NAME_KEY, "AVD Name").
+      put(HAS_HARDWARE_KEYBOARD_KEY, "Enable keyboard input").
       build();
 
   private static Map<Key<?>, String> DESCRIPTIONS = ImmutableMap.<Key<?>, String>builder().
@@ -191,6 +192,8 @@
                               "several \"layouts\" (e.g. \"landscape\" and \"portrait\") corresponding to different orientation " +
                               "/ physical configurations of the emulated device.\n").
     put(DISPLAY_NAME_KEY, "The name of this AVD.").
+    put(HAS_HARDWARE_KEYBOARD_KEY, "Enables you to enter text input and interact with the AVD with your hardware computer keyboard " +
+                                   "instead of a of the on on-screen software keyboard.\n").
     build();
 
   /**
diff --git a/android/src/com/android/tools/idea/avdmanager/AvdDisplayList.java b/android/src/com/android/tools/idea/avdmanager/AvdDisplayList.java
index f4c4b89..fe90b55 100644
--- a/android/src/com/android/tools/idea/avdmanager/AvdDisplayList.java
+++ b/android/src/com/android/tools/idea/avdmanager/AvdDisplayList.java
@@ -107,6 +107,7 @@
     newButton.setText(createAvdAction.getText());
     southPanel.add(newButton, BorderLayout.WEST);
     nonemptyPanel.add(southPanel, BorderLayout.SOUTH);
+    myTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
     myTable.getSelectionModel().addListSelectionListener(this);
     myTable.addMouseListener(myEditingListener);
     myTable.addMouseMotionListener(myEditingListener);
diff --git a/android/src/com/android/tools/idea/avdmanager/AvdEditWizard.java b/android/src/com/android/tools/idea/avdmanager/AvdEditWizard.java
index 13cd6c0..18f2bf4 100644
--- a/android/src/com/android/tools/idea/avdmanager/AvdEditWizard.java
+++ b/android/src/com/android/tools/idea/avdmanager/AvdEditWizard.java
@@ -139,6 +139,7 @@
     state.put(BACK_CAMERA_KEY, properties.get(BACK_CAMERA_KEY.name));
     state.put(NETWORK_LATENCY_KEY, properties.get(NETWORK_LATENCY_KEY.name));
     state.put(NETWORK_SPEED_KEY, properties.get(NETWORK_SPEED_KEY.name));
+    state.put(HAS_HARDWARE_KEYBOARD_KEY, fromIniString(properties.get(HAS_HARDWARE_KEYBOARD_KEY.name)));
     state.put(DISPLAY_NAME_KEY, AvdManagerConnection.getAvdDisplayName(avdInfo));
 
     String skinPath = properties.get(CUSTOM_SKIN_FILE_KEY.name);
diff --git a/android/src/com/android/tools/idea/avdmanager/AvdManagerConnection.java b/android/src/com/android/tools/idea/avdmanager/AvdManagerConnection.java
index 8b2d29d..9d1a870 100644
--- a/android/src/com/android/tools/idea/avdmanager/AvdManagerConnection.java
+++ b/android/src/com/android/tools/idea/avdmanager/AvdManagerConnection.java
@@ -367,7 +367,7 @@
       if (currentInfo != null) {
         avdFolder = new File(currentInfo.getDataFolderPath());
       } else {
-        avdFolder = AvdInfo.getDefaultAvdFolder(ourAvdManager, avdName);
+        avdFolder = AvdInfo.getDefaultAvdFolder(ourAvdManager, avdName, true);
       }
     }
     catch (AndroidLocation.AndroidLocationException e) {
diff --git a/android/src/com/android/tools/idea/avdmanager/AvdWizardConstants.java b/android/src/com/android/tools/idea/avdmanager/AvdWizardConstants.java
index 1e6519f..37c9db1 100644
--- a/android/src/com/android/tools/idea/avdmanager/AvdWizardConstants.java
+++ b/android/src/com/android/tools/idea/avdmanager/AvdWizardConstants.java
@@ -25,6 +25,7 @@
 import com.android.sdklib.devices.Screen;
 import com.android.sdklib.devices.Storage;
 import com.android.sdklib.internal.avd.AvdManager;
+import com.android.sdklib.internal.avd.HardwareProperties;
 import com.android.sdklib.internal.repository.packages.*;
 import com.android.sdklib.repository.descriptors.IdDisplay;
 import com.android.sdklib.repository.descriptors.PkgDesc;
@@ -93,7 +94,7 @@
   public static final Key<Integer> RESOLUTION_HEIGHT_KEY = createKey("ResolutionHeight", STEP, Integer.class);
 
   public static final Key<Boolean> HAS_HARDWARE_BUTTONS_KEY = createKey("HasHardwareButtons", STEP, Boolean.class);
-  public static final Key<Boolean> HAS_HARDWARE_KEYBOARD_KEY = createKey("HasHardwareKeyboard", STEP, Boolean.class);
+  public static final Key<Boolean> HAS_HARDWARE_KEYBOARD_KEY = createKey(HardwareProperties.HW_KEYBOARD, WIZARD, Boolean.class);
   public static final Key<Navigation> NAVIGATION_KEY = createKey("Navigation", STEP, Navigation.class);
 
   public static final Key<Boolean> SUPPORTS_LANDSCAPE_KEY = createKey("SupportsLandscape", STEP, Boolean.class);
diff --git a/android/src/com/android/tools/idea/avdmanager/ConfigureAvdOptionsStep.form b/android/src/com/android/tools/idea/avdmanager/ConfigureAvdOptionsStep.form
index 51584d0..7c35a75 100644
--- a/android/src/com/android/tools/idea/avdmanager/ConfigureAvdOptionsStep.form
+++ b/android/src/com/android/tools/idea/avdmanager/ConfigureAvdOptionsStep.form
@@ -19,7 +19,7 @@
         </properties>
         <border type="none"/>
         <children>
-          <grid id="27dc6" layout-manager="GridLayoutManager" row-count="13" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="5">
+          <grid id="27dc6" layout-manager="GridLayoutManager" row-count="14" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="5">
             <margin top="3" left="3" bottom="3" right="10"/>
             <constraints/>
             <properties/>
@@ -84,7 +84,7 @@
               <grid id="6207e" binding="myStartupOptionsPanel" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
                 <margin top="0" left="0" bottom="0" right="0"/>
                 <constraints>
-                  <grid row="7" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+                  <grid row="7" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="true"/>
                 </constraints>
                 <properties/>
                 <border type="none"/>
@@ -143,7 +143,7 @@
               <grid id="f7319" binding="myCameraPanel" layout-manager="GridLayoutManager" row-count="4" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
                 <margin top="0" left="0" bottom="0" right="0"/>
                 <constraints>
-                  <grid row="11" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+                  <grid row="11" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="true"/>
                 </constraints>
                 <properties/>
                 <border type="none"/>
@@ -223,7 +223,7 @@
               <grid id="62a06" binding="myNetworkPanel" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
                 <margin top="0" left="0" bottom="0" right="0"/>
                 <constraints>
-                  <grid row="12" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+                  <grid row="13" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="true"/>
                 </constraints>
                 <properties/>
                 <border type="none"/>
@@ -296,14 +296,14 @@
               <grid id="38c9" binding="myPerformancePanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
                 <margin top="0" left="0" bottom="0" right="0"/>
                 <constraints>
-                  <grid row="8" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+                  <grid row="8" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="true"/>
                 </constraints>
                 <properties/>
                 <border type="none"/>
                 <children>
                   <component id="937ef" class="com.intellij.ui.components.JBLabel">
                     <constraints>
-                      <grid row="0" column="0" row-span="3" col-span="1" vsize-policy="0" hsize-policy="2" anchor="9" fill="0" indent="0" use-parent-layout="false">
+                      <grid row="0" column="0" row-span="3" col-span="1" vsize-policy="0" hsize-policy="3" anchor="9" fill="0" indent="0" use-parent-layout="false">
                         <preferred-size width="100" height="-1"/>
                       </grid>
                     </constraints>
@@ -331,7 +331,7 @@
                   </component>
                   <component id="9397d" class="com.intellij.ui.components.JBLabel">
                     <constraints>
-                      <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+                      <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
                     </constraints>
                     <properties>
                       <enabled value="false"/>
@@ -368,7 +368,7 @@
               <grid id="70d60" binding="myMemoryAndStoragePanel" layout-manager="GridLayoutManager" row-count="6" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
                 <margin top="0" left="0" bottom="0" right="0"/>
                 <constraints>
-                  <grid row="10" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+                  <grid row="10" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="true"/>
                 </constraints>
                 <properties/>
                 <border type="none"/>
@@ -387,7 +387,7 @@
                   </component>
                   <component id="57934" class="com.intellij.ui.components.JBLabel" binding="myRamLabel">
                     <constraints>
-                      <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+                      <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
                     </constraints>
                     <properties>
                       <text value="RAM:"/>
@@ -395,7 +395,7 @@
                   </component>
                   <component id="6b1e9" class="com.intellij.ui.components.JBLabel" binding="myVmHeapLabel">
                     <constraints>
-                      <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+                      <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
                     </constraints>
                     <properties>
                       <text value="VM heap:"/>
@@ -403,7 +403,7 @@
                   </component>
                   <component id="89fc4" class="com.intellij.ui.components.JBLabel" binding="myInternalStorageLabel">
                     <constraints>
-                      <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+                      <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
                     </constraints>
                     <properties>
                       <text value="Internal Storage:"/>
@@ -434,7 +434,7 @@
                   </grid>
                   <component id="95158" class="com.intellij.ui.components.JBLabel" binding="mySdCardLabel">
                     <constraints>
-                      <grid row="4" column="1" row-span="2" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+                      <grid row="4" column="1" row-span="2" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
                     </constraints>
                     <properties>
                       <anchor value="e383a"/>
@@ -482,14 +482,14 @@
               <grid id="92d7e" binding="mySkinPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
                 <margin top="0" left="0" bottom="0" right="0"/>
                 <constraints>
-                  <grid row="9" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+                  <grid row="9" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="true"/>
                 </constraints>
                 <properties/>
                 <border type="none"/>
                 <children>
                   <component id="14ec1" class="com.intellij.ui.components.JBLabel" binding="mySkinDefinitionLabel">
                     <constraints>
-                      <grid row="1" column="0" row-span="3" col-span="1" vsize-policy="0" hsize-policy="0" anchor="9" fill="0" indent="0" use-parent-layout="false">
+                      <grid row="1" column="0" row-span="3" col-span="1" vsize-policy="0" hsize-policy="3" anchor="9" fill="0" indent="0" use-parent-layout="false">
                         <preferred-size width="100" height="-1"/>
                       </grid>
                     </constraints>
@@ -505,13 +505,45 @@
                   </component>
                   <component id="23213" class="com.android.tools.idea.avdmanager.SkinChooser" binding="mySkinComboBox" custom-create="true">
                     <constraints>
-                      <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
+                      <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
                     </constraints>
                     <properties/>
                   </component>
                   <component id="61e9" class="com.intellij.ui.HyperlinkLabel" binding="myHardwareSkinHelpLabel" custom-create="true">
                     <constraints>
-                      <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+                      <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+                    </constraints>
+                    <properties/>
+                  </component>
+                </children>
+              </grid>
+              <grid id="f9eac" binding="myKeyboardPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+                <margin top="0" left="0" bottom="0" right="0"/>
+                <constraints>
+                  <grid row="12" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="true"/>
+                </constraints>
+                <properties/>
+                <border type="none"/>
+                <children>
+                  <component id="3bf7b" class="com.intellij.ui.components.JBLabel">
+                    <constraints>
+                      <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+                    </constraints>
+                    <properties>
+                      <text value="Keyboard"/>
+                    </properties>
+                  </component>
+                  <component id="29def" class="javax.swing.JCheckBox" binding="myEnableComputerKeyboard">
+                    <constraints>
+                      <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+                    </constraints>
+                    <properties>
+                      <text value="Enable keyboard input"/>
+                    </properties>
+                  </component>
+                  <component id="6abc5" class="javax.swing.JSeparator">
+                    <constraints>
+                      <grid row="1" column="0" row-span="1" col-span="2" vsize-policy="1" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
                     </constraints>
                     <properties/>
                   </component>
diff --git a/android/src/com/android/tools/idea/avdmanager/ConfigureAvdOptionsStep.java b/android/src/com/android/tools/idea/avdmanager/ConfigureAvdOptionsStep.java
index 5d04cf8..843af6b 100644
--- a/android/src/com/android/tools/idea/avdmanager/ConfigureAvdOptionsStep.java
+++ b/android/src/com/android/tools/idea/avdmanager/ConfigureAvdOptionsStep.java
@@ -17,6 +17,7 @@
 
 import com.android.SdkConstants;
 import com.android.resources.Density;
+import com.android.resources.Keyboard;
 import com.android.resources.ScreenOrientation;
 import com.android.resources.ScreenSize;
 import com.android.sdklib.devices.CameraLocation;
@@ -59,6 +60,7 @@
 import static com.android.sdklib.devices.Storage.Unit;
 import static com.android.tools.idea.avdmanager.AvdWizardConstants.*;
 import static com.android.tools.idea.wizard.ScopedStateStore.Key;
+import static com.android.tools.idea.wizard.ScopedStateStore.Scope.WIZARD;
 import static com.android.tools.idea.wizard.ScopedStateStore.createKey;
 
 /**
@@ -112,6 +114,8 @@
   private SkinChooser mySkinComboBox;
   private JPanel myAvdDisplayNamePanel;
   private JBLabel myAvdNameLabel;
+  private JCheckBox myEnableComputerKeyboard;
+  private JPanel myKeyboardPanel;
   private Set<JComponent> myAdvancedOptionsComponents;
   private String myOriginalName;
 
@@ -198,8 +202,8 @@
     registerComponents();
     deregister(getDescriptionText());
     getDescriptionText().setVisible(false);
+    Device device = myState.get(DEVICE_DEFINITION_KEY);
     if (myState.get(DISPLAY_NAME_KEY) == null || myState.get(DISPLAY_NAME_KEY).isEmpty()) {
-      Device device = myState.get(DEVICE_DEFINITION_KEY);
       SystemImageDescription systemImage = myState.get(SYSTEM_IMAGE_KEY);
       assert device != null && systemImage != null;
       String avdName = String.format(Locale.getDefault(), "%1$s API %2$d", device.getDisplayName(), systemImage.getVersion().getApiLevel());
@@ -212,13 +216,14 @@
     myOriginalName = editMode ? myState.get(AvdWizardConstants.DISPLAY_NAME_KEY) : "";
 
     File skinPath = myState.get(CUSTOM_SKIN_FILE_KEY);
-    if (skinPath == null && !editMode) {
-      Device device = myState.get(DEVICE_DEFINITION_KEY);
-      if (device != null) {
-        skinPath = AvdEditWizard.getHardwareSkinPath(device.getDefaultHardware());
-      }
+    if (skinPath == null && !editMode && device != null) {
+      skinPath = AvdEditWizard.getHardwareSkinPath(device.getDefaultHardware());
     }
     myState.put(CUSTOM_SKIN_FILE_KEY, skinPath != null ? skinPath : NO_SKIN);
+
+    if (device.getDefaultHardware().getKeyboard().equals(Keyboard.QWERTY)) {
+      myEnableComputerKeyboard.setEnabled(false);
+    }
   }
 
   private static String uniquifyDisplayName(String name) {
@@ -573,6 +578,12 @@
     myState.put(CUSTOM_SKIN_FILE_KEY, skinFile);
     setControlDescription(mySkinComboBox, myAvdConfigurationOptionHelpPanel.getDescription(CUSTOM_SKIN_FILE_KEY));
 
+    if (!myState.containsKey(HAS_HARDWARE_KEYBOARD_KEY)) {
+      myState.put(HAS_HARDWARE_KEYBOARD_KEY, true);
+    }
+    register(HAS_HARDWARE_KEYBOARD_KEY, myEnableComputerKeyboard);
+    setControlDescription(myEnableComputerKeyboard, myAvdConfigurationOptionHelpPanel.getDescription(HAS_HARDWARE_KEYBOARD_KEY));
+
     invokeUpdate(null);
   }
 
@@ -718,8 +729,8 @@
   };
 
   private void registerAdvancedOptionsVisibility() {
-    myAdvancedOptionsComponents =
-      ImmutableSet.<JComponent>of(myMemoryAndStoragePanel, myCameraPanel, myNetworkPanel, mySkinPanel, myAvdIdLabel, myAvdId);
+    myAdvancedOptionsComponents = ImmutableSet.<JComponent>of(myMemoryAndStoragePanel, myCameraPanel, myNetworkPanel,
+                                                              mySkinPanel, myAvdIdLabel, myAvdId, myKeyboardPanel);
   }
 
   /**
diff --git a/android/src/com/android/tools/idea/avdmanager/SystemImageList.java b/android/src/com/android/tools/idea/avdmanager/SystemImageList.java
index 097370a..0c35e96 100644
--- a/android/src/com/android/tools/idea/avdmanager/SystemImageList.java
+++ b/android/src/com/android/tools/idea/avdmanager/SystemImageList.java
@@ -26,19 +26,20 @@
 import com.android.sdklib.repository.descriptors.PkgDesc;
 import com.android.sdklib.repository.descriptors.PkgType;
 import com.android.sdklib.repository.local.LocalSdk;
+import com.android.sdklib.repository.remote.RemotePkgInfo;
 import com.android.sdklib.repository.remote.RemoteSdk;
 import com.android.tools.idea.sdk.wizard.SdkQuickfixWizard;
 import com.android.utils.ILogger;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.intellij.icons.AllIcons;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.util.Comparing;
-import com.intellij.ui.IdeBorderFactory;
-import com.intellij.ui.JBColor;
-import com.intellij.ui.ScrollPaneFactory;
+import com.intellij.ui.*;
 import com.intellij.ui.SingleSelectionModel;
 import com.intellij.ui.components.JBCheckBox;
 import com.intellij.ui.components.JBLabel;
@@ -60,10 +61,8 @@
 import javax.swing.table.TableCellRenderer;
 import javax.swing.table.TableRowSorter;
 import java.awt.*;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
+import java.awt.event.*;
+import java.awt.font.TextAttribute;
 import java.awt.font.TextLayout;
 import java.util.*;
 import java.util.List;
@@ -114,11 +113,13 @@
     myTable.setSelectionModel(new SingleSelectionModel() {
       @Override
       public void setSelectionInterval(int index0, int index1) {
-        AvdWizardConstants.SystemImageDescription description = myTable.getRow(index0);
-        if (description == null || description.isRemote()) {
-          return;
-        }
         super.setSelectionInterval(index0, index1);
+        TableCellEditor editor = myTable.getCellEditor();
+        if (editor != null) {
+          editor.cancelCellEditing();
+        }
+        myTable.repaint();
+        possiblySwitchEditors(index0, 0);
       }
     });
     myTable.setRowSelectionAllowed(true);
@@ -234,17 +235,13 @@
     Point p = e.getPoint();
     int row = myTable.rowAtPoint(p);
     int col = myTable.columnAtPoint(p);
+    possiblySwitchEditors(row, col);
+  }
+
+  private void possiblySwitchEditors(int row, int col) {
     if (row != myTable.getEditingRow() || col != myTable.getEditingColumn()) {
-      if (myTable.isEditing()) {
-        TableCellEditor editor = myTable.getCellEditor();
-        if (!editor.stopCellEditing()) {
-          editor.cancelCellEditing();
-        }
-      }
-      if (!myTable.isEditing()) {
-        if (row != -1 && col != -1 && myTable.isCellEditable(row, col)) {
-          myTable.editCellAt(row, col);
-        }
+      if (row != -1 && col != -1 && myTable.isCellEditable(row, col)) {
+        myTable.editCellAt(row, col);
       }
     }
   }
@@ -296,24 +293,30 @@
     }
 
     SdkSources sources = myRemoteSdk.fetchSources(RemoteSdk.DEFAULT_EXPIRATION_PERIOD_MS, ILOG);
-    myRemoteSdk.fetch(sources, ILOG);
-    for (SdkSource source : sources.getAllSources()) {
-      for (com.android.sdklib.internal.repository.packages.Package pack : source.getPackages()) {
-        if (!(pack instanceof SystemImagePackage)) {
-          continue;
-        }
-        AvdWizardConstants.SystemImageDescription desc = new AvdWizardConstants.SystemImageDescription(pack);
-        ImageFingerprint probe = new ImageFingerprint();
-        probe.version = desc.getVersion();
-        probe.tag = desc.getTag();
-        probe.abiType = desc.getAbiType();
-        // If we don't have a filter or this image passes the filter
-        if (!seen.contains(probe) && (myFilter == null || myFilter.apply(desc))) {
-          items.add(desc);
+    Multimap<PkgType, RemotePkgInfo> packages = myRemoteSdk.fetch(sources, ILOG);
+    if (packages.isEmpty()) {
+      // Couldn't get packages. Disable display.
+      myShowRemoteCheckbox.setEnabled(false);
+      myShowRemoteCheckbox.setSelected(false);
+    }
+    else {
+      for (SdkSource source : sources.getAllSources()) {
+        for (com.android.sdklib.internal.repository.packages.Package pack : source.getPackages()) {
+          if (!(pack instanceof SystemImagePackage)) {
+            continue;
+          }
+          AvdWizardConstants.SystemImageDescription desc = new AvdWizardConstants.SystemImageDescription(pack);
+          ImageFingerprint probe = new ImageFingerprint();
+          probe.version = desc.getVersion();
+          probe.tag = desc.getTag();
+          probe.abiType = desc.getAbiType();
+          // If we don't have a filter or this image passes the filter
+          if (!seen.contains(probe) && (myFilter == null || myFilter.apply(desc))) {
+            items.add(desc);
+          }
         }
       }
     }
-
     myModel.setItems(items);
   }
 
@@ -361,6 +364,9 @@
   @Override
   public void valueChanged(ListSelectionEvent e) {
     AvdWizardConstants.SystemImageDescription selected = myTable.getSelectedObject();
+    if (selected != null && selected.isRemote()) {
+      selected = null;
+    }
     for (SystemImageSelectionListener listener : myListeners) {
       listener.onSystemImageSelected(selected);
     }
@@ -465,8 +471,12 @@
       @Override
       public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
         JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-        if (table.getSelectedRow() == row) {
-          panel.setBackground(table.getSelectionBackground());
+        if (isSelected) {
+          if (image.isRemote()) {
+            panel.setBackground(UIUtil.getListUnfocusedSelectionBackground());
+          } else {
+            panel.setBackground(table.getSelectionBackground());
+          }
           panel.setForeground(table.getSelectionForeground());
           panel.setOpaque(true);
         }
@@ -481,7 +491,8 @@
           label.setFont(labelFont.deriveFont(Font.BOLD));
         }
         if (image.isRemote()) {
-          label.setFont(labelFont.deriveFont(label.getFont().getStyle() | Font.ITALIC));
+          Font font = labelFont.deriveFont(label.getFont().getStyle() | Font.ITALIC);
+          label.setFont(font);
           label.setForeground(UIUtil.getLabelDisabledForeground());
           // on OS X the actual text width isn't computed correctly. Compensating for that..
           if (!label.getText().isEmpty()) {
@@ -492,32 +503,32 @@
               label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, offset));
             }
           }
+          panel.addKeyListener(new KeyAdapter() {
+            @Override
+            public void keyTyped(KeyEvent e) {
+              if (e.getKeyChar() == KeyEvent.VK_ENTER || e.getKeyChar() == KeyEvent.VK_SPACE) {
+                downloadImage(image);
+              }
+            }
+          });
         }
         panel.add(label);
         if (image.isRemote() && column == 0) {
-          JBLabel link = new JBLabel("Download");
+          final JBLabel link = new JBLabel("Download");
           link.setBackground(table.getBackground());
           link.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
           link.setForeground(JBColor.BLUE);
+          Font font = link.getFont();
+          if (isSelected) {
+            Map<TextAttribute, Integer> attrs = Maps.newHashMap();
+            attrs.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
+            font = font.deriveFont(attrs);
+          }
+          link.setFont(font);
           link.addMouseListener(new MouseAdapter() {
             @Override
             public void mouseClicked(MouseEvent e) {
-              IPkgDesc remote = image.getRemotePackage().getPkgDesc();
-              IPkgDesc request = null;
-              if (remote.getType().equals(PkgType.PKG_SYS_IMAGE)) {
-                request =
-                  PkgDesc.Builder.newSysImg(remote.getAndroidVersion(), remote.getTag(), remote.getPath(), remote.getMajorRevision())
-                    .create();
-              }
-              else if (remote.getType().equals(PkgType.PKG_ADDON_SYS_IMAGE)) {
-                request = PkgDesc.Builder.newAddonSysImg(image.getVersion(), remote.getVendor(), image.getTag(), image.getAbiType(),
-                                                         (MajorRevision)image.getRemotePackage().getRevision()).create();
-              }
-              List<IPkgDesc> requestedPackages = Lists.newArrayList(request);
-              SdkQuickfixWizard sdkQuickfixWizard = new SdkQuickfixWizard(null, null, requestedPackages);
-              sdkQuickfixWizard.init();
-              sdkQuickfixWizard.show();
-              refreshImages(true);
+              downloadImage(image);
             }
           });
           panel.add(link);
@@ -542,6 +553,25 @@
 
     }
 
+    private void downloadImage(AvdWizardConstants.SystemImageDescription image) {
+      IPkgDesc remote = image.getRemotePackage().getPkgDesc();
+      IPkgDesc request = null;
+      if (remote.getType().equals(PkgType.PKG_SYS_IMAGE)) {
+        request =
+          PkgDesc.Builder.newSysImg(remote.getAndroidVersion(), remote.getTag(), remote.getPath(), remote.getMajorRevision())
+            .create();
+      }
+      else if (remote.getType().equals(PkgType.PKG_ADDON_SYS_IMAGE)) {
+        request = PkgDesc.Builder.newAddonSysImg(image.getVersion(), remote.getVendor(), image.getTag(), image.getAbiType(),
+                                                 (MajorRevision)image.getRemotePackage().getRevision()).create();
+      }
+      List<IPkgDesc> requestedPackages = Lists.newArrayList(request);
+      SdkQuickfixWizard sdkQuickfixWizard = new SdkQuickfixWizard(null, null, requestedPackages);
+      sdkQuickfixWizard.init();
+      sdkQuickfixWizard.show();
+      refreshImages(true);
+    }
+
     @Nullable
     @Override
     public Comparator<AvdWizardConstants.SystemImageDescription> getComparator() {
diff --git a/android/src/com/android/tools/idea/avdmanager/SystemImagePreview.java b/android/src/com/android/tools/idea/avdmanager/SystemImagePreview.java
index 2e6cbc8..c244470 100644
--- a/android/src/com/android/tools/idea/avdmanager/SystemImagePreview.java
+++ b/android/src/com/android/tools/idea/avdmanager/SystemImagePreview.java
@@ -217,7 +217,7 @@
       // Paint the help link
       g2d.setFont(AvdWizardConstants.STANDARD_FONT);
       g2d.setColor(JBColor.BLUE);
-      g2d.drawString("? See documentation for Android " + myDistribution.getVersion().toShortString() + " APIs", PADDING,
+      g2d.drawString("? - See documentation for Android " + myDistribution.getVersion().toShortString() + " APIs", PADDING,
                      getHeight() - PADDING);
     }
   }
diff --git a/android/src/com/android/tools/idea/designer/SegmentType.java b/android/src/com/android/tools/idea/designer/SegmentType.java
index 82e9ab9..7e08af9 100644
--- a/android/src/com/android/tools/idea/designer/SegmentType.java
+++ b/android/src/com/android/tools/idea/designer/SegmentType.java
@@ -16,6 +16,7 @@
 package com.android.tools.idea.designer;
 
 import com.intellij.android.designer.model.RadViewComponent;
+import com.intellij.android.designer.model.layout.TextDirection;
 import com.intellij.designer.utils.Position;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -27,6 +28,14 @@
  */
 public enum SegmentType {
   /**
+   * Segment is on start side (left if LTR, right if RTL).
+   */
+  @NotNull START,
+  /**
+   * Segment is on end side (right if LTR, left if RTL).
+   */
+  @NotNull END,
+  /**
    * Segment is on the left edge
    */
   @NotNull LEFT,
@@ -70,11 +79,23 @@
    * @param bounds the bounds of the node
    * @return the X coordinate for an edge of this type given its bounds
    */
-  public int getX(@SuppressWarnings("UnusedParameters") @Nullable RadViewComponent node, @NotNull Rectangle bounds) {
+  public int getX(@NotNull TextDirection textDirection,
+                  @SuppressWarnings("UnusedParameters") @Nullable RadViewComponent node,
+                  @NotNull Rectangle bounds) {
+    SegmentType me = this;
+    switch(this) {
+      case START:
+        me = textDirection == TextDirection.RIGHT_TO_LEFT ? RIGHT : LEFT;
+        break;
+      case END:
+        me = textDirection == TextDirection.RIGHT_TO_LEFT ? LEFT : RIGHT;
+        break;
+    }
+
     // We pass in the bounds rather than look it up via node.getBounds() because
     // during a resize or move operation, we call this method to look up proposed
     // bounds rather than actual bounds
-    switch (this) {
+    switch (me) {
       case RIGHT:
         return bounds.x + bounds.width;
       case TOP:
diff --git a/android/src/com/android/tools/idea/gradle/GradleSyncState.java b/android/src/com/android/tools/idea/gradle/GradleSyncState.java
index 07f821f..c38285f 100644
--- a/android/src/com/android/tools/idea/gradle/GradleSyncState.java
+++ b/android/src/com/android/tools/idea/gradle/GradleSyncState.java
@@ -163,7 +163,6 @@
           }
         }
 
-        notifications.updateAllNotifications();
         BuildVariantView.getInstance(myProject).updateContents();
       }
     });
diff --git a/android/src/com/android/tools/idea/gradle/eclipse/AdtImportSdkStep.java b/android/src/com/android/tools/idea/gradle/eclipse/AdtImportSdkStep.java
index cf41d87..c4785f2b 100644
--- a/android/src/com/android/tools/idea/gradle/eclipse/AdtImportSdkStep.java
+++ b/android/src/com/android/tools/idea/gradle/eclipse/AdtImportSdkStep.java
@@ -32,7 +32,7 @@
     super(context);
     // TODO: Pass in a context here which allows the configurable to request
     // validation when the text field is edited
-    myConfigurable = new DefaultSdksConfigurable(null);
+    myConfigurable = new DefaultSdksConfigurable(null, null);
   }
 
   @Override
diff --git a/android/src/com/android/tools/idea/gradle/eclipse/GradleImport.java b/android/src/com/android/tools/idea/gradle/eclipse/GradleImport.java
index 2295ee3..ac88b66 100755
--- a/android/src/com/android/tools/idea/gradle/eclipse/GradleImport.java
+++ b/android/src/com/android/tools/idea/gradle/eclipse/GradleImport.java
@@ -343,7 +343,7 @@
    * @return true if it is known to be a source file
    */
   @VisibleForTesting
-  static boolean isTextFile(@NonNull File file) {
+  public static boolean isTextFile(@NonNull File file) {
     String name = file.getName();
     return name.endsWith(DOT_JAVA) ||
            name.endsWith(DOT_XML) ||
@@ -1491,7 +1491,7 @@
                       @Nullable CopyHandler handler,
                       boolean updateEncoding,
                       @Nullable ImportModule sourceModule) throws IOException {
-    if (handler != null && handler.handle(source, dest)) {
+    if (handler != null && handler.handle(source, dest, updateEncoding, sourceModule)) {
       return;
     }
     if (source.isDirectory()) {
@@ -1617,8 +1617,14 @@
     /**
      * Optionally handle the given file; returns true if the file has been
      * handled
+     *
+     * @param source         the source file/directory to copy
+     * @param dest           the destination for that file
+     * @param updateEncoding if false, do not try to rewrite encodings to UTF-8
+     * @param sourceModule   if non null, a corresponding module this source file belongs to
      */
-    boolean handle(@NonNull File source, @NonNull File dest) throws IOException;
+    boolean handle(@NonNull File source, @NonNull File dest, boolean updateEncoding, @Nullable ImportModule sourceModule)
+      throws IOException;
   }
 
   private static class ImportException extends RuntimeException {
diff --git a/android/src/com/android/tools/idea/gradle/eclipse/ImportModule.java b/android/src/com/android/tools/idea/gradle/eclipse/ImportModule.java
index fa7f202..d99dfc3 100644
--- a/android/src/com/android/tools/idea/gradle/eclipse/ImportModule.java
+++ b/android/src/com/android/tools/idea/gradle/eclipse/ImportModule.java
@@ -20,6 +20,7 @@
 import com.android.annotations.Nullable;
 import com.android.ide.common.repository.GradleCoordinate;
 import com.android.ide.common.repository.SdkMavenRepository;
+import com.android.resources.ResourceFolderType;
 import com.android.sdklib.AndroidVersion;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -31,9 +32,9 @@
 import java.util.*;
 
 import static com.android.SdkConstants.*;
-import static com.android.tools.idea.gradle.eclipse.GradleImport.ECLIPSE_DOT_CLASSPATH;
-import static com.android.tools.idea.gradle.eclipse.GradleImport.ECLIPSE_DOT_PROJECT;
+import static com.android.tools.idea.gradle.eclipse.GradleImport.*;
 import static java.io.File.separator;
+import static java.io.File.separatorChar;
 
 abstract class ImportModule implements Comparable<ImportModule> {
   @SuppressWarnings("SpellCheckingInspection")
@@ -460,7 +461,44 @@
       if (srcRes != null && srcRes.exists()) {
         File destRes = new File(main, FD_RES);
         myImporter.mkdirs(destRes);
-        myImporter.copyDir(srcRes, destRes, null, true, this);
+        myImporter.copyDir(srcRes, destRes, new GradleImport.CopyHandler() {
+          @Override
+          public boolean handle(@NonNull File source, @NonNull File dest, boolean updateEncoding, @Nullable ImportModule sourceModule)
+              throws IOException {
+            // Resource files in non-value folders should use only lower case characters
+            if (hasUpperCaseExtension(dest) && !isIgnoredFile(source)) {
+              File parentFile = source.getParentFile();
+              if (parentFile != null) {
+                ResourceFolderType folderType = ResourceFolderType.getFolderType(parentFile.getName());
+                if (folderType != ResourceFolderType.VALUES) {
+                  String name = dest.getName();
+                  int dot = name.indexOf('.');
+                  if (dot != -1) {
+                    name = name.substring(0, dot) + name.substring(dot).toLowerCase(Locale.US);
+                    File destParent = dest.getParentFile();
+                    dest = destParent != null ? new File(destParent, name) : new File(name);
+                    if (updateEncoding && isTextFile(source)) {
+                      myImporter.copyTextFile(sourceModule, source, dest);
+                    } else {
+                      Files.copy(source, dest);
+                    }
+                    if (sourceModule != null) {
+                      // Just use the names rather than the full paths to make it clear that this was just
+                      // a file renaming (even though there is also a move happening for all resources including
+                      // these. In other words, instead of displaying
+                      // * res/drawable-hdpi/other_icon.PNG => app/src/main/res/drawable-hdpi/other_icon.png
+                      // we display
+                      // * other_icon.PNG => app/src/main/res/drawable-hdpi/other_icon.png
+                      myImporter.getSummary().reportMoved(sourceModule, new File(source.getName()), new File(name));
+                    }
+                    return true;
+                  }
+                }
+              }
+            }
+            return false;
+          }
+        }, true, this);
         summary.reportMoved(this, srcRes, destRes);
         recordCopiedFile(copied, srcRes);
       }
@@ -499,7 +537,8 @@
         // Handle moving .rs/.rsh/.fs files to main/rs/ and .aidl files to the
         // corresponding aidl package under main/aidl
         @Override
-        public boolean handle(@NonNull File source, @NonNull File dest) throws IOException {
+        public boolean handle(@NonNull File source, @NonNull File dest, boolean updateEncoding, @Nullable ImportModule sourceModule)
+            throws IOException {
           String sourcePath = source.getPath();
           if (sourcePath.endsWith(DOT_AIDL)) {
             File aidlDir = new File(main, FD_AIDL);
@@ -627,6 +666,25 @@
     reportIgnored(copied);
   }
 
+  private static boolean hasUpperCaseExtension(@NonNull File file) {
+    String path = file.getPath();
+    int index = path.lastIndexOf(separatorChar);
+    // Can be -1 (if this is just a file name, with no path); the below still works since we start from the next char (0)
+    index = path.indexOf('.', index + 1);
+    if (index == -1) {
+      return false; // no extension in the file name
+    }
+    index++;
+
+    for (; index < path.length(); index++) {
+      if (Character.isUpperCase(path.charAt(index))) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
   private void reportIgnored(Set<File> copied) throws IOException {
     File canonicalDir = getCanonicalModuleDir();
 
diff --git a/android/src/com/android/tools/idea/gradle/project/AndroidGradleProjectData.java b/android/src/com/android/tools/idea/gradle/project/AndroidGradleProjectData.java
index 7622bd8..4b425ab 100644
--- a/android/src/com/android/tools/idea/gradle/project/AndroidGradleProjectData.java
+++ b/android/src/com/android/tools/idea/gradle/project/AndroidGradleProjectData.java
@@ -247,7 +247,7 @@
     return false;
   }
 
-  private static boolean needsAndroidSdkSync(@NotNull Project project) {
+  private static boolean needsAndroidSdkSync(@NotNull final Project project) {
     if (AndroidStudioSpecificInitializer.isAndroidStudio()) {
       final File ideSdkPath = DefaultSdks.getDefaultAndroidHome();
       if (ideSdkPath != null) {
@@ -256,7 +256,7 @@
           ApplicationManager.getApplication().runWriteAction(new Runnable() {
             @Override
             public void run() {
-              DefaultSdks.setDefaultAndroidHome(ideSdkPath, DefaultSdks.getDefaultJdk());
+              DefaultSdks.setDefaultAndroidHome(ideSdkPath, DefaultSdks.getDefaultJdk(), project);
             }
           });
           return true;
diff --git a/android/src/com/android/tools/idea/gradle/project/PostProjectSetupTasksExecutor.java b/android/src/com/android/tools/idea/gradle/project/PostProjectSetupTasksExecutor.java
index 0622adc..8d4df03 100755
--- a/android/src/com/android/tools/idea/gradle/project/PostProjectSetupTasksExecutor.java
+++ b/android/src/com/android/tools/idea/gradle/project/PostProjectSetupTasksExecutor.java
@@ -19,6 +19,7 @@
 import com.android.builder.model.AndroidProject;
 import com.android.sdklib.AndroidTargetHash;
 import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.repository.FullRevision;
 import com.android.sdklib.repository.descriptors.IPkgDesc;
 import com.android.sdklib.repository.descriptors.PkgDesc;
@@ -48,6 +49,7 @@
 import com.android.tools.idea.sdk.VersionCheck;
 import com.android.tools.idea.sdk.wizard.SdkQuickfixWizard;
 import com.android.tools.idea.startup.AndroidStudioSpecificInitializer;
+import com.android.tools.idea.startup.ExternalAnnotationsSupport;
 import com.android.tools.idea.templates.TemplateManager;
 import com.google.common.base.Joiner;
 import com.google.common.collect.Iterables;
@@ -59,6 +61,7 @@
 import com.intellij.openapi.externalSystem.util.ExternalSystemConstants;
 import com.intellij.openapi.module.*;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.ProjectJdkTable;
 import com.intellij.openapi.projectRoots.Sdk;
 import com.intellij.openapi.projectRoots.SdkAdditionalData;
 import com.intellij.openapi.projectRoots.SdkModificator;
@@ -66,6 +69,7 @@
 import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
 import com.intellij.openapi.roots.libraries.Library;
 import com.intellij.openapi.roots.libraries.LibraryTable;
+import com.intellij.openapi.roots.libraries.ui.OrderRoot;
 import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.StandardFileSystems;
@@ -76,6 +80,8 @@
 import com.intellij.util.io.URLUtil;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.android.sdk.AndroidSdkAdditionalData;
+import org.jetbrains.android.sdk.AndroidSdkData;
+import org.jetbrains.android.sdk.AndroidSdkUtils;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.plugins.gradle.util.GradleConstants;
@@ -85,6 +91,7 @@
 import java.util.List;
 import java.util.Set;
 
+import static com.android.SdkConstants.FN_FRAMEWORK_LIBRARY;
 import static com.android.tools.idea.gradle.messages.CommonMessageGroupNames.FAILED_TO_SET_UP_SDK;
 import static com.android.tools.idea.gradle.service.notification.errors.AbstractSyncErrorHandler.FAILED_TO_SYNC_GRADLE_PROJECT_ERROR_GROUP_FORMAT;
 import static com.android.tools.idea.gradle.util.Projects.hasErrors;
@@ -110,8 +117,23 @@
     myProject = project;
   }
 
-
   public void onProjectRestoreFromDisk() {
+    ensureValidSdks();
+
+    if (hasErrors(myProject)) {
+      addSdkLinkIfNecessary();
+      checkSdkVersion(myProject);
+      return;
+    }
+
+    findAndShowVariantConflicts();
+    addSdkLinkIfNecessary();
+    checkSdkVersion(myProject);
+
+    TemplateManager.getInstance().refreshDynamicTemplateMenu(myProject);
+  }
+
+  public void ensureValidSdks() {
     ProjectSyncMessages messages = ProjectSyncMessages.getInstance(myProject);
 
     boolean checkJdkVersion = true;
@@ -124,7 +146,28 @@
       if (androidFacet != null && androidFacet.getIdeaAndroidProject() != null) {
         Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
         if (sdk != null && !invalidAndroidSdks.contains(sdk) && isMissingAndroidLibrary(sdk)) {
-          invalidAndroidSdks.add(sdk);
+          // First try to recreate SDK; workaround for issue 78072
+          AndroidSdkAdditionalData additionalData = (AndroidSdkAdditionalData)sdk.getSdkAdditionalData();
+          AndroidSdkData sdkData = AndroidSdkData.getSdkData(sdk);
+          if (additionalData != null && sdkData != null) {
+            IAndroidTarget target = additionalData.getBuildTarget(sdkData);
+            if (target != null) {
+              SdkModificator sdkModificator = sdk.getSdkModificator();
+              sdkModificator.removeAllRoots();
+              for (OrderRoot orderRoot : AndroidSdkUtils.getLibraryRootsForTarget(target, sdk.getHomePath(), true)) {
+                sdkModificator.addRoot(orderRoot.getFile(), orderRoot.getType());
+              }
+              ExternalAnnotationsSupport.attachJdkAnnotations(sdkModificator);
+              sdkModificator.commitChanges();
+            }
+          }
+
+          // If attempting to fix up the roots in the SDK fails, install the target over again
+          // (this is a truly corrupt install, as opposed to an incorrectly synced SDK which the
+          // above workaround deals with)
+          if (isMissingAndroidLibrary(sdk)) {
+            invalidAndroidSdks.add(sdk);
+          }
         }
 
         IdeaAndroidProject androidProject = androidFacet.getIdeaAndroidProject();
@@ -143,21 +186,9 @@
       }
     }
 
-    if (hasErrors(myProject)) {
-      addSdkLinkIfNecessary();
-      checkSdkVersion(myProject);
-      return;
-    }
-
     if (!invalidAndroidSdks.isEmpty()) {
       reinstallMissingPlatforms(invalidAndroidSdks);
     }
-
-    findAndShowVariantConflicts();
-    addSdkLinkIfNecessary();
-    checkSdkVersion(myProject);
-
-    TemplateManager.getInstance().refreshDynamicTemplateMenu(myProject);
   }
 
   private static boolean isMissingAndroidLibrary(@NotNull Sdk sdk) {
@@ -165,12 +196,12 @@
       for (VirtualFile library : sdk.getRootProvider().getFiles(OrderRootType.CLASSES)) {
         // This code does not through the classes in the Android SDK. It iterates through a list of 3 files in the IDEA SDK: android.jar,
         // annotations.jar and res folder.
-        if (!library.exists()) {
-          return true;
+        if (library.getName().equals(FN_FRAMEWORK_LIBRARY) && library.exists()) {
+          return false;
         }
       }
     }
-    return false;
+    return true;
   }
 
   private void reinstallMissingPlatforms(@NotNull Collection<Sdk> invalidAndroidSdks) {
@@ -245,6 +276,8 @@
       myGenerateSourcesAfterSync = DEFAULT_GENERATE_SOURCES_AFTER_SYNC;
     }
 
+    ensureValidSdks();
+
     TemplateManager.getInstance().refreshDynamicTemplateMenu(myProject);
   }
 
diff --git a/android/src/com/android/tools/idea/gradle/project/SdkSync.java b/android/src/com/android/tools/idea/gradle/project/SdkSync.java
index 2d455ad..20986fa 100644
--- a/android/src/com/android/tools/idea/gradle/project/SdkSync.java
+++ b/android/src/com/android/tools/idea/gradle/project/SdkSync.java
@@ -133,7 +133,7 @@
         ApplicationManager.getApplication().runWriteAction(new Runnable() {
           @Override
           public void run() {
-            DefaultSdks.setDefaultAndroidHome(projectAndroidHomePath);
+            DefaultSdks.setDefaultAndroidHome(projectAndroidHomePath, null);
           }
         });
       }
diff --git a/android/src/com/android/tools/idea/gradle/service/notification/errors/MissingPlatformErrorHandler.java b/android/src/com/android/tools/idea/gradle/service/notification/errors/MissingPlatformErrorHandler.java
index 6b9c43b..8235ec1 100644
--- a/android/src/com/android/tools/idea/gradle/service/notification/errors/MissingPlatformErrorHandler.java
+++ b/android/src/com/android/tools/idea/gradle/service/notification/errors/MissingPlatformErrorHandler.java
@@ -20,8 +20,8 @@
 import com.android.sdklib.repository.descriptors.PkgType;
 import com.android.sdklib.repository.local.LocalPkgInfo;
 import com.android.sdklib.repository.local.LocalSdk;
-import com.android.tools.idea.gradle.service.notification.hyperlink.NotificationHyperlink;
 import com.android.tools.idea.gradle.service.notification.hyperlink.InstallPlatformHyperlink;
+import com.android.tools.idea.gradle.service.notification.hyperlink.NotificationHyperlink;
 import com.android.tools.idea.gradle.service.notification.hyperlink.OpenAndroidSdkManagerHyperlink;
 import com.google.common.collect.Lists;
 import com.intellij.facet.ProjectFacetManager;
@@ -75,9 +75,7 @@
           if (pkgInfo != null) {
             loadError = pkgInfo.getLoadError();
           }
-          if (pkgInfo == null || StringUtil.isNotEmpty(loadError)) {
-            hyperlinks.add(new InstallPlatformHyperlink(version));
-          }
+          hyperlinks.add(new InstallPlatformHyperlink(version));
         }
       }
       if (hyperlinks.isEmpty()) {
diff --git a/android/src/com/android/tools/idea/gradle/structure/AndroidProjectStructureConfigurable.java b/android/src/com/android/tools/idea/gradle/structure/AndroidProjectStructureConfigurable.java
index d11deab..e93a431 100644
--- a/android/src/com/android/tools/idea/gradle/structure/AndroidProjectStructureConfigurable.java
+++ b/android/src/com/android/tools/idea/gradle/structure/AndroidProjectStructureConfigurable.java
@@ -100,7 +100,7 @@
   @NotNull private final Wrapper myDetails = new Wrapper();
   @NotNull private final UiState myUiState;
 
-  @NotNull private final DefaultSdksConfigurable mySdksConfigurable = new DefaultSdksConfigurable(this);
+  @NotNull private final DefaultSdksConfigurable mySdksConfigurable;
   @NotNull private final List<Configurable> myConfigurables = Lists.newLinkedList();
 
   private final GradleSettingsFile mySettingsFile;
@@ -153,6 +153,7 @@
   public AndroidProjectStructureConfigurable(@NotNull Project project) {
     myProject = project;
     myUiState = new UiState(project);
+    mySdksConfigurable = new DefaultSdksConfigurable(this, project);
 
     myConfigurables.add(mySdksConfigurable);
     if (!project.isDefault()) {
diff --git a/android/src/com/android/tools/idea/gradle/structure/DefaultSdksConfigurable.java b/android/src/com/android/tools/idea/gradle/structure/DefaultSdksConfigurable.java
index 931e696..1283db2 100644
--- a/android/src/com/android/tools/idea/gradle/structure/DefaultSdksConfigurable.java
+++ b/android/src/com/android/tools/idea/gradle/structure/DefaultSdksConfigurable.java
@@ -24,6 +24,7 @@
 import com.intellij.openapi.fileChooser.PathChooserDialog;
 import com.intellij.openapi.options.BaseConfigurable;
 import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.project.Project;
 import com.intellij.openapi.projectRoots.JavaSdk;
 import com.intellij.openapi.projectRoots.Sdk;
 import com.intellij.openapi.ui.DetailsComponent;
@@ -57,7 +58,9 @@
 public class DefaultSdksConfigurable extends BaseConfigurable implements ValidationAwareConfigurable {
   private static final String CHOOSE_VALID_JDK_DIRECTORY_ERR = "Please choose a valid JDK directory.";
   private static final String CHOOSE_VALID_SDK_DIRECTORY_ERR = "Please choose a valid Android SDK directory.";
-  private final ConfigurableHost myHost;
+
+  @Nullable private final ConfigurableHost myHost;
+  @Nullable private final Project myProject;
 
   // These paths are system-dependent.
   @NotNull private String myOriginalJdkHomePath;
@@ -69,8 +72,9 @@
 
   private DetailsComponent myDetailsComponent;
 
-  public DefaultSdksConfigurable(@Nullable ConfigurableHost host) {
+  public DefaultSdksConfigurable(@Nullable ConfigurableHost host, @Nullable Project project) {
     myHost = host;
+    myProject = project;
     myWholePanel.setPreferredSize(new Dimension(700, 500));
 
     myDetailsComponent = new DetailsComponent();
@@ -96,7 +100,7 @@
     ApplicationManager.getApplication().runWriteAction(new Runnable() {
       @Override
       public void run() {
-        DefaultSdks.setDefaultAndroidHome(getSdkLocation());
+        DefaultSdks.setDefaultAndroidHome(getSdkLocation(), myProject);
         DefaultSdks.setDefaultJavaHome(getJdkLocation());
 
         if (!ApplicationManager.getApplication().isUnitTestMode()) {
diff --git a/android/src/com/android/tools/idea/gradle/util/GradleUtil.java b/android/src/com/android/tools/idea/gradle/util/GradleUtil.java
index aa0662b..891e81c 100644
--- a/android/src/com/android/tools/idea/gradle/util/GradleUtil.java
+++ b/android/src/com/android/tools/idea/gradle/util/GradleUtil.java
@@ -16,6 +16,7 @@
 package com.android.tools.idea.gradle.util;
 
 import com.android.SdkConstants;
+import com.android.annotations.NonNull;
 import com.android.builder.model.*;
 import com.android.ide.common.repository.GradleCoordinate;
 import com.android.sdklib.repository.FullRevision;
@@ -869,4 +870,64 @@
     }
     return null;
   }
+
+  /**
+   * Returns true if the given project depends on the given artifact, which consists of
+   * a group id and an artifact id, such as {@link com.android.SdkConstants#APPCOMPAT_LIB_ARTIFACT}
+   *
+   * @param project the Gradle project to check
+   * @param artifact the artifact
+   * @return true if the project depends on the given artifact (including transitively)
+   */
+  public static boolean dependsOn(@NonNull IdeaAndroidProject project, @NonNull String artifact) {
+    Dependencies dependencies = project.getSelectedVariant().getMainArtifact().getDependencies();
+    return dependsOn(dependencies, artifact);
+
+  }
+
+  /**
+   * Returns true if the given dependencies include the given artifact, which consists of
+   * a group id and an artifact id, such as {@link com.android.SdkConstants#APPCOMPAT_LIB_ARTIFACT}
+   *
+   * @param dependencies the Gradle dependencies object to check
+   * @param artifact the artifact
+   * @return true if the dependencies include the given artifact (including transitively)
+   */
+  public static boolean dependsOn(@NonNull Dependencies dependencies, @NonNull String artifact) {
+    for (AndroidLibrary library : dependencies.getLibraries()) {
+      if (dependsOn(library, artifact, true)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if the given library depends on the given artifact, which consists of
+   * a group id and an artifact id, such as {@link com.android.SdkConstants#APPCOMPAT_LIB_ARTIFACT}
+   *
+   * @param library the Gradle library to check
+   * @param artifact the artifact
+   * @param transitively if false, checks only direct dependencies, otherwise checks transitively
+   * @return true if the project depends on the given artifact
+   */
+  public static boolean dependsOn(@NonNull AndroidLibrary library, @NonNull String artifact, boolean transitively) {
+    MavenCoordinates resolvedCoordinates = library.getResolvedCoordinates();
+    if (resolvedCoordinates != null) {
+      String s = resolvedCoordinates.getGroupId() + ':' + resolvedCoordinates.getArtifactId();
+      if (artifact.equals(s)) {
+        return true;
+      }
+    }
+
+    if (transitively) {
+      for (AndroidLibrary dependency : library.getLibraryDependencies()) {
+        if (dependsOn(dependency, artifact, true)) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
 }
\ No newline at end of file
diff --git a/android/src/com/android/tools/idea/gradle/util/LocalProperties.java b/android/src/com/android/tools/idea/gradle/util/LocalProperties.java
index a9c7d2e..13dcfc9 100644
--- a/android/src/com/android/tools/idea/gradle/util/LocalProperties.java
+++ b/android/src/com/android/tools/idea/gradle/util/LocalProperties.java
@@ -36,6 +36,7 @@
   private static final String HEADER_COMMENT = getHeaderComment();
 
   @NotNull private final File myFilePath;
+  @NotNull private final File myProjectDirPath;
   @NotNull private final Properties myProperties;
 
   @NotNull
@@ -75,6 +76,7 @@
    * @throws IllegalArgumentException if there is already a directory called "local.properties" at the given path.
    */
   public LocalProperties(@NotNull File projectDirPath) throws IOException {
+    myProjectDirPath = projectDirPath;
     myFilePath = new File(projectDirPath, SdkConstants.FN_LOCAL_PROPERTIES);
     myProperties = PropertiesUtil.getProperties(myFilePath);
   }
@@ -86,7 +88,10 @@
   public File getAndroidSdkPath() {
     String path = myProperties.getProperty(SdkConstants.SDK_DIR_PROPERTY);
     if (path != null && !path.isEmpty()) {
-     return new File(FileUtil.toSystemDependentName(path));
+      if (!FileUtil.isAbsolute(path)) {
+        path = new File(myProjectDirPath, path).getPath();
+      }
+      return new File(FileUtil.toSystemDependentName(path));
     }
     return null;
   }
diff --git a/android/src/com/android/tools/idea/psi/resolve/AndroidMethodConflictResolver.java b/android/src/com/android/tools/idea/psi/resolve/AndroidMethodConflictResolver.java
new file mode 100644
index 0000000..4944940
--- /dev/null
+++ b/android/src/com/android/tools/idea/psi/resolve/AndroidMethodConflictResolver.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 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.tools.idea.psi.resolve;
+
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.*;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.infos.CandidateInfo;
+import com.intellij.psi.scope.PsiConflictResolver;
+import org.jetbrains.android.AndroidSdkResolveScopeProvider;
+import org.jetbrains.android.sdk.AndroidSdkUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * There is an android specifics that either <code>*.class</code> or <code>*.java</code> files from android sdk
+ * are included to the resolve scope. That might produce errors like
+ * <a href="https://code.google.com/p/android/issues/detail?id=70135">this</a> or
+ * <a href="https://youtrack.jetbrains.com/issue/IDEA-131368">this one</a>.
+ * <p/>
+ * Android integration tries to prevent that via {@link AndroidSdkResolveScopeProvider.MyJdkScope#compare(VirtualFile, VirtualFile)}
+ * by excluding <code>*.class</code> files from the resolve scope. However, it doesn't work for situation when a super-class' method
+ * is called inside an object of an android-specific class - resolve process goes up the hierarchy and reaches <code>*.class</code>
+ * file then. <code>*.java</code> file is also in resolve scope and we have a conflict.
+ * <p/>
+ * Current conflict resolver encapsulates logic which prefers <code>*.java</code> android sdk info to <code>*.class</code>
+ * android sdk info.
+ */
+public class AndroidMethodConflictResolver implements PsiConflictResolver {
+
+  @Nullable
+  @Override
+  public CandidateInfo resolveConflict(@NotNull List<CandidateInfo> conflicts) {
+    if (conflicts.isEmpty()) {
+      return null;
+    }
+
+    ProjectFileIndex index = null;
+    for (CandidateInfo conflict : conflicts) {
+      PsiElement element = conflict.getElement();
+      if (element != null) {
+        index = ProjectRootManager.getInstance(element.getProject()).getFileIndex();
+        break;
+      }
+    }
+
+    if (index == null) {
+      return null;
+    }
+
+    CandidateInfo sourceInfo = null;
+    Sdk sdk = null;
+    for (CandidateInfo conflict : conflicts) {
+      PsiElement element = conflict.getElement();
+      if (element == null) {
+        continue;
+      }
+      PsiFile psiFile = element.getContainingFile();
+      if (psiFile == null) {
+        continue;
+      }
+      VirtualFile virtualFile = psiFile.getVirtualFile();
+      if (virtualFile == null) {
+        continue;
+      }
+
+      // We want to process a situation only with android sdk classes (either binary or source) which participate in method resolving.
+      List<OrderEntry> orderEntries = index.getOrderEntriesForFile(virtualFile);
+      if (orderEntries.size() != 1) {
+        return null;
+      }
+      OrderEntry orderEntry = orderEntries.get(0);
+      if (!(orderEntry instanceof JdkOrderEntry)) {
+        return null;
+      }
+      Sdk currentSdk = ((JdkOrderEntry)orderEntry).getJdk();
+      if (!AndroidSdkUtils.isAndroidSdk(currentSdk) || (sdk != null && !sdk.equals(currentSdk))) {
+        return null;
+      }
+      sdk = currentSdk;
+
+      if (index.isInLibrarySource(virtualFile)) {
+        if (sourceInfo == null) {
+          sourceInfo = conflict;
+        }
+        else {
+          // Adjust only 'a *.class VS single *.java' case.
+          return null;
+        }
+      }
+    }
+    return sourceInfo;
+  }
+}
diff --git a/android/src/com/android/tools/idea/rendering/AarResourceClassGenerator.java b/android/src/com/android/tools/idea/rendering/AarResourceClassGenerator.java
index 053f63d..3e03c76 100644
--- a/android/src/com/android/tools/idea/rendering/AarResourceClassGenerator.java
+++ b/android/src/com/android/tools/idea/rendering/AarResourceClassGenerator.java
@@ -21,6 +21,7 @@
 import com.android.ide.common.res2.ResourceItem;
 import com.android.resources.ResourceType;
 import com.android.tools.lint.detector.api.ClassContext;
+import org.jetbrains.android.util.AndroidResourceUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.asm4.ClassWriter;
@@ -189,7 +190,7 @@
         Collection<String> keys = myAarResources.getItemsOfType(type);
         for (String key : keys) {
           Integer initialValue = myAppResources.getResourceId(type, key);
-          cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, key, "I",
+          cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, AndroidResourceUtil.getFieldNameByResourceName(key), "I",
                         null, initialValue).visitEnd();
         }
       }
diff --git a/android/src/com/android/tools/idea/rendering/RenderErrorPanel.java b/android/src/com/android/tools/idea/rendering/RenderErrorPanel.java
index 36cc8e6..e85a063 100644
--- a/android/src/com/android/tools/idea/rendering/RenderErrorPanel.java
+++ b/android/src/com/android/tools/idea/rendering/RenderErrorPanel.java
@@ -100,6 +100,7 @@
 import static com.android.ide.common.rendering.api.LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR;
 import static com.android.tools.idea.configurations.RenderContext.UsageType.LAYOUT_EDITOR;
 import static com.android.tools.idea.rendering.HtmlLinkManager.URL_ACTION_CLOSE;
+import static com.android.tools.idea.rendering.RenderLogger.TAG_STILL_BUILDING;
 import static com.android.tools.idea.rendering.ResourceHelper.viewNeedsPackage;
 import static com.android.tools.lint.detector.api.LintUtils.editDistance;
 import static com.android.tools.lint.detector.api.LintUtils.stripIdPrefix;
@@ -682,7 +683,10 @@
   }
 
   private static void reportMissingStyles(RenderLogger logger, HtmlBuilder builder) {
-    if (logger.seenTagPrefix(TAG_RESOURCES_RESOLVE_THEME_ATTR)) {
+    if (logger.seenTagPrefix(TAG_STILL_BUILDING)) {
+      builder.addBold("Project Still Building: May cause rendering errors until the build is done.").newline();
+      builder.newline().newline();
+    } else if (logger.seenTagPrefix(TAG_RESOURCES_RESOLVE_THEME_ATTR)) {
       builder.addBold("Missing styles. Is the correct theme chosen for this layout?").newline();
       builder.addIcon(HtmlBuilderHelper.getTipIconPath());
       builder.add("Use the Theme combo box above the layout to choose a different layout, or fix the theme style references.");
diff --git a/android/src/com/android/tools/idea/rendering/RenderLogger.java b/android/src/com/android/tools/idea/rendering/RenderLogger.java
index 5c0c5ca..844720c 100644
--- a/android/src/com/android/tools/idea/rendering/RenderLogger.java
+++ b/android/src/com/android/tools/idea/rendering/RenderLogger.java
@@ -72,6 +72,7 @@
 
   public static final String TAG_MISSING_DIMENSION = "missing.dimension";
   public static final String TAG_MISSING_FRAGMENT = "missing.fragment";
+  public static final String TAG_STILL_BUILDING = "project.building";
   private static Set<String> ourIgnoredFidelityWarnings;
   private static boolean ourIgnoreAllFidelityWarnings;
   private static boolean ourIgnoreFragments;
@@ -189,6 +190,8 @@
       if (facet != null && facet.isGradleProject()) {
         description = "Still building project; theme resources from libraries may be missing. Layout should refresh when the " +
                       "build is complete.\n\n" + description;
+        tag = TAG_STILL_BUILDING;
+        addTag(tag);
       }
     }
 
diff --git a/android/src/com/android/tools/idea/sdk/DefaultSdks.java b/android/src/com/android/tools/idea/sdk/DefaultSdks.java
index 2aa7cbd..a22dfd1 100644
--- a/android/src/com/android/tools/idea/sdk/DefaultSdks.java
+++ b/android/src/com/android/tools/idea/sdk/DefaultSdks.java
@@ -109,7 +109,7 @@
 
   /**
    * @return the first SDK it finds that matches our default naming convention. There will be several SDKs so named, one for each build
-   *         target installed in the SDK; which of those this method returns is not defined.
+   * target installed in the SDK; which of those this method returns is not defined.
    */
   @Nullable
   private static Sdk getFirstAndroidSdk() {
@@ -162,8 +162,9 @@
     }
   }
 
-  public static List<Sdk> setDefaultAndroidHome(@NotNull File path) {
-    return setDefaultAndroidHome(path, null);
+  @NotNull
+  public static List<Sdk> setDefaultAndroidHome(@NotNull File path, @Nullable Project currentProject) {
+    return setDefaultAndroidHome(path, null, currentProject);
   }
 
   /**
@@ -198,11 +199,11 @@
    * Sets the path of Android Studio's default Android SDK. This method should be called in a write action. It is assumed that the given
    * path has been validated by {@link #isValidAndroidSdkPath(File)}. This method will fail silently if the given path is not valid.
    *
-   *
    * @param path the path of the Android SDK.
    * @see com.intellij.openapi.application.Application#runWriteAction(Runnable)
    */
-  public static List<Sdk> setDefaultAndroidHome(@NotNull File path, @Nullable Sdk javaSdk) {
+  @NotNull
+  public static List<Sdk> setDefaultAndroidHome(@NotNull File path, @Nullable Sdk javaSdk, @Nullable Project currentProject) {
     if (isValidAndroidSdkPath(path)) {
       assert ApplicationManager.getApplication().isWriteAccessAllowed();
 
@@ -234,7 +235,7 @@
       List<Sdk> sdks = createAndroidSdksForAllTargets(resolved, javaSdk);
 
       // Update the local.properties files for any open projects.
-      updateLocalPropertiesAndSync(resolved);
+      updateLocalPropertiesAndSync(resolved, currentProject);
 
       return sdks;
     }
@@ -305,7 +306,7 @@
     return androidPlatform.getTarget();
   }
 
-  private static void updateLocalPropertiesAndSync(@NotNull final File sdkHomePath) {
+  private static void updateLocalPropertiesAndSync(@NotNull final File sdkHomePath, @Nullable Project currentProject) {
     ProjectManager projectManager = ApplicationManager.getApplication().getComponent(ProjectManager.class);
     Project[] openProjects = projectManager.getOpenProjects();
     if (openProjects.length == 0) {
@@ -322,22 +323,24 @@
         LocalProperties localProperties = new LocalProperties(project);
         if (!FileUtil.filesEqual(sdkHomePath, localProperties.getAndroidSdkPath())) {
           localPropertiesToUpdate.add(Pair.create(project, localProperties));
-          projectsToUpdateNames.add("'" + project.getName() + "'");
+          if (!project.equals(currentProject)) {
+            projectsToUpdateNames.add("'" + project.getName() + "'");
+          }
         }
       }
       catch (IOException e) {
         // Exception thrown when local.properties file exists but cannot be read (e.g. no writing permissions.)
         logAndShowErrorWhenUpdatingLocalProperties(project, e, "read", sdkHomePath);
       }
-    } if (!localPropertiesToUpdate.isEmpty()) {
-      if (!ApplicationManager.getApplication().isUnitTestMode()) {
+    }
+    if (!localPropertiesToUpdate.isEmpty()) {
+      if (!projectsToUpdateNames.isEmpty() && !ApplicationManager.getApplication().isUnitTestMode()) {
         UIUtil.invokeAndWaitIfNeeded(new Runnable() {
           @Override
           public void run() {
-            String format =
-              "The local.properties files in projects %1$s will be modified with the path of Android Studio's default Android SDK:\n" +
-              "'%2$s'";
-            Messages.showErrorDialog(String.format(format, projectsToUpdateNames, sdkHomePath), "Sync Android SDKs");
+            String msg = "The local.properties file(s) in the project(s)\n " + projectsToUpdateNames +
+                         "\nwill be modified with the path of Android Studio's default Android Studio:\n'" + sdkHomePath + "'";
+            Messages.showErrorDialog(String.format(msg, projectsToUpdateNames, sdkHomePath), "Sync Android SDKs");
           }
         });
       }
diff --git a/android/src/com/android/tools/idea/sdk/SdkLoggerIntegration.java b/android/src/com/android/tools/idea/sdk/SdkLoggerIntegration.java
new file mode 100644
index 0000000..108508c
--- /dev/null
+++ b/android/src/com/android/tools/idea/sdk/SdkLoggerIntegration.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2014 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.tools.idea.sdk;
+
+import com.android.utils.IReaderLogger;
+import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class for integrating stream-based SDK manager UI with the Android Studio UI.
+ */
+public abstract class SdkLoggerIntegration implements IReaderLogger {
+  // Groups: 1=license-id
+  private static Pattern sLicenceText = Pattern.compile("^\\s*License id:\\s*([a-z0-9-]+).*\\s*");
+  // Groups: 1=progress values (%, ETA), 2=% int, 3=progress text
+  private static Pattern sProgress1Text = Pattern.compile("^\\s+\\((([0-9]+)%,\\s*[^)]*)\\)(.*)\\s*");
+  // Groups: 1=progress text, 2=progress values, 3=% int
+  private static Pattern sProgress2Text = Pattern.compile("^\\s+([^(]+)\\s+\\((([0-9]+)%)\\)\\s*");
+  // Groups: 1=task title
+  private static Pattern sDownloadingComponentText = Pattern.compile("^\\s+(Downloading .*)\\s*$");
+
+  private BackgroundableProcessIndicator myIndicator;
+  private String myCurrLicense;
+  private String myLastLine;
+
+  public void setIndicator(BackgroundableProcessIndicator indicator) {
+    myIndicator = indicator;
+  }
+
+  /**
+   * Used by UpdaterData.acceptLicense() to prompt for license acceptance
+   * when updating the SDK from the command-line.
+   * <p/>
+   * {@inheritDoc}
+   */
+  @Override
+  public int readLine(@NotNull byte[] inputBuffer) throws IOException {
+    if (myLastLine != null && myLastLine.contains("Do you accept the license")) {
+      // Let's take a simple shortcut and simply reply 'y' for yes.
+      inputBuffer[0] = 'y';
+      inputBuffer[1] = 0;
+      return 1;
+    }
+    inputBuffer[0] = 'n';
+    inputBuffer[1] = 0;
+    return 1;
+  }
+
+  @Override
+  public void error(@Nullable Throwable t, @Nullable String msgFormat, Object... args) {
+    if (msgFormat == null && t != null) {
+      if (myIndicator != null) myIndicator.setText2(t.toString());
+      outputLine(t.toString());
+    }
+    else if (msgFormat != null) {
+      if (myIndicator != null) myIndicator.setText2(String.format(msgFormat, args));
+      outputLine(String.format(msgFormat, args));
+    }
+  }
+
+  @Override
+  public void warning(@NotNull String msgFormat, Object... args) {
+    if (myIndicator != null) myIndicator.setText2(String.format(msgFormat, args));
+    outputLine(String.format(msgFormat, args));
+  }
+
+  @Override
+  public void info(@NotNull String msgFormat, Object... args) {
+    if (myIndicator != null) myIndicator.setText2(String.format(msgFormat, args));
+    outputLine(String.format(msgFormat, args));
+  }
+
+  @Override
+  public void verbose(@NotNull String msgFormat, Object... args) {
+    // Don't log verbose stuff in the background indicator.
+    outputLine(String.format(msgFormat, args));
+  }
+
+  /**
+   * This method takes the console output from the command-line updater.
+   * It filters it to remove some verbose output that is not desirable here.
+   * It also detects progress-bar like text and updates the dialog's progress
+   * bar accordingly.
+   */
+  private void outputLine(@NotNull final String line) {
+    myLastLine = line;
+    try {
+      // skip some of the verbose output such as license text & refreshing http sources
+      Matcher m = sLicenceText.matcher(line);
+      if (m.matches()) {
+        myCurrLicense = m.group(1);
+        return;
+      }
+      else if (myCurrLicense != null) {
+        if (line.contains("Do you accept the license") && line.contains(myCurrLicense)) {
+          myCurrLicense = null;
+        }
+        return;
+      }
+      else if (line.contains("Fetching http") ||
+               line.contains("Fetching URL:") ||
+               line.contains("Validate XML") ||
+               line.contains("Parse XML") ||
+               line.contains("---------")) {
+        return;
+      }
+
+      if (parseWithPattern(sProgress1Text, line, 3, 1, 2) ||
+          parseWithPattern(sProgress2Text, line, 1, 2, 3) ||
+          parseWithPattern(sDownloadingComponentText, line, 1, -1, -1)) {
+        return;
+      }
+
+      // This is invoked from a backgroundable task, only update text on the ui thread.
+      UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+        @Override
+        public void run() {
+          lineAdded(line);
+        }
+      });
+    }
+    catch (Exception ignore) {
+    }
+  }
+
+  private boolean parseWithPattern(@NotNull Pattern pattern, @NotNull String line, int titleGroup, int progressTextGroup, int progressGroup) {
+    int progInt = -1;
+
+    Matcher m = pattern.matcher(line);
+    if (m.matches()) {
+      if (progressGroup >= 0) {
+        // Groups: 1=progress values (%, ETA), 2=% int, 3=progress text
+        try {
+          progInt = Integer.parseInt(m.group(progressGroup));
+        }
+        catch (NumberFormatException ignore) {
+          progInt = 0;
+        }
+      }
+      final int fProgInt = progInt;
+      final String fProgText2 = getGroupText(m, progressTextGroup);
+      final String fProgText1 = getGroupText(m, titleGroup);
+
+      // This is invoked from a backgroundable task, only update text on the ui thread.
+      UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+        @Override
+        public void run() {
+          if (fProgText1 != null) {
+            setTitle(fProgText1);
+          }
+
+          if (fProgText2 != null) {
+            setDescription(fProgText2);
+          }
+
+          if (fProgInt >= 0) {
+            setProgress(fProgInt);
+          }
+        }
+      });
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+
+  @Nullable
+  private static String getGroupText(Matcher m, int group) {
+    return group >= 0 ? m.group(group) : null;
+  }
+
+  protected abstract void setProgress(int progress);
+
+  protected abstract void setDescription(String description);
+
+  protected abstract void setTitle(String title);
+
+  protected abstract void lineAdded(String string);
+}
diff --git a/android/src/com/android/tools/idea/sdk/wizard/LicenseAgreementStep.form b/android/src/com/android/tools/idea/sdk/wizard/LicenseAgreementStep.form
deleted file mode 100644
index fc679f1..0000000
--- a/android/src/com/android/tools/idea/sdk/wizard/LicenseAgreementStep.form
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.android.tools.idea.sdk.wizard.LicenseAgreementStep">
-  <grid id="27dc6" binding="myComponent" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="1" vgap="1">
-    <margin top="2" left="2" bottom="2" right="2"/>
-    <constraints>
-      <xy x="20" y="20" width="942" height="587"/>
-    </constraints>
-    <properties/>
-    <border type="none"/>
-    <children>
-      <scrollpane id="4ad9b" class="com.intellij.ui.components.JBScrollPane">
-        <constraints>
-          <grid row="0" column="0" row-span="2" col-span="1" vsize-policy="3" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false">
-            <preferred-size width="300" height="440"/>
-          </grid>
-        </constraints>
-        <properties/>
-        <border type="none"/>
-        <children>
-          <component id="4646" class="com.intellij.ui.treeStructure.Tree" binding="myChangeTree">
-            <constraints/>
-            <properties/>
-          </component>
-        </children>
-      </scrollpane>
-      <scrollpane id="9e526" class="com.intellij.ui.components.JBScrollPane">
-        <constraints>
-          <grid row="0" column="1" row-span="1" col-span="2" vsize-policy="3" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false">
-            <preferred-size width="350" height="-1"/>
-          </grid>
-        </constraints>
-        <properties>
-          <opaque value="false"/>
-          <verticalScrollBarPolicy value="22"/>
-        </properties>
-        <border type="none"/>
-        <children>
-          <component id="ad692" class="javax.swing.JTextPane" binding="myLicenseTextField">
-            <constraints/>
-            <properties/>
-          </component>
-        </children>
-      </scrollpane>
-      <component id="8beac" class="javax.swing.JRadioButton" binding="myDeclineRadioButton" default-binding="true">
-        <constraints>
-          <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="1" fill="0" indent="0" use-parent-layout="false"/>
-        </constraints>
-        <properties>
-          <text value="Decline"/>
-        </properties>
-      </component>
-      <component id="a3a11" class="javax.swing.JRadioButton" binding="myAcceptRadioButton" default-binding="true">
-        <constraints>
-          <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="1" fill="0" indent="0" use-parent-layout="false"/>
-        </constraints>
-        <properties>
-          <hideActionText value="true"/>
-          <text value="Accept"/>
-        </properties>
-      </component>
-    </children>
-  </grid>
-</form>
diff --git a/android/src/com/android/tools/idea/sdk/wizard/LicenseAgreementStep.java b/android/src/com/android/tools/idea/sdk/wizard/LicenseAgreementStep.java
index c8fb724..ad47b87 100644
--- a/android/src/com/android/tools/idea/sdk/wizard/LicenseAgreementStep.java
+++ b/android/src/com/android/tools/idea/sdk/wizard/LicenseAgreementStep.java
@@ -22,8 +22,11 @@
 import com.google.common.collect.Maps;
 import com.intellij.icons.AllIcons;
 import com.intellij.openapi.Disposable;
+import com.intellij.openapi.ui.Splitter;
 import com.intellij.ui.ColoredTreeCellRenderer;
+import com.intellij.ui.ScrollPaneFactory;
 import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.ui.components.JBRadioButton;
 import com.intellij.ui.treeStructure.Tree;
 import org.jetbrains.annotations.NotNull;
 
@@ -33,6 +36,7 @@
 import javax.swing.tree.DefaultMutableTreeNode;
 import javax.swing.tree.DefaultTreeModel;
 import javax.swing.tree.TreePath;
+import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.List;
@@ -46,7 +50,6 @@
 public class LicenseAgreementStep extends DynamicWizardStepWithHeaderAndDescription {
   private JTextPane myLicenseTextField;
   private Tree myChangeTree;
-  private JPanel myComponent;
   private JRadioButton myDeclineRadioButton;
   private JRadioButton myAcceptRadioButton;
 
@@ -58,7 +61,31 @@
 
   public LicenseAgreementStep(@NotNull Disposable disposable) {
     super("License Agreement", "Read and agree to the licenses for the components which will be installed", null, disposable);
-    setBodyComponent(myComponent);
+    Splitter splitter = new Splitter(false, .30f);
+    splitter.setHonorComponentsMinimumSize(true);
+
+    myChangeTree = new Tree();
+    splitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myChangeTree));
+
+    myLicenseTextField = new JTextPane();
+    splitter.setSecondComponent(ScrollPaneFactory.createScrollPane(myLicenseTextField));
+
+    myDeclineRadioButton = new JBRadioButton("Decline");
+    myAcceptRadioButton = new JBRadioButton("Accept");
+
+    ButtonGroup optionsGroup = new ButtonGroup();
+    optionsGroup.add(myDeclineRadioButton);
+    optionsGroup.add(myAcceptRadioButton);
+
+    JPanel optionsPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+    optionsPanel.add(myDeclineRadioButton);
+    optionsPanel.add(myAcceptRadioButton);
+
+    JPanel mainPanel = new JPanel(new BorderLayout());
+    mainPanel.add(splitter, BorderLayout.CENTER);
+    mainPanel.add(optionsPanel, BorderLayout.SOUTH);
+
+    setBodyComponent(mainPanel);
   }
 
   @Override
diff --git a/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.form b/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.form
index 963be58..f8bad73 100755
--- a/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.form
+++ b/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.form
@@ -66,7 +66,7 @@
         </properties>
         <border type="none"/>
         <children>
-          <component id="9f832" class="javax.swing.JTextArea" binding="myTextArea1" default-binding="true">
+          <component id="9f832" class="javax.swing.JTextArea" binding="mySdkManagerOutput" default-binding="true">
             <constraints/>
             <properties>
               <text value=""/>
diff --git a/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java b/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java
index 8b0c0a6..ffbf8bf 100755
--- a/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java
+++ b/android/src/com/android/tools/idea/sdk/wizard/SmwOldApiDirectInstall.java
@@ -20,10 +20,10 @@
 import com.android.sdklib.internal.repository.updater.SdkUpdaterNoWindow;
 import com.android.sdklib.repository.descriptors.IPkgDesc;
 import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.tools.idea.sdk.SdkLoggerIntegration;
 import com.android.tools.idea.sdk.SdkState;
 import com.android.tools.idea.wizard.DynamicWizardStepWithHeaderAndDescription;
 import com.android.utils.ILogger;
-import com.android.utils.IReaderLogger;
 import com.google.common.collect.Lists;
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.diagnostic.Logger;
@@ -43,11 +43,8 @@
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import static com.android.tools.idea.wizard.WizardConstants.INSTALL_REQUESTS_KEY;
 import static com.android.tools.idea.wizard.WizardConstants.NEWLY_INSTALLED_API_KEY;
@@ -56,7 +53,7 @@
 public class SmwOldApiDirectInstall extends DynamicWizardStepWithHeaderAndDescription {
   private Logger LOG = Logger.getInstance(SmwOldApiDirectInstall.class);
   private JBLabel myLabelSdkPath;
-  private JTextArea myTextArea1;
+  private JTextArea mySdkManagerOutput;
   private JBLabel myLabelProgress1;
   private JProgressBar myProgressBar;
   private JBLabel myLabelProgress2;
@@ -64,7 +61,6 @@
   private JPanel myContentPanel;
   private boolean myInstallFinished;
   private Boolean myBackgroundSuccess = null;
-  private boolean myBeforeInstall = true;
 
   public SmwOldApiDirectInstall(@NotNull Disposable disposable) {
     super("Installing Requested Components", "", null, disposable);
@@ -74,7 +70,7 @@
   @Override
   public void onEnterStep() {
     super.onEnterStep();
-    myTextArea1.setText("");
+    mySdkManagerOutput.setText("");
     startSdkInstallUsingNonSwtOldApi();
   }
 
@@ -198,7 +194,6 @@
       // The command-line API is a bit archaic and has some drastic limitations, one of them being that
       // it blindly re-install stuff even if already present IIRC.
 
-      myBeforeInstall = false;
       String osSdkFolder = mySdkData.getLocation().getPath();
       SdkManager sdkManager = SdkManager.createManager(osSdkFolder, myLogger);
 
@@ -281,176 +276,36 @@
     }
   }
 
-  // Groups: 1=license-id
-  private static Pattern sLicenceText = Pattern.compile("^\\s*License id:\\s*([a-z0-9-]+).*\\s*");
-  // Groups: 1=progress values (%, ETA), 2=% int, 3=progress text
-  private static Pattern sProgress1Text = Pattern.compile("^\\s+\\((([0-9]+)%,\\s*[^)]*)\\)(.*)\\s*");
-  // Groups: 1=progress text, 2=progress values, 3=% int
-  private static Pattern sProgress2Text = Pattern.compile("^\\s+([^(]+)\\s+\\((([0-9]+)%)\\)\\s*");
-
-  private class CustomLogger implements IReaderLogger {
-
-    private BackgroundableProcessIndicator myIndicator;
-    private String myCurrLicense;
-    private String myLastLine;
-
-    public CustomLogger() {
-    }
-
-    public void setIndicator(BackgroundableProcessIndicator indicator) {
-      myIndicator = indicator;
-    }
-
-    /**
-     * Used by UpdaterData.acceptLicense() to prompt for license acceptance
-     * when updating the SDK from the command-line.
-     * <p/>
-     * {@inheritDoc}
-     */
+  private final class CustomLogger extends SdkLoggerIntegration {
     @Override
-    public int readLine(@NotNull byte[] inputBuffer) throws IOException {
-      if (myLastLine != null && myLastLine.contains("Do you accept the license")) {
-        // Let's take a simple shortcut and simply reply 'y' for yes.
-        inputBuffer[0] = 'y';
-        inputBuffer[1] = 0;
-        return 1;
+    protected void setProgress(int progress) {
+      myProgressBar.setValue(progress);
+    }
+
+    @Override
+    protected void setDescription(String description) {
+      myLabelProgress2.setText(description);
+    }
+
+    @Override
+    protected void setTitle(String title) {
+      myLabelProgress1.setText(title);
+    }
+
+    @Override
+    protected void lineAdded(String string) {
+      String current = mySdkManagerOutput.getText();
+      if (current == null) {
+        current = "";
       }
-      inputBuffer[0] = 'n';
-      inputBuffer[1] = 0;
-      return 1;
-    }
-
-    @Override
-    public void error(@Nullable Throwable t,
-                      @Nullable String msgFormat,
-                      Object... args) {
-      if (msgFormat == null && t != null) {
-        if (myIndicator != null) myIndicator.setText2(t.toString());
-        outputLine(t.toString());
-      } else if (msgFormat != null) {
-        if (myIndicator != null) myIndicator.setText2(String.format(msgFormat, args));
-        outputLine(String.format(msgFormat, args));
+      mySdkManagerOutput.setText(current + string);
+      if (string.contains("Nothing was installed") ||
+          string.contains("Failed") ||
+          string.contains("The package filter removed all packages")) {
+        myBackgroundSuccess = false;
+      } else if (string.contains("Done") && !string.contains("othing")) {
+        myBackgroundSuccess = true;
       }
     }
-
-    @Override
-    public void warning(@NotNull String msgFormat, Object... args) {
-      if (myIndicator != null) myIndicator.setText2(String.format(msgFormat, args));
-      outputLine(String.format(msgFormat, args));
-    }
-
-    @Override
-    public void info(@NotNull String msgFormat, Object... args) {
-      if (myIndicator != null) myIndicator.setText2(String.format(msgFormat, args));
-      outputLine(String.format(msgFormat, args));
-    }
-
-    @Override
-    public void verbose(@NotNull String msgFormat, Object... args) {
-      // Don't log verbose stuff in the background indicator.
-      outputLine(String.format(msgFormat, args));
-    }
-
-    /**
-     * This method takes the console output from the command-line updater.
-     * It filters it to remove some verbose output that is not desirable here.
-     * It also detects progress-bar like text and updates the dialog's progress
-     * bar accordingly.
-     */
-    private void outputLine(@NotNull String line) {
-      myLastLine = line;
-      try {
-        // skip some of the verbose output such as license text & refreshing http sources
-        Matcher m = sLicenceText.matcher(line);
-        if (m.matches()) {
-          myCurrLicense = m.group(1);
-          return;
-        }
-        else if (myCurrLicense != null) {
-          if (line.contains("Do you accept the license") && line.contains(myCurrLicense)) {
-            myCurrLicense = null;
-          }
-          return;
-        }
-        else if (line.contains("Fetching http") ||
-                 line.contains("Fetching URL:") ||
-                 line.contains("Validate XML") ||
-                 line.contains("Parse XML") ||
-                 line.contains("---------")) {
-          return;
-        }
-
-        int progInt = -1;
-        String progText2 = null;
-        String progText1 = null;
-
-        m = sProgress1Text.matcher(line);
-        if (m.matches()) {
-          // Groups: 1=progress values (%, ETA), 2=% int, 3=progress text
-          try {
-            progInt = Integer.parseInt(m.group(2));
-          }
-          catch (NumberFormatException ignore) {
-            progInt = 0;
-          }
-          progText1 = m.group(3);
-          progText2 = m.group(1);
-          line = null;
-        } else {
-          m = sProgress2Text.matcher(line);
-          if (m.matches()) {
-            // Groups: 1=progress text, 2=progress values, 3=% int
-            try {
-              progInt = Integer.parseInt(m.group(3));
-            }
-            catch (NumberFormatException ignore) {
-              progInt = 0;
-            }
-            progText1 = m.group(1);
-            progText2 = m.group(2);
-            line = null;
-          }
-        }
-
-        final int fProgInt = progInt;
-        final String fProgText2 = progText2;
-        final String fProgText1 = progText1;
-        final String fAddLine = line;
-
-        // This is invoked from a backgroundable task, only update text on the ui thread.
-        UIUtil.invokeAndWaitIfNeeded(new Runnable() {
-          @Override
-          public void run() {
-            if (fAddLine != null) {
-              String current = myTextArea1.getText();
-              if (current == null) {
-                current = "";
-              }
-              myTextArea1.setText(current + fAddLine);
-              if (fAddLine.contains("Nothing was installed")) {
-                myBackgroundSuccess = false;
-              } else if (fAddLine.contains("Failed") || fAddLine.contains("The package filter removed all packages")) {
-                myBackgroundSuccess = false;
-              } else if (fAddLine.contains("Done") && !fAddLine.contains("othing")) {
-                myBackgroundSuccess = Boolean.TRUE;
-              }
-            }
-
-            if (fProgText1 != null) {
-              myLabelProgress1.setText(fProgText1);
-            }
-
-            if (fProgText2 != null) {
-              myLabelProgress2.setText(fProgText2);
-            }
-
-            if (fProgInt >= 0) {
-              myProgressBar.setValue(fProgInt);
-            }
-          }
-        });
-      } catch (Exception ignore) {}
-    }
   }
-
 }
diff --git a/android/src/com/android/tools/idea/templates/FmHasDependencyMethod.java b/android/src/com/android/tools/idea/templates/FmHasDependencyMethod.java
new file mode 100644
index 0000000..7095dcf
--- /dev/null
+++ b/android/src/com/android/tools/idea/templates/FmHasDependencyMethod.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2014 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.tools.idea.templates;
+
+import com.android.SdkConstants;
+import com.android.builder.model.Dependencies;
+import com.android.tools.idea.gradle.IdeaAndroidProject;
+import com.android.tools.idea.gradle.util.GradleUtil;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectLocator;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import freemarker.ext.beans.BooleanModel;
+import freemarker.template.*;
+import org.jetbrains.android.facet.AndroidFacet;
+import org.jetbrains.android.inspections.lint.IntellijLintClient;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import static com.android.tools.idea.templates.FmUtil.stripSuffix;
+import static com.android.tools.idea.wizard.TemplateWizardState.ACTIVITY_NAME_SUFFIX;
+import static com.android.tools.idea.wizard.TemplateWizardState.LAYOUT_NAME_PREFIX;
+
+/**
+ * Method invoked by FreeMarker to check whether a given dependency
+ * is available in this module
+ */
+public class FmHasDependencyMethod implements TemplateMethodModelEx {
+  private final Map<String, Object> myParamMap;
+
+  public FmHasDependencyMethod(Map<String, Object> paramMap) {
+    myParamMap = paramMap;
+  }
+
+  @Override
+  public TemplateModel exec(List args) throws TemplateModelException {
+    if (args.size() != 1) {
+      throw new TemplateModelException("Wrong arguments");
+    }
+    String artifact = args.get(0).toString();
+    if (artifact.isEmpty()) {
+      return TemplateBooleanModel.FALSE;
+    }
+
+    if (myParamMap.containsKey(TemplateMetadata.ATTR_DEPENDENCIES_LIST)) {
+      Object listObject = myParamMap.get(TemplateMetadata.ATTR_DEPENDENCIES_LIST);
+      if (listObject instanceof List) {
+        @SuppressWarnings("unchecked")
+        List<String> dependencyList = (List<String>)listObject;
+        for (String dependency : dependencyList) {
+          if (dependency.contains(artifact)) {
+            return TemplateBooleanModel.TRUE;
+          }
+        }
+      }
+    }
+
+    // Find the corresponding module, if any
+    String modulePath = (String)myParamMap.get(TemplateMetadata.ATTR_PROJECT_OUT);
+    if (modulePath != null) {
+      VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(new File(modulePath.replace('/', File.separatorChar)));
+      if (file != null) {
+        Project project = ProjectLocator.getInstance().guessProjectForFile(file);
+        if (project != null) {
+          Module module = ModuleUtilCore.findModuleForFile(file, project);
+          if (module != null) {
+            AndroidFacet facet = AndroidFacet.getInstance(module);
+            if (facet != null) {
+              IdeaAndroidProject gradleProject = facet.getIdeaAndroidProject();
+              if (gradleProject != null) {
+                return GradleUtil.dependsOn(gradleProject, artifact) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // Creating a new module, so no existing dependencies: provide some defaults. This is really intended for appcompat-v7,
+    // but since it depends on support-v4, we include it here (such that a query to see if support-v4 is installed in a newly
+    // created project will return true since it will be by virtue of appcompat also being installed.)
+    if (artifact.contains(SdkConstants.APPCOMPAT_LIB_ARTIFACT) || artifact.contains(SdkConstants.SUPPORT_LIB_ARTIFACT)) {
+      // No dependencies: Base it off of the minApi and buildApi versions:
+      // If building with Lollipop, and targeting anything earlier than Lollipop, use appcompat.
+      // (Also use it if minApi is less than ICS.)
+      Object buildApiObject = myParamMap.get(TemplateMetadata.ATTR_BUILD_API);
+      Object minApiObject = myParamMap.get(TemplateMetadata.ATTR_MIN_API_LEVEL);
+      if (buildApiObject instanceof Integer && minApiObject instanceof Integer) {
+        int buildApi = (Integer)buildApiObject;
+        int minApi = (Integer)minApiObject;
+        return minApi >= 8 && ((buildApi >= 21 && minApi < 21) || minApi < 14) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+      }
+    }
+
+    return TemplateBooleanModel.FALSE;
+  }
+}
\ No newline at end of file
diff --git a/android/src/com/android/tools/idea/templates/Template.java b/android/src/com/android/tools/idea/templates/Template.java
index 8241671..6d078b6 100755
--- a/android/src/com/android/tools/idea/templates/Template.java
+++ b/android/src/com/android/tools/idea/templates/Template.java
@@ -314,6 +314,7 @@
     paramMap.put("escapeXmlString", new FmEscapeXmlStringMethod());
     paramMap.put("escapePropertyValue", new FmEscapePropertyValueMethod());
     paramMap.put("extractLetters", new FmExtractLettersMethod());
+    paramMap.put("hasDependency", new FmHasDependencyMethod(paramMap));
 
     // Dependency list
     paramMap.put(TemplateMetadata.ATTR_DEPENDENCIES_LIST, new LinkedList<String>());
diff --git a/android/src/com/android/tools/idea/welcome/AndroidSdk.java b/android/src/com/android/tools/idea/welcome/AndroidSdk.java
index c30e6ca..deb4c72 100644
--- a/android/src/com/android/tools/idea/welcome/AndroidSdk.java
+++ b/android/src/com/android/tools/idea/welcome/AndroidSdk.java
@@ -15,27 +15,19 @@
  */
 package com.android.tools.idea.welcome;
 
+import com.android.annotations.VisibleForTesting;
+import com.android.sdklib.AndroidVersion;
 import com.android.sdklib.devices.Storage;
-import com.android.tools.idea.sdk.DefaultSdks;
-import com.android.tools.idea.sdk.SdkMerger;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
 import com.android.tools.idea.wizard.DynamicWizardStep;
 import com.android.tools.idea.wizard.ScopedStateStore;
-import com.google.common.collect.ImmutableSet;
-import com.intellij.execution.ui.ConsoleViewContentType;
-import com.intellij.openapi.application.Application;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.progress.ProgressManager;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.download.DownloadableFileDescription;
-import com.intellij.util.download.DownloadableFileService;
+import com.intellij.openapi.util.SystemInfo;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
-import java.io.IOException;
-import java.util.Set;
 
 /**
  * Android SDK installable component.
@@ -44,117 +36,54 @@
   public static final long SIZE = 2300 * Storage.Unit.MiB.getNumberOfBytes();
   private static final ScopedStateStore.Key<Boolean> KEY_INSTALL_SDK =
     ScopedStateStore.createKey("download.sdk", ScopedStateStore.Scope.PATH, Boolean.class);
-  private static final ScopedStateStore.Key<String> KEY_SDK_INSTALL_LOCATION =
-    ScopedStateStore.createKey("download.sdk.location", ScopedStateStore.Scope.PATH, String.class);
-  private final DownloadableFileDescription myAndroidSdkDescription = DownloadableFileService.getInstance()
-    .createFileDescription(FirstRunWizardDefaults.getSdkDownloadUrl(), FirstRunWizardDefaults.ANDROID_SDK_ARCHIVE_FILE_NAME);
-  private final ScopedStateStore.Key<Boolean> myKeyCustomInstall;
-  private ScopedStateStore myState;
-  private SdkComponentsStep myStep;
 
-  public AndroidSdk(ScopedStateStore.Key<Boolean> keyCustomInstall) {
+  public AndroidSdk() {
     super("Android SDK", SIZE, KEY_INSTALL_SDK);
-    myKeyCustomInstall = keyCustomInstall;
+  }
+
+  @VisibleForTesting
+  static PkgDesc.Builder[] getPackages() {
+    AndroidVersion lVersion = new AndroidVersion(21, null);
+    MajorRevision unspecifiedRevision = new MajorRevision(FullRevision.NOT_SPECIFIED);
+
+    PkgDesc.Builder androidSdkTools = PkgDesc.Builder.newTool(FullRevision.NOT_SPECIFIED, FullRevision.NOT_SPECIFIED);
+    PkgDesc.Builder androidSdkPlatformTools = PkgDesc.Builder.newPlatformTool(FullRevision.NOT_SPECIFIED);
+    PkgDesc.Builder androidSdkBuildTools = PkgDesc.Builder.newBuildTool(new FullRevision(21, 0, 2));
+    PkgDesc.Builder supportRepository = InstallComponentsPath.createExtra(true, "android", "m2repository");
+    PkgDesc.Builder googleRepository = InstallComponentsPath.createExtra(true, "google", "m2repository");
+    PkgDesc.Builder atomImage = PkgDesc.Builder.newSysImg(lVersion, new IdDisplay("default", ""), "x86", unspecifiedRevision);
+    PkgDesc.Builder platform = PkgDesc.Builder.newPlatform(lVersion, unspecifiedRevision, FullRevision.NOT_SPECIFIED);
+    PkgDesc.Builder sample = PkgDesc.Builder.newSample(lVersion, unspecifiedRevision, FullRevision.NOT_SPECIFIED);
+    PkgDesc.Builder platformSources = PkgDesc.Builder.newSource(lVersion, unspecifiedRevision);
+    PkgDesc.Builder usb = InstallComponentsPath.createExtra(SystemInfo.isLinux || SystemInfo.isWindows, "google", "usb_driver");
+
+    return new PkgDesc.Builder[]{androidSdkTools, androidSdkPlatformTools, androidSdkBuildTools, supportRepository, googleRepository,
+      atomImage, platform, sample, platformSources, usb};
   }
 
   @NotNull
   @Override
-  public Set<DownloadableFileDescription> getFilesToDownloadAndExpand() {
-    if (getHandoffAndroidSdkSource() == null) {
-      return ImmutableSet.of(myAndroidSdkDescription);
-    }
-    else {
-      return ImmutableSet.of();
-    }
+  public PkgDesc.Builder[] getRequiredSdkPackages() {
+    return getPackages();
   }
 
   @Override
-  public void init(ScopedStateStore state) {
-    myState = state;
-    InstallerData data = InstallerData.get(state);
-    String location;
-    if (data.exists()) {
-      location = data.getAndroidDest();
-    }
-    else {
-      location = FirstRunWizardDefaults.getDefaultSdkLocation();
-    }
+  public void init(@NotNull ScopedStateStore state, @NotNull ProgressStep progressStep) {
     state.put(KEY_INSTALL_SDK, true);
-    state.put(KEY_SDK_INSTALL_LOCATION, location);
   }
 
   @Override
   public DynamicWizardStep[] createSteps() {
-    myStep = new SdkComponentsStep(InstallComponentsPath.COMPONENTS, myKeyCustomInstall, KEY_SDK_INSTALL_LOCATION);
-    return new DynamicWizardStep[]{myStep};
+    return new DynamicWizardStep[]{};
   }
 
   @Override
-  public boolean hasVisibleStep() {
-    return myStep.isStepVisible();
-  }
-
-  @Override
-  public void perform(@NotNull InstallContext downloaded) throws WizardException {
-    String destinationPath = myState.get(KEY_SDK_INSTALL_LOCATION);
-    assert destinationPath != null;
-    final File destination = new File(destinationPath);
-    File source = getHandoffAndroidSdkSource();
-    if (FileUtil.filesEqual(source, destination)) {
-      return;
-    }
-    ProgressStep progressStep = downloaded.getProgressStep();
-    ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
-    indicator.setText("Installing Android SDK");
-    indicator.setIndeterminate(true);
-    if (source == null) {
-      source = downloaded.getExpandedLocation(myAndroidSdkDescription);
-      assert source != null && source.isDirectory();
-    }
-    try {
-      FileUtil.ensureExists(destination);
-      if (!FileUtil.filesEqual(destination.getCanonicalFile(), source.getCanonicalFile())) {
-        if (SdkMerger.hasMergeableContent(source, destination)) {
-          SdkMerger.mergeSdks(source, destination, indicator);
-        }
-      }
-      progressStep.print(String.format("Android SDK was installed to %s", destination), ConsoleViewContentType.SYSTEM_OUTPUT);
-      final Application application = ApplicationManager.getApplication();
-      // SDK can only be set from write action, write action can only be started from UI thread
-      ApplicationManager.getApplication().invokeAndWait(new Runnable() {
-        @Override
-        public void run() {
-          application.runWriteAction(new Runnable() {
-            @Override
-            public void run() {
-              DefaultSdks.setDefaultAndroidHome(destination);
-              AndroidFirstRunPersistentData.getInstance().markSdkUpToDate();
-            }
-          });
-        }
-      }, application.getAnyModalityState());
-    }
-    catch (IOException e) {
-      throw new WizardException(WelcomeUIUtils.getMessageWithDetails("Unable to install Android SDK", e.getMessage()), e);
-    }
+  public void configure(@NotNull InstallContext installContext, @NotNull File sdk) {
+    // Nothing to do, having components installed is enough
   }
 
   @Override
   public boolean isOptional() {
     return false;
   }
-
-  @Nullable
-  private File getHandoffAndroidSdkSource() {
-    InstallerData data = InstallerData.get(myState);
-    String androidSrc = data.getAndroidSrc();
-    if (!StringUtil.isEmpty(androidSrc)) {
-      File srcFolder = new File(androidSrc);
-      File[] files = srcFolder.listFiles();
-      if (srcFolder.isDirectory() && files != null && files.length > 0) {
-        return srcFolder;
-      }
-    }
-    return null;
-  }
 }
diff --git a/android/src/com/android/tools/idea/welcome/DownloadOperation.java b/android/src/com/android/tools/idea/welcome/DownloadOperation.java
index 36ebcfa..f7da0bd5 100644
--- a/android/src/com/android/tools/idea/welcome/DownloadOperation.java
+++ b/android/src/com/android/tools/idea/welcome/DownloadOperation.java
@@ -15,11 +15,13 @@
  */
 package com.android.tools.idea.welcome;
 
+import com.google.common.collect.ImmutableList;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.util.download.DownloadableFileDescription;
 import com.intellij.util.download.DownloadableFileService;
 import com.intellij.util.download.FileDownloader;
+import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
 import java.io.IOException;
@@ -28,23 +30,24 @@
 /**
  * Downloads files needed to setup Android Studio.
  */
-public final class DownloadOperation extends PreinstallOperation {
+public final class DownloadOperation extends PreinstallOperation<File> {
+  private final String myUrl;
 
-  public DownloadOperation(InstallContext context) {
-    super(context, 0.7);
+  public DownloadOperation(InstallContext context, String url, double progressShare) {
+    super(context, progressShare);
+    myUrl = url;
   }
 
   @Override
-  protected void perform() throws WizardException {
+  @Nullable
+  protected File perform() throws WizardException {
     DownloadableFileService fileService = DownloadableFileService.getInstance();
-    FileDownloader downloader = fileService.createDownloader(myContext.getFilesToDownload(), "Android Studio components");
-    do {
+    DownloadableFileDescription myDescription = fileService.createFileDescription(myUrl, "components.zip");
+    FileDownloader downloader = fileService.createDownloader(ImmutableList.of(myDescription), "Android Studio components");
+    while (true) {
       try {
         List<Pair<File, DownloadableFileDescription>> result = downloader.download(myContext.getTempDirectory());
-        for (Pair<File, DownloadableFileDescription> fileDescriptionPair : result) {
-          myContext.setDownloadedLocation(fileDescriptionPair.getSecond(), fileDescriptionPair.getFirst());
-        }
-        break;
+        return result.size() == 1 ? result.get(0).getFirst() : null;
       }
       catch (IOException e) {
         String details = StringUtil.isEmpty(e.getMessage()) ? "." : (": " + e.getMessage());
@@ -52,6 +55,5 @@
         promptToRetry(message + " Please check your Internet connection and retry.", message, e);
       }
     }
-    while (true);
   }
 }
diff --git a/android/src/com/android/tools/idea/welcome/FirstRunWizard.java b/android/src/com/android/tools/idea/welcome/FirstRunWizard.java
index fbbb7d4..412d85c 100644
--- a/android/src/com/android/tools/idea/welcome/FirstRunWizard.java
+++ b/android/src/com/android/tools/idea/welcome/FirstRunWizard.java
@@ -38,7 +38,7 @@
    */
   private final AtomicInteger myFinishClicks = new AtomicInteger(0);
   private final SetupJdkPath myJdkPath = new SetupJdkPath();
-  private final InstallComponentsPath myComponentsPath = new InstallComponentsPath();
+  private InstallComponentsPath myComponentsPath;
 
   public FirstRunWizard(DynamicWizardHost host) {
     super(null, null, WIZARD_TITLE, host);
@@ -47,6 +47,8 @@
 
   @Override
   public void init() {
+    SetupProgressStep progressStep = new SetupProgressStep();
+    myComponentsPath = new InstallComponentsPath(progressStep);
     addPath(new SingleStepPath(new FirstRunWelcomeStep()));
     addPath(myJdkPath);
     addPath(myComponentsPath);
@@ -56,7 +58,7 @@
         return super.isStepVisible() && !InstallerData.get(myState).exists();
       }
     }));
-    addPath(new SingleStepPath(new SetupProgressStep()));
+    addPath(new SingleStepPath(progressStep));
     super.init();
   }
 
@@ -79,7 +81,7 @@
         break;
       }
       if (path instanceof LongRunningOperationPath) {
-        ((LongRunningOperationPath)path).runLongOperation(progressStep);
+        ((LongRunningOperationPath)path).runLongOperation();
       }
     }
   }
diff --git a/android/src/com/android/tools/idea/welcome/FirstRunWizardDefaults.java b/android/src/com/android/tools/idea/welcome/FirstRunWizardDefaults.java
index 1e1b0b1..4ee0d27 100644
--- a/android/src/com/android/tools/idea/welcome/FirstRunWizardDefaults.java
+++ b/android/src/com/android/tools/idea/welcome/FirstRunWizardDefaults.java
@@ -16,31 +16,34 @@
 package com.android.tools.idea.welcome;
 
 import com.android.sdklib.devices.Storage;
+import com.google.common.collect.Iterables;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.projectRoots.Sdk;
 import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.util.PathUtil;
+import com.intellij.util.Urls;
+import org.jetbrains.android.sdk.AndroidSdkUtils;
 import org.jetbrains.annotations.NotNull;
 
+import java.io.File;
+import java.net.MalformedURLException;
+import java.util.List;
+
 /**
  * The goal is to keep all defaults in one place so it is easier to update
  * them as needed.
  */
 public class FirstRunWizardDefaults {
-  public static final String HAXM_INSTALLER_ARCHIVE_FILE_NAME = "haxm.zip";
   public static final String HAXM_DOCUMENTATION_URL = "http://www.intel.com/software/android/";
-  public static final String ANDROID_SDK_ARCHIVE_FILE_NAME = "androidsdk.zip";
 
   private FirstRunWizardDefaults() {
     // Do nothing
   }
 
   /**
-   * @return Intel® HAXM download URL
-   */
-  @NotNull
-  public static String getHaxmDownloadUrl() {
-    return "https://github.com/vladikoff/chromeos-apk/archive/master.zip";
-  }
-
-  /**
    * @return Recommended memory allocation given the computer RAM size
    */
   public static int getRecommendedHaxmMemory(long memorySize) {
@@ -65,7 +68,23 @@
    */
   @NotNull
   public static String getSdkDownloadUrl() {
-    return "https://github.com/FezVrasta/bootstrap-material-design/archive/master.zip";
+    String url = System.getProperty("android.sdkurl");
+    if (!StringUtil.isEmptyOrSpaces(url)) {
+      File file = new File(url);
+      if (file.isFile()) {
+        // Can't use any path => URL utilities as they don't add two slashes
+        // after the protocol as required by IJ downloader
+        return LocalFileSystem.PROTOCOL_PREFIX + PathUtil.toSystemIndependentName(file.getAbsolutePath());
+      }
+      else {
+        System.err.println("File " + file.getAbsolutePath() + " does not exist.");
+      }
+    }
+    String downloadUrl = AndroidSdkUtils.getSdkDownloadUrl();
+    if (downloadUrl == null) {
+      throw new IllegalStateException("Unsupported OS");
+    }
+    return downloadUrl;
   }
 
   /**
@@ -73,15 +92,18 @@
    */
   @NotNull
   public static String getDefaultSdkLocation() {
+    List<Sdk> sdks = AndroidSdkUtils.getAllAndroidSdks();
+    Sdk sdk = Iterables.getFirst(sdks, null);
+    if (sdk != null && !StringUtil.isEmptyOrSpaces(sdk.getHomePath())) {
+      return sdk.getHomePath();
+    }
     // TODO Need exact paths
+    String userHome = System.getProperty("user.home");
     if (SystemInfo.isWindows) {
-      return "C:\\Android SDK";
+      return FileUtil.join(userHome, "AppData", "Local", "Android", "Sdk");
     }
-    else if (SystemInfo.isLinux) {
-      return "/usr/local/androidsdk";
-    }
-    else if (SystemInfo.isMac) {
-      return String.format("%s/Android SDK", System.getProperty("user.home"));
+    else if (SystemInfo.isLinux || SystemInfo.isMac) {
+      return FileUtil.join(userHome, "Android", "Sdk");
     }
     else {
       throw new IllegalStateException("Unsupported OS");
diff --git a/android/src/com/android/tools/idea/welcome/Haxm.java b/android/src/com/android/tools/idea/welcome/Haxm.java
index 080cd68..99ce200 100644
--- a/android/src/com/android/tools/idea/welcome/Haxm.java
+++ b/android/src/com/android/tools/idea/welcome/Haxm.java
@@ -15,12 +15,14 @@
  */
 package com.android.tools.idea.welcome;
 
+import com.android.SdkConstants;
 import com.android.sdklib.devices.Storage;
+import com.android.sdklib.repository.descriptors.PkgDesc;
 import com.android.tools.idea.wizard.DynamicWizardStep;
 import com.android.tools.idea.wizard.ScopedStateStore;
 import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
 import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Platform;
 import com.intellij.execution.configurations.GeneralCommandLine;
 import com.intellij.execution.process.CapturingAnsiEscapesAwareProcessHandler;
 import com.intellij.execution.ui.ConsoleViewContentType;
@@ -28,8 +30,7 @@
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.util.SystemInfo;
-import com.intellij.util.download.DownloadableFileDescription;
-import com.intellij.util.download.DownloadableFileService;
+import com.intellij.openapi.util.io.FileUtil;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
@@ -37,7 +38,6 @@
 import java.lang.management.OperatingSystemMXBean;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.Set;
 
 /**
  * Intel® HAXM installable component
@@ -45,28 +45,39 @@
 public final class Haxm extends InstallableComponent {
   // In UI we cannot use longs, so we need to pick a unit other then byte
   public static final Storage.Unit UI_UNITS = Storage.Unit.MiB;
+  public static final Logger LOG = Logger.getInstance(Haxm.class);
+  public static final String COMPONENT_VENDOR = "intel";
+  public static final String COMPONENT_PATH = "Hardware_Accelerated_Execution_Manager";
   private static final ScopedStateStore.Key<Boolean> KEY_INSTALL_HAXM =
     ScopedStateStore.createKey("install.haxm", ScopedStateStore.Scope.PATH, Boolean.class);
   private static final ScopedStateStore.Key<Integer> KEY_EMULATOR_MEMORY_MB =
     ScopedStateStore.createKey("emulator.memory", ScopedStateStore.Scope.PATH, Integer.class);
-
-  private static final Logger LOG = Logger.getInstance(Haxm.class);
-
-  private static final DownloadableFileDescription myHaxmInstaller = DownloadableFileService.getInstance()
-    .createFileDescription(FirstRunWizardDefaults.getHaxmDownloadUrl(), FirstRunWizardDefaults.HAXM_INSTALLER_ARCHIVE_FILE_NAME);
-
+  private static long memorySize = -1;
   private final ScopedStateStore.Key<Boolean> myIsCustomInstall;
   private ScopedStateStore myState;
+  private ProgressStep myProgressStep;
+
 
   public Haxm(ScopedStateStore.Key<Boolean> isCustomInstall) {
     super("SDK Emulator Extra - Intel® HAXM", 2306867, KEY_INSTALL_HAXM);
     myIsCustomInstall = isCustomInstall;
   }
 
+  public static boolean canRun() {
+    // TODO HAXM is disabled as headless install is not in the repositories yet
+    if (Boolean.getBoolean("install.haxm") && (SystemInfo.isWindows || SystemInfo.isMac)) {
+      return getMemorySize() >= Storage.Unit.GiB.getNumberOfBytes();
+    }
+    else {
+      return false;
+    }
+  }
+
+  @NotNull
   private static GeneralCommandLine getMacHaxmInstallCommandLine(File source, int memorySize) {
     String shellScript = getAbsolutePathString(source, "silent_install.sh");
     String diskImage = getAbsolutePathString(source, "IntelHAXM_1.1.0_below_10.10.dmg");
-    // Explicitely calling bash so we don't have to deal with permissions on the shell script file.
+    // Explicitly calling bash so we don't have to deal with permissions on the shell script file.
     // Note that bash is preinstalled on Mac OS X so we can rely on it.
     String[] installerInvocation = {"/bin/bash", shellScript, "-f", diskImage, "-m", String.valueOf(memorySize)};
 
@@ -83,6 +94,7 @@
     return "'" + new File(source, filename).getAbsolutePath() + "'";
   }
 
+  @NotNull
   private static GeneralCommandLine getWindowsHaxmInstallCommandLine(File source, int memorySize) {
     // TODO
     GeneralCommandLine commandLine = new GeneralCommandLine();
@@ -97,6 +109,13 @@
   }
 
   public static long getMemorySize() {
+    if (memorySize < 0) {
+      memorySize = checkMemorySize();
+    }
+    return memorySize;
+  }
+
+  private static long checkMemorySize() {
     OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean();
     // This is specific to JDKs derived from Oracle JDK (including OpenJDK and Apple JDK among others).
     // Other then this, there's no standard way of getting memory size
@@ -125,15 +144,10 @@
     return 32L * Storage.Unit.GiB.getNumberOfBytes();
   }
 
-  @NotNull
   @Override
-  public Set<DownloadableFileDescription> getFilesToDownloadAndExpand() {
-    return ImmutableSet.of(myHaxmInstaller);
-  }
-
-  @Override
-  public void init(ScopedStateStore state) {
+  public void init(@NotNull ScopedStateStore state, @NotNull ProgressStep progressStep) {
     myState = state;
+    myProgressStep = progressStep;
     state.put(KEY_EMULATOR_MEMORY_MB, getRecommendedMemoryAllocation());
     state.put(KEY_INSTALL_HAXM, true);
   }
@@ -144,44 +158,59 @@
   }
 
   @Override
-  public boolean hasVisibleStep() {
-    return true;
+  public void configure(@NotNull InstallContext installContext, @NotNull File sdk) {
+    if (!canRun()) {
+      Logger.getInstance(getClass()).error("Tried to install HAXM on %s OS with %s memory size", Platform.current().name(),
+                                           String.valueOf(getMemorySize()));
+      installContext.print("Unable to install Intel HAXM", ConsoleViewContentType.ERROR_OUTPUT);
+      return;
+    }
+    GeneralCommandLine commandLine = getCommandLine(sdk);
+    try {
+      CapturingAnsiEscapesAwareProcessHandler process = new CapturingAnsiEscapesAwareProcessHandler(commandLine);
+      myProgressStep.attachToProcess(process);
+      int exitCode = process.runProcess().getExitCode();
+      if (exitCode != 0) {
+        // HAXM is not required so we do not stop setup process if this install failed.
+        myProgressStep.print("HAXM installation failed. To install HAXM follow the instructions found at " +
+                             FirstRunWizardDefaults.HAXM_DOCUMENTATION_URL + ".", ConsoleViewContentType.ERROR_OUTPUT);
+      }
+      else {
+        myProgressStep.print("Completed HAXM installation\n", ConsoleViewContentType.SYSTEM_OUTPUT);
+      }
+    }
+    catch (ExecutionException e) {
+      installContext.print("Unable to run Intel HAXM installer: " + e.getMessage(), ConsoleViewContentType.ERROR_OUTPUT);
+      LOG.error(e);
+    }
   }
 
-  @Override
-  public void perform(@NotNull InstallContext context) throws WizardException {
+  /**
+   * @return command line object or <code>null</code> if OS is not supported
+   */
+  @NotNull
+  private GeneralCommandLine getCommandLine(File sdk) {
     ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
     progressIndicator.setIndeterminate(true);
     progressIndicator.setText("Running Intel® HAXM installer");
     int memorySize = myState.getNotNull(KEY_EMULATOR_MEMORY_MB, getRecommendedMemoryAllocation());
-    File sourceLocation = context.getExpandedLocation(myHaxmInstaller);
-    GeneralCommandLine commandLine;
+    String path = FileUtil.join(SdkConstants.FD_EXTRAS, COMPONENT_VENDOR, COMPONENT_PATH);
+    File sourceLocation = new File(sdk, path);
     if (SystemInfo.isMac) {
-      commandLine = getMacHaxmInstallCommandLine(sourceLocation, memorySize);
+      return getMacHaxmInstallCommandLine(sourceLocation, memorySize);
     }
     else if (SystemInfo.isWindows) {
-      commandLine = getWindowsHaxmInstallCommandLine(sourceLocation, memorySize);
+      return getWindowsHaxmInstallCommandLine(sourceLocation, memorySize);
     }
     else {
-      throw new WizardException("Unsupported OS");
+      assert !canRun();
+      throw new IllegalStateException("Usupported OS");
     }
-    try {
-      CapturingAnsiEscapesAwareProcessHandler process = new CapturingAnsiEscapesAwareProcessHandler(commandLine);
-      ProgressStep progressStep = context.getProgressStep();
-      progressStep.attachToProcess(process);
-      int exitCode = process.runProcess().getExitCode();
-      if (exitCode != 0) {
-        // HAXM is not required so we do not stop setup process if this install failed.
-        progressStep.print("HAXM installation failed. To install HAXM follow the instructions found at " +
-                           FirstRunWizardDefaults.HAXM_DOCUMENTATION_URL + ".",
-                           ConsoleViewContentType.ERROR_OUTPUT);
-      }
-      else {
-        progressStep.print("Completed HAXM installation\n", ConsoleViewContentType.SYSTEM_OUTPUT);
-      }
-    }
-    catch (ExecutionException e) {
-      throw new WizardException("Unable to run HAXM installer", e);
-    }
+  }
+
+  @NotNull
+  @Override
+  public PkgDesc.Builder[] getRequiredSdkPackages() {
+    return new PkgDesc.Builder[]{InstallComponentsPath.createExtra(true, COMPONENT_VENDOR, COMPONENT_PATH)};
   }
 }
diff --git a/android/src/com/android/tools/idea/welcome/InstallComponentsPath.java b/android/src/com/android/tools/idea/welcome/InstallComponentsPath.java
index 5b0cbc0..7267b21 100644
--- a/android/src/com/android/tools/idea/welcome/InstallComponentsPath.java
+++ b/android/src/com/android/tools/idea/welcome/InstallComponentsPath.java
@@ -15,20 +15,39 @@
  */
 package com.android.tools.idea.welcome;
 
+import com.android.SdkConstants;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.updater.SdkUpdaterNoWindow;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.NoPreviewRevision;
+import com.android.sdklib.repository.descriptors.IdDisplay;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.tools.idea.sdk.DefaultSdks;
+import com.android.tools.idea.sdk.SdkMerger;
 import com.android.tools.idea.wizard.DynamicWizardPath;
 import com.android.tools.idea.wizard.DynamicWizardStep;
 import com.android.tools.idea.wizard.ScopedStateStore;
-import com.google.common.collect.ImmutableList;
+import com.android.utils.ILogger;
+import com.android.utils.NullLogger;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.ThrowableComputable;
 import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.util.download.DownloadableFileDescription;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.Contract;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
@@ -41,13 +60,20 @@
 public class InstallComponentsPath extends DynamicWizardPath implements LongRunningOperationPath {
   public static final ScopedStateStore.Key<Boolean> KEY_CUSTOM_INSTALL =
     ScopedStateStore.createKey("custom.install", ScopedStateStore.Scope.PATH, Boolean.class);
-  public static final InstallableComponent[] COMPONENTS = createComponents();
-  private static final Logger LOG = Logger.getInstance(InstallComponentsPath.class);
+  private static final InstallableComponent[] COMPONENTS = createComponents();
+  private static final ScopedStateStore.Key<String> KEY_SDK_INSTALL_LOCATION =
+    ScopedStateStore.createKey("download.sdk.location", ScopedStateStore.Scope.PATH, String.class);
+  private final ProgressStep myProgressStep;
   private InstallationTypeWizardStep myInstallationTypeWizardStep;
+  private SdkComponentsStep mySdkComponentsStep;
+
+  public InstallComponentsPath(@NotNull ProgressStep progressStep) {
+    myProgressStep = progressStep;
+  }
 
   private static InstallableComponent[] createComponents() {
-    AndroidSdk androidSdk = new AndroidSdk(KEY_CUSTOM_INSTALL);
-    if (SystemInfo.isWindows || SystemInfo.isMac) {
+    AndroidSdk androidSdk = new AndroidSdk();
+    if (Haxm.canRun()) {
       return new InstallableComponent[]{androidSdk, new Haxm(KEY_CUSTOM_INSTALL)};
     }
     else {
@@ -66,20 +92,172 @@
     return tempDirectory;
   }
 
+  private static boolean hasPlatformsDir(@Nullable File[] files) {
+    if (files == null) {
+      return false;
+    }
+    for (File file : files) {
+      if (isPlatformsDir(file)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static boolean isPlatformsDir(File file) {
+    return file.isDirectory() && file.getName().equalsIgnoreCase(SdkConstants.FD_PLATFORMS);
+  }
+
+  /**
+   * This is an attempt to isolate from SDK packaging peculiarities.
+   */
+  @NotNull
+  private static File getSdkRoot(@NotNull File expandedLocation) {
+    File[] files = expandedLocation.listFiles();
+    // Breadth-first scan - to lower chance of false positive
+    if (hasPlatformsDir(files)) {
+      return expandedLocation;
+    }
+    // Only scan one level down (no recursion) - avoid false positives
+    if (files != null) {
+      for (File file : files) {
+        if (hasPlatformsDir(file.listFiles())) {
+          return file;
+        }
+      }
+    }
+    return expandedLocation;
+  }
+
+  @VisibleForTesting
+  static boolean downloadAndUnzipSdkSeed(@NotNull InstallContext context, @NotNull File destination, double progressShare)
+    throws WizardException {
+    final double DOWNLOAD_OPERATION_PROGRESS_SHARE = progressShare * 0.8;
+    final double UNZIP_OPERATION_PROGRESS_SHARE = progressShare - DOWNLOAD_OPERATION_PROGRESS_SHARE;
+
+    File file = new DownloadOperation(context, FirstRunWizardDefaults.getSdkDownloadUrl(), DOWNLOAD_OPERATION_PROGRESS_SHARE).execute();
+    try {
+      return unzip(context, file, destination, UNZIP_OPERATION_PROGRESS_SHARE);
+    }
+    finally {
+      if (file != null && file.isFile() && file.getAbsolutePath().startsWith(context.getTempDirectory().getAbsolutePath())) {
+        FileUtil.delete(file);
+      }
+    }
+  }
+
+  private static boolean unzip(@NotNull InstallContext context, @Nullable File archive, @NotNull File destination, double progressShare)
+    throws WizardException {
+    if (archive == null) {
+      return false;
+    }
+    try {
+      FileUtil.ensureExists(destination.getParentFile());
+      File unpacked = new UnzipOperation(context, archive, progressShare).execute();
+      if (unpacked != null) {
+        try {
+          FileUtil.rename(getSdkRoot(unpacked), destination);
+        }
+        finally {
+          if (unpacked.isDirectory()) {
+            FileUtil.delete(unpacked);
+          }
+        }
+        return true;
+      }
+    }
+    catch (IOException e) {
+      throw new WizardException("Unable to prepare Android SDK", e);
+    }
+    return false;
+  }
+
+  @Nullable
+  @Contract("false, _, _ -> null;true, _, _ -> !null")
+  public static PkgDesc.Builder createExtra(boolean shouldInstallFlag, String vendor, String path) {
+    if (!shouldInstallFlag) {
+      return null;
+    }
+    return PkgDesc.Builder.newExtra(new IdDisplay(vendor, ""), path, "", null, new NoPreviewRevision(FullRevision.MISSING_MAJOR_REV));
+  }
+
+  private static File mergeRepoIntoDestination(final InstallContext context,
+                                               @NotNull final File repo,
+                                               @NotNull final File destination,
+                                               double progressRatio) throws WizardException {
+    try {
+      return context.run(new MergeOperation(destination, repo, context), progressRatio);
+    }
+    catch (IOException e) {
+      throw new WizardException(e.getMessage(), e);
+    }
+  }
+
+  private static boolean existsAndIsVisible(DynamicWizardStep step) {
+    return step != null && step.isStepVisible();
+  }
+
+  @VisibleForTesting
+  static void setupSdkComponents(@NotNull InstallContext installContext,
+                                 @NotNull File sdk,
+                                 @NotNull Collection<? extends InstallableComponent> selectedComponents,
+                                 double progressRatio) throws WizardException {
+    // TODO: Prompt about connection in handoff case?
+    Set<String> packages = Sets.newHashSet();
+    for (InstallableComponent component1 : selectedComponents) {
+      for (PkgDesc.Builder pkg : component1.getRequiredSdkPackages()) {
+        if (pkg != null) {
+          packages.add(pkg.create().getInstallId());
+        }
+      }
+    }
+    installContext.run(new InstallComponentsOperation(installContext, sdk, packages), progressRatio);
+    for (InstallableComponent component : selectedComponents) {
+      component.configure(installContext, sdk);
+    }
+  }
+
+  private static void setSdkInPreferences(final File sdk) {
+    final Application application = ApplicationManager.getApplication();
+    // SDK can only be set from write action, write action can only be started from UI thread
+    ApplicationManager.getApplication().invokeAndWait(new Runnable() {
+      @Override
+      public void run() {
+        application.runWriteAction(new Runnable() {
+          @Override
+          public void run() {
+            DefaultSdks.setDefaultAndroidHome(sdk, null);
+            AndroidFirstRunPersistentData.getInstance().markSdkUpToDate();
+          }
+        });
+      }
+    }, application.getAnyModalityState());
+  }
+
   @Override
   protected void init() {
-    boolean handoff = InstallerData.get(myState).exists();
-    if (!handoff) {
+    InstallerData data = InstallerData.get(myState);
+    String location = null;
+    if (!data.exists()) {
       myInstallationTypeWizardStep = new InstallationTypeWizardStep(KEY_CUSTOM_INSTALL);
+      location = data.getAndroidDest();
       addStep(myInstallationTypeWizardStep);
     }
+    if (StringUtil.isEmptyOrSpaces(location)) {
+      location = FirstRunWizardDefaults.getDefaultSdkLocation();
+    }
+    myState.put(KEY_SDK_INSTALL_LOCATION, location);
+
+    mySdkComponentsStep = new SdkComponentsStep(COMPONENTS, KEY_CUSTOM_INSTALL, KEY_SDK_INSTALL_LOCATION);
+    addStep(mySdkComponentsStep);
+
     for (InstallableComponent component : COMPONENTS) {
-      component.init(myState);
+      component.init(myState, myProgressStep);
       for (DynamicWizardStep step : component.createSteps()) {
         addStep(step);
       }
     }
-    if (SystemInfo.isLinux && !handoff) {
+    if (SystemInfo.isLinux && !data.exists()) {
       addStep(new LinuxHaxmInfoStep());
     }
   }
@@ -90,6 +268,19 @@
     return "Setup Android Studio Components";
   }
 
+  @Override
+  public void runLongOperation() throws WizardException {
+    final double INIT_SDK_OPERATION_PROGRESS_SHARE = 0.3;
+    final double INSTALL_COMPONENTS_OPERATION_PROGRESS_SHARE = 1.0 - INIT_SDK_OPERATION_PROGRESS_SHARE;
+
+    InstallContext installContext = new InstallContext(createTempDir(), myProgressStep);
+    final File sdk = initializeSdk(installContext, INIT_SDK_OPERATION_PROGRESS_SHARE);
+    if (sdk != null) {
+      setSdkInPreferences(sdk);
+      setupSdkComponents(installContext, sdk, getSelectedComponents(), INSTALL_COMPONENTS_OPERATION_PROGRESS_SHARE);
+    }
+  }
+
   private List<InstallableComponent> getSelectedComponents() throws WizardException {
     boolean customInstall = myState.getNotNull(KEY_CUSTOM_INSTALL, true);
     List<InstallableComponent> selectedOperations = Lists.newArrayListWithCapacity(COMPONENTS.length);
@@ -102,32 +293,46 @@
     return selectedOperations;
   }
 
-  @Override
-  public void runLongOperation(@NotNull ProgressStep progressStep) throws WizardException {
-    List<InstallableComponent> selectedComponents = getSelectedComponents();
-    File tempDirectory = createTempDir();
+  /**
+   * @return SDK location or <code>null</code> if the operation was cancelled
+   */
+  @Nullable
+  private File initializeSdk(InstallContext installContext, double progressRatio) throws WizardException {
+    String destinationPath = myState.get(KEY_SDK_INSTALL_LOCATION);
+    assert destinationPath != null;
+    final File destination = new File(destinationPath);
+    if (destination.isFile()) {
+      throw new WizardException(String.format("Path %s does not point to a directory", destination));
+    }
+    else if (destination.isDirectory()) {
+      SdkManager manager = SdkManager.createManager(destination.getAbsolutePath(), new NullLogger());
+      if (manager != null) {
+        installContext.advance(progressRatio);
+        // We got ourselves an SDK
+        return destination;
+      }
+    }
+    File handoffSource = getHandoffAndroidSdkSource();
+    if (handoffSource == null) {
+      return downloadAndUnzipSdkSeed(installContext, destination, progressRatio) ? destination : null;
+    }
+    else {
+      return mergeRepoIntoDestination(installContext, handoffSource, destination, progressRatio);
+    }
+  }
 
-    Set<DownloadableFileDescription> descriptions = Sets.newHashSet();
-    for (InstallableComponent component : selectedComponents) {
-      descriptions.addAll(component.getFilesToDownloadAndExpand());
-    }
-    InstallContext installContext = new InstallContext(tempDirectory, descriptions, progressStep);
-    List<PreinstallOperation> preinstallOperations =
-      ImmutableList.of(new DownloadOperation(installContext), new UnzipOperation(installContext));
-    try {
-      for (PreinstallOperation operation : preinstallOperations) {
-        if (!operation.execute()) {
-          return;
-        }
-      }
-      progressStep.getProgressIndicator().setIndeterminate(true);
-      for (InstallableComponent component : selectedComponents) {
-        component.perform(installContext);
+  @Nullable
+  private File getHandoffAndroidSdkSource() {
+    InstallerData data = InstallerData.get(myState);
+    String androidSrc = data.getAndroidSrc();
+    if (!StringUtil.isEmpty(androidSrc)) {
+      File srcFolder = new File(androidSrc);
+      File[] files = srcFolder.listFiles();
+      if (srcFolder.isDirectory() && files != null && files.length > 0) {
+        return srcFolder;
       }
     }
-    finally {
-      installContext.cleanup(progressStep);
-    }
+    return null;
   }
 
   @Override
@@ -142,21 +347,58 @@
   }
 
   public boolean showsStep() {
-    if (isPathVisible()) {
-      if (myInstallationTypeWizardStep != null && myInstallationTypeWizardStep.isStepVisible()) {
-        return true;
+    return isPathVisible() && (existsAndIsVisible(myInstallationTypeWizardStep) || existsAndIsVisible(mySdkComponentsStep));
+  }
+
+  private static class MergeOperation implements ThrowableComputable<File, IOException> {
+    private final File myDestination;
+    private final File myRepo;
+    private final InstallContext myContext;
+
+    public MergeOperation(File destination, File repo, InstallContext context) {
+      myDestination = destination;
+      myRepo = repo;
+      myContext = context;
+    }
+
+    @Override
+    public File compute() throws IOException {
+      ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
+      indicator.setText("Installing Android SDK");
+      indicator.setIndeterminate(true);
+      FileUtil.ensureExists(myDestination);
+      if (!FileUtil.filesEqual(myDestination.getCanonicalFile(), myRepo.getCanonicalFile())) {
+        SdkMerger.mergeSdks(myRepo, myDestination, indicator);
       }
-      try {
-        for (InstallableComponent component : getSelectedComponents()) {
-          if (component.hasVisibleStep()) {
-            return true;
-          }
-        }
+      myContext.print(String.format("Android SDK was installed to %s", myDestination), ConsoleViewContentType.SYSTEM_OUTPUT);
+      return myDestination;
+    }
+  }
+
+  private static class InstallComponentsOperation implements ThrowableComputable<Void, WizardException> {
+    private final InstallContext myContext;
+    private final File mySdkLocation;
+    private final Set<String> myComponents;
+
+    public InstallComponentsOperation(InstallContext context, File sdkLocation, Set<String> components) {
+      myContext = context;
+      mySdkLocation = sdkLocation;
+      myComponents = components;
+    }
+
+    @Override
+    public Void compute() throws WizardException {
+      final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
+      ILogger log = new SdkManagerProgressIndicatorIntegration(indicator, myContext, myComponents.size());
+      SdkManager manager = SdkManager.createManager(mySdkLocation.getAbsolutePath(), log);
+      if (manager != null) {
+        SdkUpdaterNoWindow updater = new SdkUpdaterNoWindow(manager.getLocation(), manager, log, false, true, null, null);
+        updater.updateAll(Lists.newArrayList(myComponents), true, false, null);
+        return null;
       }
-      catch (WizardException e) {
-        LOG.error(e);
+      else {
+        throw new WizardException("Corrupted SDK installation");
       }
     }
-    return false;
   }
 }
diff --git a/android/src/com/android/tools/idea/welcome/InstallContext.java b/android/src/com/android/tools/idea/welcome/InstallContext.java
index 29020abb..4d82267 100644
--- a/android/src/com/android/tools/idea/welcome/InstallContext.java
+++ b/android/src/com/android/tools/idea/welcome/InstallContext.java
@@ -15,76 +15,133 @@
  */
 package com.android.tools.idea.welcome;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
+import com.android.annotations.VisibleForTesting;
 import com.intellij.execution.ui.ConsoleViewContentType;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.util.download.DownloadableFileDescription;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.util.ProgressIndicatorBase;
+import com.intellij.openapi.util.ThrowableComputable;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
 
 /**
  * Keeps installation process state.
  */
 public class InstallContext {
   private final File myTempDirectory;
-  @NotNull private final List<DownloadableFileDescription> myDescriptions;
-  @NotNull private final ProgressStep myProgressStep;
-  private final Map<DownloadableFileDescription, File> myDownloadLocations = Maps.newHashMap();
-  private Map<DownloadableFileDescription, File> myExpandedLocations = Maps.newHashMap();
+  @Nullable private final ProgressStep myProgressStep;
 
-  public InstallContext(@NotNull File tempDirectory, @NotNull Iterable<DownloadableFileDescription> descriptions,
-                        @NotNull ProgressStep progressStep) {
+  @VisibleForTesting
+  InstallContext(@NotNull File tempDirectory) {
+    assert ApplicationManager.getApplication().isUnitTestMode();
     myTempDirectory = tempDirectory;
-    myDescriptions = ImmutableList.copyOf(descriptions);
+    myProgressStep = null;
+  }
+
+  @SuppressWarnings("NullableProblems")
+  public InstallContext(@NotNull File tempDirectory, @NotNull ProgressStep progressStep) {
+    myTempDirectory = tempDirectory;
     myProgressStep = progressStep;
   }
 
-  public File getExpandedLocation(DownloadableFileDescription downloadableFile) {
-    return myExpandedLocations.get(downloadableFile);
-  }
-
-  @NotNull
-  public ProgressStep getProgressStep() {
-    return myProgressStep;
-  }
-
-  public List<DownloadableFileDescription> getFilesToDownload() {
-    return myDescriptions;
-  }
-
-  public void setDownloadedLocation(DownloadableFileDescription downloadableFile, File destination) {
-    myDownloadLocations.put(downloadableFile, destination);
-  }
-
   public File getTempDirectory() {
     return myTempDirectory;
   }
 
-  public void cleanup(ProgressStep step) {
-    for (File file : Iterables.concat(myDownloadLocations.values(), myExpandedLocations.values())) {
-      if (!FileUtil.delete(file)) {
-        step.print(String.format("Can't delete %s\n", file.getAbsolutePath()), ConsoleViewContentType.ERROR_OUTPUT);
+  public boolean isCanceled() {
+    return myProgressStep != null && myProgressStep.isCanceled();
+  }
+
+  @SuppressWarnings("UseOfSystemOutOrSystemErr")
+  public void print(String message, ConsoleViewContentType contentType) {
+    if (myProgressStep != null) {
+      myProgressStep.print(message, contentType);
+    }
+    else {
+      if (contentType == ConsoleViewContentType.ERROR_OUTPUT) {
+        System.err.println(message);
+      }
+      else {
+        System.out.println(message);
       }
     }
   }
 
-  public Collection<File> getDownloadedFiles() {
-    return myDownloadLocations.values();
+  public <R, E extends Exception> R run(ThrowableComputable<R, E> operation, double progressRatio) throws E {
+    Wrapper<R, E> wrapper = new Wrapper<R, E>(operation);
+    if (myProgressStep != null) {
+      myProgressStep.run(wrapper, progressRatio);
+    }
+    else {
+      ProgressManager.getInstance().executeProcessUnderProgress(wrapper, new TestingProgressIndicator());
+    }
+    return wrapper.getResult();
   }
 
-  @Nullable
-  public File getDownloadLocation(DownloadableFileDescription description) {
-    return myDownloadLocations.get(description);
+  public void advance(double progress) {
+    if (myProgressStep != null) {
+      myProgressStep.advance(progress);
+    }
   }
 
-  public void setExpandedLocation(DownloadableFileDescription description, File dir) {
-    myExpandedLocations.put(description, dir);
+  @SuppressWarnings("UseOfSystemOutOrSystemErr")
+  private static class TestingProgressIndicator extends ProgressIndicatorBase {
+    private int previous = 0;
+
+    @Override
+    public void setText(String text) {
+      System.out.println(text);
+    }
+
+    @Override
+    public void setText2(String text) {
+      System.out.println("Text2: " + text);
+    }
+
+    @Override
+    public void setFraction(double fraction) {
+      int p = (int)Math.floor(fraction * 20);
+      if (p > previous) {
+        previous = p;
+        System.out.print(".");
+      }
+    }
+  }
+
+  private static class Wrapper<R, E extends Exception> implements Runnable {
+    private final ThrowableComputable<R, E> myRunnable;
+    private volatile R myResult;
+    private volatile E myException;
+
+    public Wrapper(ThrowableComputable<R, E> runnable) {
+      myRunnable = runnable;
+    }
+
+    @Override
+    public void run() {
+      try {
+        myResult = myRunnable.compute();
+      }
+      catch (RuntimeException e) {
+        throw e;
+      }
+      catch (Exception e) {
+        // We know this is not a runtime exception, hence it is the exception from the callable prototype
+        //noinspection unchecked
+        myException = (E)e;
+      }
+    }
+
+    private R getResult() throws E {
+      if (myException != null) {
+        throw myException;
+      }
+      else {
+        return myResult;
+      }
+    }
+
   }
 }
diff --git a/android/src/com/android/tools/idea/welcome/InstallableComponent.java b/android/src/com/android/tools/idea/welcome/InstallableComponent.java
index f87a165..1accc3e 100644
--- a/android/src/com/android/tools/idea/welcome/InstallableComponent.java
+++ b/android/src/com/android/tools/idea/welcome/InstallableComponent.java
@@ -15,12 +15,14 @@
  */
 package com.android.tools.idea.welcome;
 
+import com.android.sdklib.repository.descriptors.PkgDesc;
 import com.android.tools.idea.wizard.DynamicWizardStep;
 import com.android.tools.idea.wizard.ScopedStateStore;
 import com.intellij.util.download.DownloadableFileDescription;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.io.File;
 import java.util.Set;
 
 /**
@@ -85,14 +87,12 @@
     }
   }
 
-  public abstract void perform(@NotNull InstallContext downloaded) throws WizardException;
-
   @NotNull
-  public abstract Set<DownloadableFileDescription> getFilesToDownloadAndExpand();
+  public abstract PkgDesc.Builder[] getRequiredSdkPackages();
 
-  public abstract void init(ScopedStateStore state);
+  public abstract void init(@NotNull ScopedStateStore state, @NotNull ProgressStep progressStep);
 
   public abstract DynamicWizardStep[] createSteps();
 
-  public abstract boolean hasVisibleStep();
+  public abstract void configure(@NotNull InstallContext installContext, @NotNull File sdk);
 }
diff --git a/android/src/com/android/tools/idea/welcome/InstallerData.java b/android/src/com/android/tools/idea/welcome/InstallerData.java
index bef55e6..ab3d272 100644
--- a/android/src/com/android/tools/idea/welcome/InstallerData.java
+++ b/android/src/com/android/tools/idea/welcome/InstallerData.java
@@ -16,11 +16,13 @@
 package com.android.tools.idea.welcome;
 
 import com.android.annotations.VisibleForTesting;
+import com.android.prefs.AndroidLocation;
 import com.android.tools.idea.wizard.ScopedStateStore;
+import com.android.tools.idea.wizard.WizardUtils;
 import com.google.common.base.Objects;
 import com.google.common.io.Closeables;
-import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.util.text.StringUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -36,6 +38,8 @@
 public class InstallerData {
   @VisibleForTesting static final ScopedStateStore.Key<InstallerData> CONTEXT_KEY =
     ScopedStateStore.createKey("installer.handoff.data", ScopedStateStore.Scope.WIZARD, InstallerData.class);
+  public static final String PATH_FIRST_RUN_PROPERTIES = FileUtil.join("studio", "installer", "firstrun.properties");
+
   @Nullable private final String myJavaDir;
   @Nullable private final String myAndroidSrc;
   @Nullable private final String myAndroidDest;
@@ -55,19 +59,24 @@
 
   private static Properties readProperties() {
     Properties properties = new Properties();
-    File file = new File(System.getProperty("user.home"), ".android/firstlaunch/firstlaunch.properties");
-    if (file.isFile()) {
-      FileInputStream stream = null;
-      try {
-        stream = new FileInputStream(file);
-        properties.load(stream);
+    try {
+      File file = new File(AndroidLocation.getFolder(), PATH_FIRST_RUN_PROPERTIES);
+      if (file.isFile()) {
+        FileInputStream stream = null;
+        try {
+          stream = new FileInputStream(file);
+          properties.load(stream);
+        }
+        catch (IOException e) {
+          Logger.getInstance(InstallerData.class).error(e);
+        }
+        finally {
+          Closeables.closeQuietly(stream);
+        }
       }
-      catch (IOException e) {
-        Logger.getInstance(InstallerData.class).error(e);
-      }
-      finally {
-        Closeables.closeQuietly(stream);
-      }
+    }
+    catch (AndroidLocation.AndroidLocationException e) {
+      Logger.getInstance(InstallerData.class).error(e);
     }
     return properties;
   }
@@ -118,7 +127,7 @@
   }
 
   public boolean hasValidSdkLocation() {
-    return exists() && SdkComponentsStep.validateDestinationPath(getAndroidDest(), AndroidSdk.SIZE) == null;
+    return exists() && !WizardUtils.validateLocation(getAndroidDest(), SdkComponentsStep.FIELD_SDK_LOCATION, false).isError();
   }
 
   public boolean hasValidJdkLocation() {
diff --git a/android/src/com/android/tools/idea/welcome/LongRunningOperationPath.java b/android/src/com/android/tools/idea/welcome/LongRunningOperationPath.java
index a45ce63..f28d58f 100644
--- a/android/src/com/android/tools/idea/welcome/LongRunningOperationPath.java
+++ b/android/src/com/android/tools/idea/welcome/LongRunningOperationPath.java
@@ -15,11 +15,9 @@
  */
 package com.android.tools.idea.welcome;
 
-import org.jetbrains.annotations.NotNull;
-
 /**
  * Interface for paths that have a long-running operation running after the wizard completes.
  */
 public interface LongRunningOperationPath {
-  void runLongOperation(@NotNull ProgressStep progressStep) throws WizardException;
+  void runLongOperation() throws WizardException;
 }
diff --git a/android/src/com/android/tools/idea/welcome/PreinstallOperation.java b/android/src/com/android/tools/idea/welcome/PreinstallOperation.java
index 46f7b1d..257b5a6 100644
--- a/android/src/com/android/tools/idea/welcome/PreinstallOperation.java
+++ b/android/src/com/android/tools/idea/welcome/PreinstallOperation.java
@@ -15,19 +15,21 @@
  */
 package com.android.tools.idea.welcome;
 
-import com.google.common.util.concurrent.Atomics;
 import com.intellij.execution.ui.ConsoleViewContentType;
 import com.intellij.openapi.application.Application;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.ThrowableComputable;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
- * Operation that is executed prior to installing the application.
+ * <p>Operation that is executed prior to installing the application.</p>
+ *
+ * <p>Type argument specifies the type of the operation return value</p>
  */
-public abstract class PreinstallOperation {
+public abstract class PreinstallOperation<T> {
   protected final InstallContext myContext;
   private final double myProgressRatio;
 
@@ -39,21 +41,22 @@
   /**
    * Performs the actual logic
    */
-  protected abstract void perform() throws WizardException;
+  protected abstract T perform() throws WizardException;
 
   /**
    * Runs the operation under progress indicator that only gives access to progress portion.
    */
-  public final boolean execute() throws WizardException {
-    ProgressStep myProgressStep = myContext.getProgressStep();
-    if (myProgressStep.isCanceled()) {
-      return false;
+  @Nullable
+  public final T execute() throws WizardException {
+    if (myContext.isCanceled()) {
+      return null;
     }
-    Wrapper wrapper = new Wrapper();
-    myProgressStep.run(wrapper, myProgressRatio);
-    wrapper.rethrowIfExceptionRecorded();
-    return !myProgressStep.isCanceled();
-
+    return myContext.run(new ThrowableComputable<T, WizardException>() {
+      @Override
+      public T compute() throws WizardException {
+        return perform();
+      }
+    }, myProgressRatio);
   }
 
   /**
@@ -73,28 +76,7 @@
       throw new WizardException(failureDescription, e);
     }
     else {
-      myContext.getProgressStep().print(failureDescription + "\n", ConsoleViewContentType.ERROR_OUTPUT);
-    }
-  }
-
-  private final class Wrapper implements Runnable {
-    private final AtomicReference<WizardException> myException = Atomics.newReference(null);
-
-    @Override
-    public void run() {
-      try {
-        perform();
-      }
-      catch (WizardException e) {
-        myException.set(e);
-      }
-    }
-
-    public void rethrowIfExceptionRecorded() throws WizardException {
-      WizardException exception = myException.get();
-      if (exception != null) {
-        throw exception;
-      }
+      myContext.print(failureDescription + "\n", ConsoleViewContentType.ERROR_OUTPUT);
     }
   }
 }
diff --git a/android/src/com/android/tools/idea/welcome/ProgressStep.form b/android/src/com/android/tools/idea/welcome/ProgressStep.form
index b4ecd1d7..9dc633f 100644
--- a/android/src/com/android/tools/idea/welcome/ProgressStep.form
+++ b/android/src/com/android/tools/idea/welcome/ProgressStep.form
@@ -1,55 +1,73 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.android.tools.idea.welcome.ProgressStep">
-  <grid id="27dc6" binding="myRoot" layout-manager="GridLayoutManager" row-count="4" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+  <grid id="27dc6" binding="myRoot" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
     <margin top="0" left="0" bottom="0" right="0"/>
     <constraints>
-      <xy x="20" y="20" width="544" height="400"/>
+      <xy x="20" y="20" width="648" height="400"/>
     </constraints>
     <properties/>
     <border type="none"/>
     <children>
-      <component id="8eaa0" class="javax.swing.JLabel" binding="myLabel">
-        <constraints>
-          <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
-        </constraints>
-        <properties/>
-      </component>
-      <hspacer id="fefa0">
-        <constraints>
-          <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
-        </constraints>
-      </hspacer>
-      <component id="986d6" class="javax.swing.JProgressBar" binding="myProgressBar">
-        <constraints>
-          <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false">
-            <minimum-size width="500" height="-1"/>
-          </grid>
-        </constraints>
-        <properties>
-          <indeterminate value="true"/>
-        </properties>
-      </component>
-      <hspacer id="e0562">
-        <constraints>
-          <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
-        </constraints>
-      </hspacer>
-      <component id="686b1" class="javax.swing.JButton" binding="myShowDetailsButton" default-binding="true">
-        <constraints>
-          <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
-        </constraints>
-        <properties>
-          <text value="Show Details"/>
-        </properties>
-      </component>
       <grid id="fc7ff" binding="myConsole" layout-manager="BorderLayout" hgap="0" vgap="0">
         <constraints>
-          <grid row="3" column="0" row-span="1" col-span="3" vsize-policy="7" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+          <grid row="2" column="0" row-span="1" col-span="3" vsize-policy="7" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
         </constraints>
         <properties/>
         <border type="none"/>
         <children/>
       </grid>
+      <grid id="14145" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+        <margin top="0" left="0" bottom="0" right="0"/>
+        <constraints>
+          <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties/>
+        <border type="none">
+          <size top="0" left="20" bottom="0" right="20"/>
+        </border>
+        <children>
+          <component id="8eaa0" class="javax.swing.JLabel" binding="myLabel">
+            <constraints>
+              <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
+                <minimum-size width="650" height="-1"/>
+                <preferred-size width="650" height="-1"/>
+                <maximum-size width="650" height="-1"/>
+              </grid>
+            </constraints>
+            <properties/>
+          </component>
+          <component id="986d6" class="javax.swing.JProgressBar" binding="myProgressBar">
+            <constraints>
+              <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
+                <minimum-size width="650" height="-1"/>
+                <preferred-size width="650" height="-1"/>
+                <maximum-size width="650" height="-1"/>
+              </grid>
+            </constraints>
+            <properties>
+              <indeterminate value="true"/>
+            </properties>
+          </component>
+        </children>
+      </grid>
+      <hspacer id="78ad1">
+        <constraints>
+          <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+      </hspacer>
+      <hspacer id="9470d">
+        <constraints>
+          <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+      </hspacer>
+      <component id="686b1" class="javax.swing.JButton" binding="myShowDetailsButton" default-binding="true">
+        <constraints>
+          <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <text value="Show Details"/>
+        </properties>
+      </component>
     </children>
   </grid>
 </form>
diff --git a/android/src/com/android/tools/idea/welcome/ProgressStep.java b/android/src/com/android/tools/idea/welcome/ProgressStep.java
index 847caa4..8c09ee5 100644
--- a/android/src/com/android/tools/idea/welcome/ProgressStep.java
+++ b/android/src/com/android/tools/idea/welcome/ProgressStep.java
@@ -28,6 +28,7 @@
 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.ThrowableComputable;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -172,6 +173,10 @@
     myProgressBar.setValue((int)(1000 * fraction));
   }
 
+  public void advance(double progress) {
+    getProgressIndicator().setFraction(myFraction + progress);
+  }
+
   /**
    * Progress indicator that scales task to only use a portion of the parent indicator.
    */
diff --git a/android/src/com/android/tools/idea/welcome/SdkComponentsStep.java b/android/src/com/android/tools/idea/welcome/SdkComponentsStep.java
index 39d79fc..f832814 100644
--- a/android/src/com/android/tools/idea/welcome/SdkComponentsStep.java
+++ b/android/src/com/android/tools/idea/welcome/SdkComponentsStep.java
@@ -15,8 +15,10 @@
  */
 package com.android.tools.idea.welcome;
 
+import com.android.tools.idea.templates.TemplateUtils;
 import com.android.tools.idea.wizard.ScopedStateStore;
 import com.android.tools.idea.wizard.WizardConstants;
+import com.android.tools.idea.wizard.WizardUtils;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
@@ -27,8 +29,8 @@
 import com.intellij.ui.table.JBTable;
 import com.intellij.uiDesigner.core.GridConstraints;
 import com.intellij.uiDesigner.core.GridLayoutManager;
-import com.intellij.util.PathUtil;
 import com.intellij.util.ui.UIUtil;
+import org.jetbrains.android.sdk.AndroidSdkData;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -52,6 +54,8 @@
  * Wizard page for selecting SDK components to download.
  */
 public class SdkComponentsStep extends FirstRunWizardStep {
+  public static final String FIELD_SDK_LOCATION = "SDK location";
+
   private final InstallableComponent[] myInstallableComponents;
   private final ScopedStateStore.Key<Boolean> myKeyInstallSdk;
   private JPanel myContents;
@@ -65,9 +69,9 @@
   private TextFieldWithBrowseButton myPath;
   private boolean myUserEditedPath = false;
 
-  public SdkComponentsStep(InstallableComponent[] components,
-                           ScopedStateStore.Key<Boolean> keyInstallSdk,
-                           ScopedStateStore.Key<String> sdkDownloadPathKey) {
+  public SdkComponentsStep(@NotNull InstallableComponent[] components,
+                           @NotNull ScopedStateStore.Key<Boolean> keyInstallSdk,
+                           @NotNull ScopedStateStore.Key<String> sdkDownloadPathKey) {
     super("SDK Settings");
 
     myPath.addBrowseFolderListener("Android SDK", "Select Android SDK install directory", null,
@@ -165,44 +169,39 @@
     return file;
   }
 
-  @Nullable
-  public static String validateDestinationPath(@Nullable String path, long componentsSize) {
-    if (StringUtil.isEmpty(path)) {
-      return "Path is empty";
-    }
-    else {
-      File file = new File(path);
-      while (file != null && !file.exists()) {
-        if (!PathUtil.isValidFileName(file.getName())) {
-          return "Specified path is not valid";
-        }
-        file = file.getParentFile();
-      }
-    }
-    File filesystem = getTargetFilesystem(path);
-    if (filesystem != null) {
-      long diskSpace = filesystem.getUsableSpace();
-      if (diskSpace == 0) {
-        return "Selected location is not writeable";
-      }
-      else {
-        if (componentsSize >= diskSpace) {
-          return "Not enough disk space";
-        }
-      }
-    }
-    return null;
-  }
-
   @Override
   public boolean validate() {
     String path = myState.get(mySdkDownloadPathKey);
     if (!StringUtil.isEmpty(path)) {
       myUserEditedPath = true;
     }
-    String error = validateDestinationPath(path, getComponentsSize());
-    setErrorHtml(myUserEditedPath ? error : null);
-    return error == null;
+    WizardUtils.ValidationResult error = WizardUtils.validateLocation(path, FIELD_SDK_LOCATION, false);
+    String message = error.isOk() ? null : error.getFormattedMessage();
+    boolean isOk = !error.isError();
+    if (isOk) {
+      File filesystem = getTargetFilesystem(path);
+      if (!(filesystem == null || filesystem.getFreeSpace() > getComponentsSize())) {
+        isOk = false;
+        message = "Target drive does not have enough free space";
+      }
+      else if (isNonEmptyNonSdk(path)) {
+        isOk = true;
+        message = "Target folder is neither empty nor does it point to an existing SDK installation.";
+      }
+    }
+    setErrorHtml(myUserEditedPath ? message : null);
+    return isOk;
+  }
+
+  private static boolean isNonEmptyNonSdk(@Nullable String path) {
+    if (path == null) {
+      return false;
+    }
+    File file = new File(path);
+    if (file.exists() && TemplateUtils.listFiles(file).length > 0) {
+      return AndroidSdkData.getSdkData(file) == null;
+    }
+    return false;
   }
 
   @Override
diff --git a/android/src/com/android/tools/idea/welcome/SdkManagerProgressIndicatorIntegration.java b/android/src/com/android/tools/idea/welcome/SdkManagerProgressIndicatorIntegration.java
new file mode 100644
index 0000000..a948e18
--- /dev/null
+++ b/android/src/com/android/tools/idea/welcome/SdkManagerProgressIndicatorIntegration.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 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.tools.idea.welcome;
+
+import com.android.tools.idea.sdk.SdkLoggerIntegration;
+import com.google.common.base.Objects;
+import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Integrates SDK manager progress reporting with the wizard UI.
+ */
+public class SdkManagerProgressIndicatorIntegration extends SdkLoggerIntegration {
+  private final ProgressIndicator myIndicator;
+  private final InstallContext myContext;
+  private final int myComponentCount;
+  private int myCompletedOperations = -1;
+  private String previousTitle;
+
+  public SdkManagerProgressIndicatorIntegration(@NotNull ProgressIndicator indicator,
+                                                @NotNull InstallContext context,
+                                                int componentCount) {
+    assert componentCount > 0;
+    myIndicator = indicator;
+    myContext = context;
+    myComponentCount = componentCount;
+  }
+
+  @Override
+  protected void setProgress(int progress) {
+    double completedOperations = progress / 100.0 + myCompletedOperations;
+    double progressBar = completedOperations / (myComponentCount * 2); // Installing a component is 2 operations - download + unzip
+    myIndicator.setFraction(progressBar);
+  }
+
+  @Override
+  protected void setDescription(String description) {
+    // Nothing
+  }
+
+  @Override
+  protected void setTitle(String title) {
+    if (!StringUtil.isEmptyOrSpaces(title) && !Objects.equal(title, previousTitle)) {
+      previousTitle = title;
+      myCompletedOperations++;
+      myIndicator.setText(previousTitle);
+      setProgress(0);
+    }
+  }
+
+  @Override
+  protected void lineAdded(String string) {
+    myContext.print(string, ConsoleViewContentType.SYSTEM_OUTPUT);
+  }
+}
diff --git a/android/src/com/android/tools/idea/welcome/UnzipOperation.java b/android/src/com/android/tools/idea/welcome/UnzipOperation.java
index df7a0b9..aab1997 100644
--- a/android/src/com/android/tools/idea/welcome/UnzipOperation.java
+++ b/android/src/com/android/tools/idea/welcome/UnzipOperation.java
@@ -19,8 +19,8 @@
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.util.download.DownloadableFileDescription;
-import com.intellij.util.io.ZipUtil;
+import com.intellij.platform.templates.github.ZipUtil;
+import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
 import java.io.IOException;
@@ -28,17 +28,22 @@
 /**
  * Unzips the archives required for Android Studio setup.
  */
-public final class UnzipOperation extends PreinstallOperation {
-  public UnzipOperation(InstallContext context) {
-    super(context, 0.3);
+public final class UnzipOperation extends PreinstallOperation<File> {
+  private final InstallContext myContext;
+  private final File myArchive;
+
+  public UnzipOperation(InstallContext context, File archive, double progressShare) {
+    super(context, progressShare);
+    myContext = context;
+    myArchive = archive;
   }
 
-  private File unzip(File archive, File tempDirectory) throws WizardException {
-    myContext.getProgressStep().print(String.format("Unpacking %s\n", archive.getName()), ConsoleViewContentType.SYSTEM_OUTPUT);
+  private File unzip(ProgressIndicator progressIndicator, File archive, File tempDirectory) throws WizardException {
+    myContext.print(String.format("Unpacking %s\n", archive.getName()), ConsoleViewContentType.SYSTEM_OUTPUT);
     File dir = new File(tempDirectory, archive.getName() + "-unpacked");
     do {
       try {
-        ZipUtil.extract(archive, dir, null);
+        ZipUtil.unzip(progressIndicator, dir, archive, null, null, true);
         if (archive.getCanonicalPath().startsWith(myContext.getTempDirectory().getCanonicalPath())) {
           FileUtil.delete(archive); // Even if this fails, there's nothing we can do, and the folder should be deleted on exit anyways
         }
@@ -53,29 +58,13 @@
     while (true);
   }
 
+  @Nullable
   @Override
-  protected void perform() throws WizardException {
-    long allFiles = 0, done = 0;
+  protected File perform() throws WizardException {
     ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
-    progressIndicator.start();
-    progressIndicator.setText("Unpacking archives");
-    for (File file : myContext.getDownloadedFiles()) {
-      allFiles += file.length();
+    if (progressIndicator.isCanceled()) {
+      return null;
     }
-    try {
-      for (DownloadableFileDescription description : myContext.getFilesToDownload()) {
-        if (progressIndicator.isCanceled()) {
-          break;
-        }
-        File first = myContext.getDownloadLocation(description);
-        assert first != null;
-        myContext.setExpandedLocation(description, unzip(first, myContext.getTempDirectory()));
-        done += first.length();
-        progressIndicator.setFraction(1.0 * done / allFiles);
-      }
-    }
-    finally {
-      progressIndicator.stop();
-    }
+    return unzip(progressIndicator, myArchive, myContext.getTempDirectory());
   }
 }
diff --git a/android/src/com/android/tools/idea/wizard/ChooseAndroidAndJavaSdkStep.java b/android/src/com/android/tools/idea/wizard/ChooseAndroidAndJavaSdkStep.java
index 60dc541..02022a9 100644
--- a/android/src/com/android/tools/idea/wizard/ChooseAndroidAndJavaSdkStep.java
+++ b/android/src/com/android/tools/idea/wizard/ChooseAndroidAndJavaSdkStep.java
@@ -59,7 +59,7 @@
       ApplicationManager.getApplication().runWriteAction(new Runnable() {
         @Override
         public void run() {
-          DefaultSdks.setDefaultAndroidHome(new File(location), javaSdk);
+          DefaultSdks.setDefaultAndroidHome(new File(location), javaSdk, null);
         }
       });
     }
diff --git a/android/src/com/android/tools/idea/wizard/GetSdkStep.java b/android/src/com/android/tools/idea/wizard/GetSdkStep.java
index 3861702..fb5699b 100644
--- a/android/src/com/android/tools/idea/wizard/GetSdkStep.java
+++ b/android/src/com/android/tools/idea/wizard/GetSdkStep.java
@@ -30,7 +30,6 @@
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.TextFieldWithBrowseButton;
 import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VfsUtil;
@@ -40,6 +39,7 @@
 import com.intellij.platform.templates.github.Outcome;
 import com.intellij.platform.templates.github.ZipUtil;
 import org.jetbrains.android.sdk.AndroidSdkType;
+import org.jetbrains.android.sdk.AndroidSdkUtils;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -58,10 +58,6 @@
  */
 public class GetSdkStep extends DynamicWizardStepWithHeaderAndDescription {
   private static final Logger LOG = Logger.getInstance(GetSdkStep.class);
-  // TODO: Update these to a stable link
-  private static final String MAC_SDK_URL = "http://dl.google.com/android/android-sdk_r22.6.2-macosx.zip";
-  private static final String LINUX_SDK_URL = "http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz";
-  private static final String WINDOWS_SDK_URL = "http://dl.google.com/android/android-sdk_r22.6.2-windows.zip";
   private TextFieldWithBrowseButton mySdkLocationField;
   private JPanel myPanel;
   private JButton myDownloadANewSDKButton;
@@ -126,7 +122,7 @@
         @Override
         public File call() throws Exception {
           ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
-          String downloadUrl = getDownloadUrl();
+          String downloadUrl = AndroidSdkUtils.getSdkDownloadUrl();
           if (downloadUrl == null) {
             setErrorHtml("We cannot recognize your OS. Please visit http://developer.android.com/sdk/index.html and select" +
                          "the appropriate SDK bundle.");
@@ -167,7 +163,7 @@
     ApplicationManager.getApplication().runWriteAction(new Runnable() {
       @Override
       public void run() {
-        DefaultSdks.setDefaultAndroidHome(sdkFile);
+        DefaultSdks.setDefaultAndroidHome(sdkFile, null);
       }
     });
     return true;
@@ -240,17 +236,4 @@
       return null;
     }
   }
-
-  @Nullable
-  private String getDownloadUrl() {
-    if (SystemInfo.isLinux) {
-      return LINUX_SDK_URL;
-    } else if (SystemInfo.isWindows) {
-      return WINDOWS_SDK_URL;
-    } else if (SystemInfo.isMac) {
-      return MAC_SDK_URL;
-    } else {
-      return null;
-    }
-  }
 }
diff --git a/android/src/com/android/tools/idea/wizard/NewFormFactorModulePath.java b/android/src/com/android/tools/idea/wizard/NewFormFactorModulePath.java
index 8351c03..e485e360 100644
--- a/android/src/com/android/tools/idea/wizard/NewFormFactorModulePath.java
+++ b/android/src/com/android/tools/idea/wizard/NewFormFactorModulePath.java
@@ -203,6 +203,7 @@
     }
     if (myState.containsKey(NEWLY_INSTALLED_API_KEY)) {
       Integer newApiLevel = myState.get(NEWLY_INSTALLED_API_KEY);
+      assert newApiLevel != null;
       Key<Integer> targetApiLevelKey = FormFactorUtils.getTargetApiLevelKey(myFormFactor);
       Integer currentTargetLevel = myState.get(targetApiLevelKey);
       if (currentTargetLevel == null || newApiLevel > currentTargetLevel) {
diff --git a/android/src/com/android/tools/idea/wizard/WizardUtils.java b/android/src/com/android/tools/idea/wizard/WizardUtils.java
index 07c1d33..62dcbb1 100644
--- a/android/src/com/android/tools/idea/wizard/WizardUtils.java
+++ b/android/src/com/android/tools/idea/wizard/WizardUtils.java
@@ -18,9 +18,14 @@
 import com.android.annotations.VisibleForTesting;
 import com.android.tools.idea.templates.TemplateUtils;
 import com.google.common.base.CharMatcher;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationNamesInfo;
+import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleManager;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.ArrayUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -78,18 +83,20 @@
     }
 
     public enum Message {
-      NO_LOCATION_SPECIFIED("Please specify a project location"),
-      BAD_SLASHES("Your project location contains incorrect slashes ('\\' vs '/')"),
-      ILLEGAL_CHARACTER("Illegal character in project location path: '%1c' in filename %2s"),
-      ILLEGAL_FILENAME("Illegal filename in project location path: %s"),
-      WHITESPACE("Your project location contains whitespace. This can cause problems on some platforms and is not recommended."),
-      NON_ASCII_CHARS("Your project location contains non-ASCII characters. This can cause problems on Windows. Proceed with caution."),
-      PATH_NOT_WRITEABLE("The path '%s' is not writeable. Please choose a new location."),
-      PROJECT_LOC_IS_FILE("There must not already be a file at the project location."),
+      NO_LOCATION_SPECIFIED("Please specify a %1s"),
+      BAD_SLASHES("Your %1s contains incorrect slashes ('\\' vs '/')"),
+      ILLEGAL_CHARACTER("Illegal character in %1s path: '%2c' in filename %3s"),
+      ILLEGAL_FILENAME("Illegal filename in %1s path: %2s"),
+      WHITESPACE("Your %1s contains whitespace. This can cause problems on some platforms and is not recommended."),
+      NON_ASCII_CHARS("Your %1s contains non-ASCII characters. This can cause problems on Windows. Proceed with caution."),
+      PATH_NOT_WRITEABLE("The path '%2s' is not writeable. Please choose a new location."),
+      PROJECT_LOC_IS_FILE("There must not already be a file at the %1s."),
       NON_EMPTY_DIR(
-        "A non-empty directory already exists at the specified project location. Existing files may be overwritten. Proceed with caution."),
-      PROJECT_IS_FILE_SYSTEM_ROOT("The project location can not be at the filesystem root"),
-      PARENT_NOT_DIR("The project location's parent directory must be a directory, not a plain file");
+        "A non-empty directory already exists at the specified %1s. Existing files may be overwritten. Proceed with caution."),
+      PROJECT_IS_FILE_SYSTEM_ROOT("The %1s can not be at the filesystem root"),
+      IS_UNDER_ANDROID_STUDIO_ROOT("Path points to a location within Android Studio installation directory"),
+      PARENT_NOT_DIR("The %1s's parent directory must be a directory, not a plain file"),
+      INSIDE_ANDROID_STUDIO("The %1s is inside %2s install location");
 
       private final String myText;
 
@@ -103,24 +110,24 @@
       }
     }
 
-    public static final ValidationResult OK = new ValidationResult(Status.OK, null);
+    public static final ValidationResult OK = new ValidationResult(Status.OK, null, "any");
 
     private final Status myStatus;
     private final Message myMessage;
     private final Object[] myMessageParams;
 
-    private ValidationResult(@NotNull Status status, @Nullable Message message, Object... messageParams) {
+    private ValidationResult(@NotNull Status status, @Nullable Message message, @NotNull String field, Object... messageParams) {
       myStatus = status;
       myMessage = message;
-      myMessageParams = messageParams;
+      myMessageParams = ArrayUtil.prepend(field, messageParams);
     }
 
-    public static ValidationResult warn(@NotNull Message message, Object... params) {
-      return new ValidationResult(Status.WARN, message, params);
+    public static ValidationResult warn(@NotNull Message message, String field, Object... params) {
+      return new ValidationResult(Status.WARN, message, field, params);
     }
 
-    public static ValidationResult error(@NotNull Message message, Object... params) {
-      return new ValidationResult(Status.ERROR, message, params);
+    public static ValidationResult error(@NotNull Message message, String field, Object... params) {
+      return new ValidationResult(Status.ERROR, message, field, params);
     }
 
     @Nullable
@@ -156,19 +163,24 @@
   }
 
   /**
-   * Will return {@link com.android.tools.idea.wizard.WizardUtils.ValidationResult.OK} if projectLocation is valid
+   * Will return {@link com.android.tools.idea.wizard.WizardUtils.ValidationResult#OK} if projectLocation is valid
    * or {@link com.android.tools.idea.wizard.WizardUtils.ValidationResult} with error if not.
    */
   @NotNull
   public static ValidationResult validateLocation(@Nullable String projectLocation) {
+    return validateLocation(projectLocation, "project location", true);
+  }
+
+  @NotNull
+  public static ValidationResult validateLocation(@Nullable String projectLocation, @NotNull String fieldName, boolean checkEmpty) {
     ValidationResult warningResult = null;
     if (projectLocation == null || projectLocation.isEmpty()) {
-      return ValidationResult.error(ValidationResult.Message.NO_LOCATION_SPECIFIED);
+      return ValidationResult.error(ValidationResult.Message.NO_LOCATION_SPECIFIED, fieldName);
     }
     // Check the separators
     if ((File.separatorChar == '/' && projectLocation.contains("\\")) ||
         (File.separatorChar == '\\' && projectLocation.contains("/"))) {
-      return ValidationResult.error(ValidationResult.Message.BAD_SLASHES);
+      return ValidationResult.error(ValidationResult.Message.BAD_SLASHES, fieldName);
     }
     // Check the individual components for not allowed characters.
     File testFile = new File(projectLocation);
@@ -176,21 +188,21 @@
       String filename = testFile.getName();
       if (ILLEGAL_CHARACTER_MATCHER.matchesAnyOf(filename)) {
         char illegalChar = filename.charAt(ILLEGAL_CHARACTER_MATCHER.indexIn(filename));
-        return ValidationResult.error(ValidationResult.Message.ILLEGAL_CHARACTER, illegalChar, filename);
+        return ValidationResult.error(ValidationResult.Message.ILLEGAL_CHARACTER, fieldName, illegalChar, filename);
       }
       if (WizardConstants.INVALID_WINDOWS_FILENAMES.contains(filename.toLowerCase())) {
-        return ValidationResult.error(ValidationResult.Message.ILLEGAL_FILENAME, filename);
+        return ValidationResult.error(ValidationResult.Message.ILLEGAL_FILENAME, fieldName, filename);
       }
       if (CharMatcher.WHITESPACE.matchesAnyOf(filename)) {
-        warningResult = ValidationResult.warn(ValidationResult.Message.WHITESPACE);
+        warningResult = ValidationResult.warn(ValidationResult.Message.WHITESPACE, fieldName);
       }
       if (!CharMatcher.ASCII.matchesAllOf(filename)) {
-        warningResult = ValidationResult.warn(ValidationResult.Message.NON_ASCII_CHARS);
+        warningResult = ValidationResult.warn(ValidationResult.Message.NON_ASCII_CHARS, fieldName);
       }
       // Check that we can write to that location: make sure we can write into the first extant directory in the path.
       if (!testFile.exists() && testFile.getParentFile() != null && testFile.getParentFile().exists()) {
         if (!testFile.getParentFile().canWrite()) {
-          return ValidationResult.error(ValidationResult.Message.PATH_NOT_WRITEABLE, testFile.getParentFile().getPath());
+          return ValidationResult.error(ValidationResult.Message.PATH_NOT_WRITEABLE, fieldName, testFile.getParentFile().getPath());
         }
       }
       testFile = testFile.getParentFile();
@@ -198,18 +210,25 @@
 
     File file = new File(projectLocation);
     if (file.isFile()) {
-      return ValidationResult.error(ValidationResult.Message.PROJECT_LOC_IS_FILE);
-    } else if (file.isDirectory() && TemplateUtils.listFiles(file).length > 0) {
-      return ValidationResult.warn(ValidationResult.Message.NON_EMPTY_DIR);
+      return ValidationResult.error(ValidationResult.Message.PROJECT_LOC_IS_FILE, fieldName);
     }
     if (file.getParent() == null) {
-      return ValidationResult.error(ValidationResult.Message.PROJECT_IS_FILE_SYSTEM_ROOT);
+      return ValidationResult.error(ValidationResult.Message.PROJECT_IS_FILE_SYSTEM_ROOT, fieldName);
     }
     if (file.getParentFile().exists() && !file.getParentFile().isDirectory()) {
-      return ValidationResult.error(ValidationResult.Message.PARENT_NOT_DIR);
+      return ValidationResult.error(ValidationResult.Message.PARENT_NOT_DIR, fieldName);
+    }
+
+    String installLocation = PathManager.getHomePathFor(Application.class);
+    if (installLocation != null && FileUtil.isAncestor(new File(installLocation), file, false)) {
+      String applicationName = ApplicationNamesInfo.getInstance().getProductName();
+      return ValidationResult.error(ValidationResult.Message.INSIDE_ANDROID_STUDIO, fieldName, applicationName);
+    }
+
+    if (checkEmpty && file.exists() && TemplateUtils.listFiles(file).length > 0) {
+      return ValidationResult.warn(ValidationResult.Message.NON_EMPTY_DIR, fieldName);
     }
 
     return (warningResult == null) ? ValidationResult.OK : warningResult;
   }
-
 }
diff --git a/android/src/com/intellij/android/designer/model/layout/TextDirection.java b/android/src/com/intellij/android/designer/model/layout/TextDirection.java
new file mode 100644
index 0000000..133874b
--- /dev/null
+++ b/android/src/com/intellij/android/designer/model/layout/TextDirection.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2014 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.intellij.android.designer.model.layout;
+
+import com.android.ide.common.resources.configuration.LayoutDirectionQualifier;
+import com.android.resources.LayoutDirection;
+import com.android.tools.idea.configurations.Configuration;
+import com.android.tools.idea.designer.SegmentType;
+import com.intellij.android.designer.designSurface.AndroidDesignerEditorPanel;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import static com.android.SdkConstants.*;
+
+/**
+ * RTL state for the designer editor. It provides helper methods to manage the RTL attributes.
+ */
+public enum TextDirection {
+  LEFT_TO_RIGHT,
+  RIGHT_TO_LEFT;
+
+  /**
+   * Returns the {@linkplain TextDirection} used in the passed {@link AndroidDesignerEditorPanel}. If the passed panel is null, LTR is used.
+   */
+  public static TextDirection fromAndroidDesignerEditorPanel(@Nullable AndroidDesignerEditorPanel panel) {
+    Configuration configuration = panel != null ? panel.getConfiguration() : null;
+
+    if (configuration == null) {
+      return LEFT_TO_RIGHT;
+    }
+    LayoutDirectionQualifier qualifier = configuration.getFullConfig().getLayoutDirectionQualifier();
+    return qualifier == null || qualifier.getValue() != LayoutDirection.RTL ? LEFT_TO_RIGHT : RIGHT_TO_LEFT;
+  }
+
+  /**
+   * Returns the RTL SegmentType that corresponds to the LEFT side of the screen. This will be different depending if
+   * the user has RTL preview enabled or not.
+   */
+  @NotNull
+  public SegmentType getLeftSegment() {
+    return this == LEFT_TO_RIGHT ? SegmentType.START : SegmentType.END;
+  }
+
+  /**
+   * Returns the RTL SegmentType that corresponds to the RIGHT side of the screen. This will be different depending if
+   * the user has RTL preview enabled or not.
+   */
+  @NotNull
+  public SegmentType getRightSegment() {
+    return this == LEFT_TO_RIGHT ? SegmentType.END : SegmentType.START;
+  }
+
+  /**
+   * Returns whether the segment type is being seen on the left side of the screen. This is always the
+   * SegmentType.LEFT but it could also be SegmentType.START (if LTR is on) or SegmentType.END (if RTL is on)
+   *
+   * @see #getLeftSegment
+   */
+  public boolean isLeftSegment(@NotNull SegmentType type) {
+    return type == SegmentType.LEFT || type == getLeftSegment();
+  }
+
+  /**
+   * Returns whether the segment type is being seen on the right side of the screen. This is always the
+   * SegmentType.RIGHT but it could also be SegmentType.END (if LTR is on) or SegmentType.START (if RTL is on)
+   *
+   * @see #getRightSegment
+   */
+  public boolean isRightSegment(@NotNull SegmentType type) {
+    return type == SegmentType.RIGHT || type == getRightSegment();
+  }
+
+  /**
+   * Returns the RTL attribute name for aligning components left on screen.
+   */
+  @NotNull
+  public String getAttrLeft() {
+    return this == LEFT_TO_RIGHT ? ATTR_LAYOUT_ALIGN_START : ATTR_LAYOUT_ALIGN_END;
+  }
+
+  /**
+   * Returns the RTL attribute name for aligning components left on screen.
+   */
+  @NotNull
+  public String getAttrLeftOf() {
+    return this == LEFT_TO_RIGHT ? ATTR_LAYOUT_TO_START_OF : ATTR_LAYOUT_TO_END_OF;
+  }
+
+  /**
+   * Returns the RTL attribute name for aligning components right on screen.
+   */
+  @NotNull
+  public String getAttrRight() {
+    return this == LEFT_TO_RIGHT ? ATTR_LAYOUT_ALIGN_END : ATTR_LAYOUT_ALIGN_START;
+  }
+
+  /**
+   * Returns the RTL attribute name for aligning components right-of on screen.
+   */
+  @NotNull
+  public String getAttrRightOf() {
+    return this == LEFT_TO_RIGHT ? ATTR_LAYOUT_TO_END_OF : ATTR_LAYOUT_TO_START_OF;
+  }
+
+  /**
+   * Returns the RTL attribute name for aligning components parent-left on screen.
+   */
+  @NotNull
+  public String getAttrAlignParentLeft() {
+    return this == LEFT_TO_RIGHT ? ATTR_LAYOUT_ALIGN_PARENT_START : ATTR_LAYOUT_ALIGN_PARENT_END;
+  }
+
+  /**
+   * Returns the RTL attribute name for aligning components parent-right on screen.
+   */
+  @NotNull
+  public String getAttrAlignParentRight() {
+    return this == LEFT_TO_RIGHT ? ATTR_LAYOUT_ALIGN_PARENT_END : ATTR_LAYOUT_ALIGN_PARENT_START;
+  }
+
+  /**
+   * Returns the RTL attribute name to specify the margin on the left side of the component.
+   */
+  @NotNull
+  public String getAttrMarginLeft() {
+    return this == LEFT_TO_RIGHT ? ATTR_LAYOUT_MARGIN_START : ATTR_LAYOUT_MARGIN_END;
+  }
+
+  /**
+   * Returns the RTL attribute name to specify the margin on the right side of the component.
+   */
+  @NotNull
+  public String getAttrMarginRight() {
+    return this == LEFT_TO_RIGHT ? ATTR_LAYOUT_MARGIN_END : ATTR_LAYOUT_MARGIN_START;
+  }
+}
diff --git a/android/src/com/intellij/android/designer/model/layout/actions/OrientationAction.java b/android/src/com/intellij/android/designer/model/layout/actions/OrientationAction.java
index 243e322..e1bcec7 100644
--- a/android/src/com/intellij/android/designer/model/layout/actions/OrientationAction.java
+++ b/android/src/com/intellij/android/designer/model/layout/actions/OrientationAction.java
@@ -43,9 +43,8 @@
     super(designer, "Change attribute 'orientation'", null, AndroidDesignerIcons.SwitchHorizontalLinear);
     myLayout = layout;
     myDefaultHorizontal = defaultHorizontal;
-    update(getTemplatePresentation());
-
     myHorizontal = !VALUE_VERTICAL.equals(layout.getTag().getAttributeValue(ATTR_ORIENTATION, ANDROID_URI));
+    update(getTemplatePresentation());
   }
 
   @Override
diff --git a/android/src/com/intellij/android/designer/model/layout/relative/ConstraintPainter.java b/android/src/com/intellij/android/designer/model/layout/relative/ConstraintPainter.java
index fe7ca35..46b469a 100644
--- a/android/src/com/intellij/android/designer/model/layout/relative/ConstraintPainter.java
+++ b/android/src/com/intellij/android/designer/model/layout/relative/ConstraintPainter.java
@@ -19,6 +19,7 @@
 import com.android.tools.idea.designer.SegmentType;
 import com.intellij.android.designer.designSurface.graphics.DesignerGraphics;
 import com.intellij.android.designer.model.RadViewComponent;
+import com.intellij.android.designer.model.layout.TextDirection;
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
@@ -52,7 +53,8 @@
    * @param state        the handler state
    * @param match        the match
    */
-  static void paintConstraint(DesignerGraphics graphics, GuidelineHandler state, Match match) {
+  static void paintConstraint(
+    DesignerGraphics graphics, GuidelineHandler state, Match match) {
     RadViewComponent node = match.edge.node;
     if (node == null) {
       return;
@@ -63,7 +65,8 @@
     assert type != null;
     Rectangle sourceBounds = state.myBounds;
     paintConstraint(graphics, type, node, sourceBounds, node, targetBounds, null /* allConstraints */,
-                    true /* highlightTargetEdge */);
+                    true /* highlightTargetEdge */,
+                    state.myTextDirection);
   }
 
   /**
@@ -75,7 +78,10 @@
    * @param graphics   the graphics context to draw into
    * @param constraint The constraint to be drawn
    */
-  private static void paintConstraint(DesignerGraphics graphics, DependencyGraph.Constraint constraint, Set<DependencyGraph.Constraint> allConstraints) {
+  private static void paintConstraint(DesignerGraphics graphics,
+                                      DependencyGraph.Constraint constraint,
+                                      Set<DependencyGraph.Constraint> allConstraints,
+                                      TextDirection textDirection) {
     DependencyGraph.ViewData source = constraint.from;
     DependencyGraph.ViewData target = constraint.to;
 
@@ -89,7 +95,8 @@
     JComponent targetComponent = graphics.getTarget();
     Rectangle sourceBounds = sourceNode.getBounds(targetComponent);
     Rectangle targetBounds = targetNode.getBounds(targetComponent);
-    paintConstraint(graphics, constraint.type, sourceNode, sourceBounds, targetNode, targetBounds, allConstraints, false /* highlightTargetEdge */);
+    paintConstraint(graphics, constraint.type, sourceNode, sourceBounds, targetNode, targetBounds,
+                    allConstraints, false /* highlightTargetEdge */, textDirection);
   }
 
   /**
@@ -103,7 +110,8 @@
   public static void paintSelectionFeedback(DesignerGraphics graphics,
                                             RadViewComponent parentNode,
                                             List<? extends RadViewComponent> childNodes,
-                                            boolean showDependents) {
+                                            boolean showDependents,
+                                            TextDirection textDirection) {
 
     DependencyGraph dependencyGraph = DependencyGraph.get(parentNode);
     Set<RadViewComponent> horizontalDeps = dependencyGraph.dependsOn(childNodes, false /* vertical */);
@@ -131,18 +139,18 @@
 
       // Paint all incoming constraints
       if (showDependents) {
-        paintConstraints(graphics, view.dependedOnBy);
+        paintConstraints(graphics, view.dependedOnBy, textDirection);
       }
 
       // Paint all outgoing constraints
-      paintConstraints(graphics, view.dependsOn);
+      paintConstraints(graphics, view.dependsOn, textDirection);
     }
   }
 
   /**
    * Paints a set of constraints.
    */
-  private static void paintConstraints(DesignerGraphics graphics, List<DependencyGraph.Constraint> constraints) {
+  private static void paintConstraints(DesignerGraphics graphics, List<DependencyGraph.Constraint> constraints, TextDirection textDirection) {
     Set<DependencyGraph.Constraint> mutableConstraintSet = new HashSet<DependencyGraph.Constraint>(constraints);
 
     // WORKAROUND! Hide alignBottom attachments if we also have a alignBaseline
@@ -164,7 +172,7 @@
       // paintConstraint can digest more than one constraint, so we need to keep
       // checking to see if the given constraint is still relevant.
       if (mutableConstraintSet.contains(constraint)) {
-        paintConstraint(graphics, constraint, mutableConstraintSet);
+        paintConstraint(graphics, constraint, mutableConstraintSet, textDirection);
       }
     }
   }
@@ -180,7 +188,8 @@
                                       RadViewComponent targetNode,
                                       Rectangle targetBounds,
                                       @Nullable Set<DependencyGraph.Constraint> allConstraints,
-                                      boolean highlightTargetEdge) {
+                                      boolean highlightTargetEdge,
+                                      TextDirection textDirection) {
 
     SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX;
     SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY;
@@ -201,20 +210,20 @@
 
     // Corner constraint?
     if (allConstraints != null && (type == ConstraintType.LAYOUT_ABOVE || type == ConstraintType.LAYOUT_BELOW || type == ConstraintType.LAYOUT_LEFT_OF || type == ConstraintType.LAYOUT_RIGHT_OF)) {
-      if (paintCornerConstraint(graphics, type, sourceNode, sourceBounds, targetNode, targetBounds, allConstraints)) {
+      if (paintCornerConstraint(graphics, type, sourceNode, sourceBounds, targetNode, targetBounds, allConstraints, textDirection)) {
         return;
       }
     }
 
     // Vertical constraint?
     if (sourceSegmentTypeX == SegmentType.UNKNOWN) {
-      paintVerticalConstraint(graphics, type, sourceNode, sourceBounds, targetNode, targetBounds, highlightTargetEdge);
+      paintVerticalConstraint(graphics, type, sourceNode, sourceBounds, targetNode, targetBounds, highlightTargetEdge, textDirection);
       return;
     }
 
     // Horizontal constraint?
     if (sourceSegmentTypeY == SegmentType.UNKNOWN) {
-      paintHorizontalConstraint(graphics, type, sourceNode, sourceBounds, targetNode, targetBounds, highlightTargetEdge);
+      paintHorizontalConstraint(graphics, type, sourceNode, sourceBounds, targetNode, targetBounds, highlightTargetEdge, textDirection);
       return;
     }
 
@@ -273,7 +282,8 @@
                                                Rectangle sourceBounds,
                                                RadViewComponent targetNode,
                                                Rectangle targetBounds,
-                                               Set<DependencyGraph.Constraint> allConstraints) {
+                                               Set<DependencyGraph.Constraint> allConstraints,
+                                               TextDirection textDirection) {
 
     SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX;
     SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY;
@@ -323,7 +333,7 @@
       }
 
       int x1, y1, x2, y2;
-      if (sourceSegmentTypeX == SegmentType.LEFT) {
+      if (textDirection.isLeftSegment(sourceSegmentTypeX)) {
         x1 = sourceBounds.x + 1 * sourceBounds.width / 4;
       }
       else {
@@ -335,7 +345,7 @@
       else {
         y1 = sourceBounds.y + 3 * sourceBounds.height / 4;
       }
-      if (targetSegmentTypeX == SegmentType.LEFT) {
+      if (textDirection.isLeftSegment(targetSegmentTypeX)) {
         x2 = targetBounds.x + 1 * targetBounds.width / 4;
       }
       else {
@@ -386,7 +396,8 @@
                                               Rectangle sourceBounds,
                                               RadViewComponent targetNode,
                                               Rectangle targetBounds,
-                                              boolean highlightTargetEdge) {
+                                              boolean highlightTargetEdge,
+                                              TextDirection textDirection) {
     SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY;
     SegmentType targetSegmentTypeY = type.targetSegmentTypeY;
     JComponent targetComponent = graphics.getTarget();
@@ -627,7 +638,8 @@
                                                 Rectangle sourceBounds,
                                                 RadViewComponent targetNode,
                                                 Rectangle targetBounds,
-                                                boolean highlightTargetEdge) {
+                                                boolean highlightTargetEdge,
+                                                TextDirection textDirection) {
     SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX;
     SegmentType targetSegmentTypeX = type.targetSegmentTypeX;
     JComponent targetComponent = graphics.getTarget();
@@ -638,8 +650,8 @@
 
     // See paintVerticalConstraint for explanations of the various cases.
 
-    int sourceX = sourceSegmentTypeX.getX(sourceNode, sourceBounds);
-    int targetX = targetSegmentTypeX == SegmentType.UNKNOWN ? sourceX : targetSegmentTypeX.getX(targetNode, targetBounds);
+    int sourceX = sourceSegmentTypeX.getX(textDirection, sourceNode, sourceBounds);
+    int targetX = targetSegmentTypeX == SegmentType.UNKNOWN ? sourceX : targetSegmentTypeX.getX(textDirection, targetNode, targetBounds);
 
     if (highlightTargetEdge && type.isRelativeToParentEdge()) {
       graphics.useStyle(DROP_ZONE_ACTIVE);
@@ -654,7 +666,7 @@
     int center = (maxTop + minBottom) / 2;
     if (center > sourceBounds.y && center < GuidelineHandler.y2(sourceBounds)) {
       // See if we should draw a margin line
-      if (targetSegmentTypeX == SegmentType.RIGHT && targetMargins.right > 5) {
+      if (textDirection.isRightSegment(targetSegmentTypeX) && targetMargins.right > 5) {
         int sharedX = targetX + targetMargins.right;
         if (sourceX > sharedX + 2) { // Skip when source falls on the margin line
           graphics.useStyle(GUIDELINE_DASHED);
@@ -671,7 +683,7 @@
         }
         return;
       }
-      else if (targetSegmentTypeX == SegmentType.LEFT && targetMargins.left > 5) {
+      else if (textDirection.isLeftSegment(targetSegmentTypeX) && targetMargins.left > 5) {
         int sharedX = targetX - targetMargins.left;
         if (sourceX < sharedX - 2) {
           graphics.useStyle(GUIDELINE_DASHED);
@@ -688,7 +700,7 @@
       }
 
       if (sourceX == targetX) {
-        if (sourceSegmentTypeX == SegmentType.RIGHT) {
+        if (textDirection.isRightSegment(sourceSegmentTypeX)) {
           sourceX -= 2 * ARROW_SIZE;
         }
         else if (sourceSegmentTypeX == SegmentType.LEFT) {
@@ -708,12 +720,12 @@
     // Segment line
 
     // Compute overlap region and pick the middle
-    int sharedX = targetSegmentTypeX == SegmentType.UNKNOWN ? sourceX : targetSegmentTypeX.getX(targetNode, targetBounds);
+    int sharedX = targetSegmentTypeX == SegmentType.UNKNOWN ? sourceX : targetSegmentTypeX.getX(textDirection, targetNode, targetBounds);
     if (type.relativeToMargin) {
-      if (targetSegmentTypeX == SegmentType.LEFT) {
+      if (textDirection.isLeftSegment(targetSegmentTypeX)) {
         sharedX -= targetMargins.left;
       }
-      else if (targetSegmentTypeX == SegmentType.RIGHT) {
+      else if (textDirection.isRightSegment(targetSegmentTypeX)) {
         sharedX += targetMargins.right;
       }
     }
@@ -755,7 +767,7 @@
 
     // Draw the line from the target to the horizontal shared edge
     int ty = GuidelineHandler.centerY(targetBounds);
-    if (targetSegmentTypeX == SegmentType.LEFT) {
+    if (textDirection.isLeftSegment(targetSegmentTypeX)) {
       int tx = targetBounds.x;
       int margin = targetMargins.left;
       if (margin == 0 || !type.relativeToMargin) {
@@ -766,7 +778,7 @@
       }
     }
     else {
-      assert targetSegmentTypeX == SegmentType.RIGHT;
+      assert textDirection.isRightSegment(targetSegmentTypeX);
       int tx = GuidelineHandler.x2(targetBounds);
       int margin = targetMargins.right;
       if (margin == 0 || !type.relativeToMargin) {
diff --git a/android/src/com/intellij/android/designer/model/layout/relative/ConstraintType.java b/android/src/com/intellij/android/designer/model/layout/relative/ConstraintType.java
index 6153071..7159582 100644
--- a/android/src/com/intellij/android/designer/model/layout/relative/ConstraintType.java
+++ b/android/src/com/intellij/android/designer/model/layout/relative/ConstraintType.java
@@ -37,8 +37,12 @@
   ALIGN_BOTTOM(ATTR_LAYOUT_ALIGN_BOTTOM, null, SegmentType.BOTTOM, null, SegmentType.BOTTOM, false, true, false, false),
   ALIGN_LEFT(ATTR_LAYOUT_ALIGN_LEFT, SegmentType.LEFT, null, SegmentType.LEFT, null, false, false, true, false),
   ALIGN_RIGHT(ATTR_LAYOUT_ALIGN_RIGHT, SegmentType.RIGHT, null, SegmentType.RIGHT, null, false, false, true, false),
+  LAYOUT_ALIGN_START(ATTR_LAYOUT_ALIGN_START, SegmentType.START, null, SegmentType.START, null, false, false, true, false),
+  LAYOUT_ALIGN_END(ATTR_LAYOUT_ALIGN_END, SegmentType.END, null, SegmentType.END, null, false, false, true, false),
   LAYOUT_LEFT_OF(ATTR_LAYOUT_TO_LEFT_OF, SegmentType.RIGHT, null, SegmentType.LEFT, null, false, false, true, true),
   LAYOUT_RIGHT_OF(ATTR_LAYOUT_TO_RIGHT_OF, SegmentType.LEFT, null, SegmentType.RIGHT, null, false, false, true, true),
+  LAYOUT_ALIGN_START_OF(ATTR_LAYOUT_TO_START_OF, SegmentType.START, null, SegmentType.START, null, false, false, true, false),
+  LAYOUT_ALIGN_END_OF(ATTR_LAYOUT_TO_END_OF, SegmentType.END, null, SegmentType.END, null, false, false, true, false),
   ALIGN_PARENT_TOP(ATTR_LAYOUT_ALIGN_PARENT_TOP, null, SegmentType.TOP, null, SegmentType.TOP, true, true, false, false),
   ALIGN_BASELINE(ATTR_LAYOUT_ALIGN_BASELINE, null, SegmentType.BASELINE, null, SegmentType.BASELINE, false, true, false, false),
   ALIGN_PARENT_LEFT(ATTR_LAYOUT_ALIGN_PARENT_LEFT, SegmentType.LEFT, null, SegmentType.LEFT, null, true, false, true, false),
@@ -223,6 +227,20 @@
         break;
       case BASELINE:
         return ALIGN_BASELINE;
+      case START:
+        switch (to) {
+          case START:
+            return LAYOUT_ALIGN_START;
+          case END:
+            return LAYOUT_ALIGN_END_OF;
+        }
+      case END:
+        switch (to) {
+          case START:
+            return LAYOUT_ALIGN_START_OF;
+          case END:
+            return LAYOUT_ALIGN_END;
+        }
     }
 
     return null;
diff --git a/android/src/com/intellij/android/designer/model/layout/relative/GuidelineHandler.java b/android/src/com/intellij/android/designer/model/layout/relative/GuidelineHandler.java
index 986c113..de29c78 100644
--- a/android/src/com/intellij/android/designer/model/layout/relative/GuidelineHandler.java
+++ b/android/src/com/intellij/android/designer/model/layout/relative/GuidelineHandler.java
@@ -19,7 +19,9 @@
 import com.android.tools.idea.designer.Segment;
 import com.android.tools.idea.designer.SegmentType;
 import com.intellij.android.designer.AndroidDesignerUtils;
+import com.intellij.android.designer.designSurface.AndroidDesignerEditorPanel;
 import com.intellij.android.designer.model.RadViewComponent;
+import com.intellij.android.designer.model.layout.TextDirection;
 import com.intellij.designer.designSurface.OperationContext;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.util.text.StringUtil;
@@ -48,6 +50,7 @@
  * like move and resize, and performs various constraint computations.
  */
 public class GuidelineHandler {
+
   /**
    * A dependency graph for the relative layout recording constraint relationships
    */
@@ -217,6 +220,11 @@
   protected String myErrorMessage;
 
   /**
+   * Is the operation running on an RTL locale?
+   */
+  protected final TextDirection myTextDirection;
+
+  /**
    * Construct a new {@link GuidelineHandler} for the given relative layout.
    *
    * @param layout the RelativeLayout to handle
@@ -225,6 +233,9 @@
     this.layout = layout;
     myContext = context;
 
+    AndroidDesignerEditorPanel panel = AndroidDesignerUtils.getPanel(myContext.getArea());
+    myTextDirection = TextDirection.fromAndroidDesignerEditorPanel(panel);
+
     myHorizontalEdges = new ArrayList<Segment>();
     myVerticalEdges = new ArrayList<Segment>();
     myCenterVertEdges = new ArrayList<Segment>();
@@ -422,7 +433,7 @@
         return dragged == SegmentType.TOP || dragged == SegmentType.BOTTOM;
       case LEFT:
       case RIGHT:
-        return dragged == SegmentType.LEFT || dragged == SegmentType.RIGHT;
+        return dragged == SegmentType.LEFT || dragged == SegmentType.RIGHT || dragged == SegmentType.START || dragged == SegmentType.END;
 
       // Center horizontal, center vertical and Baseline only matches the same
       // type, and only within the matching distance -- no margins!
@@ -572,6 +583,8 @@
       clearAttribute(n, ANDROID_URI, ATTR_LAYOUT_ALIGN_START);
       clearAttribute(n, ANDROID_URI, ATTR_LAYOUT_TO_RIGHT_OF);
       clearAttribute(n, ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL);
+      clearAttribute(n, ANDROID_URI, myTextDirection.getAttrLeft());
+      clearAttribute(n, ANDROID_URI, myTextDirection.getAttrLeftOf());
     }
 
     if (myMoveRight) {
@@ -582,6 +595,8 @@
       clearAttribute(n, ANDROID_URI, ATTR_LAYOUT_ALIGN_END);
       clearAttribute(n, ANDROID_URI, ATTR_LAYOUT_TO_LEFT_OF);
       clearAttribute(n, ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL);
+      clearAttribute(n, ANDROID_URI, myTextDirection.getAttrRight());
+      clearAttribute(n, ANDROID_URI, myTextDirection.getAttrRightOf());
     }
 
     if (myMoveTop && myCurrentTopMatch != null) {
@@ -600,7 +615,7 @@
 
     if (myMoveLeft && myCurrentLeftMatch != null) {
       String constraint = myCurrentLeftMatch.getConstraint(true);
-      String rtlConstraint = myCurrentLeftMatch.getRtlConstraint(true);
+      String rtlConstraint = myCurrentLeftMatch.getRtlConstraint(myTextDirection, true);
       if (rtlConstraint != null && supportsStartEnd()) {
         if (requiresRightLeft()) {
           applyConstraint(n, constraint);
@@ -613,7 +628,7 @@
 
     if (myMoveRight && myCurrentRightMatch != null) {
       String constraint = myCurrentRightMatch.getConstraint(true);
-      String rtlConstraint = myCurrentRightMatch.getRtlConstraint(true);
+      String rtlConstraint = myCurrentRightMatch.getRtlConstraint(myTextDirection, true);
       if (rtlConstraint != null && supportsStartEnd()) {
         if (requiresRightLeft()) {
           applyConstraint(n, constraint);
@@ -625,10 +640,26 @@
     }
 
     if (myMoveLeft) {
-      applyMargin(n, ATTR_LAYOUT_MARGIN_LEFT, getLeftMarginDp());
+      if (supportsStartEnd()) {
+        if (requiresRightLeft()) {
+          applyMargin(n, ATTR_LAYOUT_MARGIN_LEFT, getLeftMarginDp());
+        }
+        applyMargin(n, myTextDirection.getAttrMarginLeft(), getLeftMarginDp());
+      }
+      else {
+        applyMargin(n, ATTR_LAYOUT_MARGIN_LEFT, getLeftMarginDp());
+      }
     }
     if (myMoveRight) {
-      applyMargin(n, ATTR_LAYOUT_MARGIN_RIGHT, getRightMarginDp());
+      if (supportsStartEnd()) {
+        if (requiresRightLeft()) {
+          applyMargin(n, ATTR_LAYOUT_MARGIN_RIGHT, getRightMarginDp());
+        }
+        applyMargin(n, myTextDirection.getAttrMarginRight(), getRightMarginDp());
+      }
+      else {
+        applyMargin(n, ATTR_LAYOUT_MARGIN_RIGHT, getRightMarginDp());
+      }
     }
     if (myMoveTop) {
       applyMargin(n, ATTR_LAYOUT_MARGIN_TOP, getTopMarginDp());
@@ -689,6 +720,8 @@
     for (ConstraintType type : ConstraintType.values()) {
       clearAttribute(node, ANDROID_URI, type.name);
     }
+    clearAttribute(node, ANDROID_URI, ATTR_LAYOUT_MARGIN_START);
+    clearAttribute(node, ANDROID_URI, ATTR_LAYOUT_MARGIN_END);
     clearAttribute(node, ANDROID_URI, ATTR_LAYOUT_MARGIN_LEFT);
     clearAttribute(node, ANDROID_URI, ATTR_LAYOUT_MARGIN_RIGHT);
     clearAttribute(node, ANDROID_URI, ATTR_LAYOUT_MARGIN_TOP);
@@ -971,8 +1004,8 @@
       }
 
       // Prefer matching top/left edges before matching bottom/right edges
-      int orientation1 = (m1.with.edgeType == SegmentType.LEFT || m1.with.edgeType == SegmentType.TOP) ? -1 : 1;
-      int orientation2 = (m2.with.edgeType == SegmentType.LEFT || m2.with.edgeType == SegmentType.TOP) ? -1 : 1;
+      int orientation1 = (myTextDirection.isLeftSegment(m1.with.edgeType) || m1.with.edgeType == SegmentType.TOP) ? -1 : 1;
+      int orientation2 = (myTextDirection.isLeftSegment(m2.with.edgeType) || m2.with.edgeType == SegmentType.TOP) ? -1 : 1;
       if (orientation1 != orientation2) {
         return orientation1 - orientation2;
       }
diff --git a/android/src/com/intellij/android/designer/model/layout/relative/Match.java b/android/src/com/intellij/android/designer/model/layout/relative/Match.java
index 49d211b..7a97173 100644
--- a/android/src/com/intellij/android/designer/model/layout/relative/Match.java
+++ b/android/src/com/intellij/android/designer/model/layout/relative/Match.java
@@ -17,6 +17,7 @@
 
 import com.android.tools.idea.designer.Segment;
 import com.intellij.android.designer.designSurface.feedbacks.TextFeedback;
+import com.intellij.android.designer.model.layout.TextDirection;
 import com.intellij.ui.SimpleTextAttributes;
 import org.jetbrains.annotations.Nullable;
 
@@ -136,20 +137,20 @@
   }
 
   @Nullable
-  public String getRtlConstraint(boolean generateId) {
+  public String getRtlConstraint(TextDirection textDirection, boolean generateId) {
     switch (type) {
       case ALIGN_LEFT:
-        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_ALIGN_LEFT, ATTR_LAYOUT_ALIGN_START);
+        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_ALIGN_LEFT, textDirection.getAttrLeft());
       case LAYOUT_LEFT_OF:
-        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_TO_LEFT_OF, ATTR_LAYOUT_TO_START_OF);
+        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_TO_LEFT_OF, textDirection.getAttrLeftOf());
       case ALIGN_RIGHT:
-        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_ALIGN_RIGHT, ATTR_LAYOUT_ALIGN_END);
+        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_ALIGN_RIGHT, textDirection.getAttrRight());
       case LAYOUT_RIGHT_OF:
-        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_TO_RIGHT_OF, ATTR_LAYOUT_TO_END_OF);
+        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_TO_RIGHT_OF, textDirection.getAttrRightOf());
       case ALIGN_PARENT_LEFT:
-        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_ALIGN_PARENT_LEFT, ATTR_LAYOUT_ALIGN_PARENT_START);
+        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_ALIGN_PARENT_LEFT, textDirection.getAttrAlignParentLeft());
       case ALIGN_PARENT_RIGHT:
-        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_ALIGN_PARENT_RIGHT, ATTR_LAYOUT_ALIGN_PARENT_END);
+        return replaceAttribute(getConstraint(generateId), ATTR_LAYOUT_ALIGN_PARENT_RIGHT, textDirection.getAttrAlignParentRight());
     }
     return null;
   }
diff --git a/android/src/com/intellij/android/designer/model/layout/relative/MoveHandler.java b/android/src/com/intellij/android/designer/model/layout/relative/MoveHandler.java
index 172d8e2..7b4cbc7 100644
--- a/android/src/com/intellij/android/designer/model/layout/relative/MoveHandler.java
+++ b/android/src/com/intellij/android/designer/model/layout/relative/MoveHandler.java
@@ -50,9 +50,7 @@
    * @param elements the elements being dragged in the move operation
    * @param context  the applicable {@link com.intellij.designer.designSurface.OperationContext}
    */
-  public MoveHandler(@NotNull RadViewComponent layout,
-                     @NotNull List<RadViewComponent> elements,
-                     @NotNull OperationContext context) {
+  public MoveHandler(@NotNull RadViewComponent layout, @NotNull List<RadViewComponent> elements, @NotNull OperationContext context) {
     super(layout, context);
 
     // Compute list of nodes being dragged within the layout, if any
@@ -97,7 +95,7 @@
   @Override
   protected void snapVertical(Segment vEdge, int x, Rectangle newBounds) {
     int maxDistance = MAX_MATCH_DISTANCE;
-    if (vEdge.edgeType == SegmentType.LEFT) {
+    if (myTextDirection.isLeftSegment(vEdge.edgeType)) {
       int margin = !mySnap ? 0 : abs(newBounds.x - x);
       if (margin > maxDistance) {
         myLeftMargin = margin;
@@ -106,7 +104,7 @@
         newBounds.x = x;
       }
     }
-    else if (vEdge.edgeType == SegmentType.RIGHT) {
+    else if (myTextDirection.isRightSegment(vEdge.edgeType)) {
       int margin = !mySnap ? 0 : abs(newBounds.x - (x - newBounds.width));
       if (margin > maxDistance) {
         myRightMargin = margin;
@@ -181,10 +179,15 @@
     edge = new Segment(y2(b), b.x, x2(b), null, null, SegmentType.BOTTOM, NO_MARGIN);
     addClosest(edge, myHorizontalEdges, horizontalMatches);
 
+    // We add the LEFT and RIGHT segments. Also we add the START and END segments that will change based on the current RTL view context.
     edge = new Segment(b.x, b.y, y2(b), null, null, SegmentType.LEFT, NO_MARGIN);
     List<Match> verticalMatches = findClosest(edge, myVerticalEdges);
+    edge = new Segment(b.x, b.y, y2(b), null, null, myTextDirection.getLeftSegment(), NO_MARGIN);
+    addClosest(edge, myVerticalEdges, verticalMatches);
     edge = new Segment(x2(b), b.y, y2(b), null, null, SegmentType.RIGHT, NO_MARGIN);
     addClosest(edge, myVerticalEdges, verticalMatches);
+    edge = new Segment(x2(b), b.y, y2(b), null, null, myTextDirection.getRightSegment(), NO_MARGIN);
+    addClosest(edge, myVerticalEdges, verticalMatches);
 
     // Match center
     edge = new Segment(centerX(b), b.y, y2(b), null, null, SegmentType.CENTER_VERTICAL, NO_MARGIN);
@@ -254,10 +257,10 @@
 
       snapVertical(match.with, match.edge.at, myBounds);
 
-      if (match.with.edgeType == SegmentType.LEFT) {
+      if (myTextDirection.isLeftSegment(match.with.edgeType)) {
         myCurrentLeftMatch = match;
       }
-      else if (match.with.edgeType == SegmentType.RIGHT) {
+      else if (myTextDirection.isRightSegment(match.with.edgeType)) {
         myCurrentRightMatch = match;
       }
       else {
diff --git a/android/src/com/intellij/android/designer/model/layout/relative/RelativeLayoutDecorator.java b/android/src/com/intellij/android/designer/model/layout/relative/RelativeLayoutDecorator.java
index 1273394..6be909c 100644
--- a/android/src/com/intellij/android/designer/model/layout/relative/RelativeLayoutDecorator.java
+++ b/android/src/com/intellij/android/designer/model/layout/relative/RelativeLayoutDecorator.java
@@ -15,8 +15,11 @@
  */
 package com.intellij.android.designer.model.layout.relative;
 
+import com.intellij.android.designer.AndroidDesignerUtils;
+import com.intellij.android.designer.designSurface.AndroidDesignerEditorPanel;
 import com.intellij.android.designer.designSurface.graphics.DesignerGraphics;
 import com.intellij.android.designer.model.RadViewComponent;
+import com.intellij.android.designer.model.layout.TextDirection;
 import com.intellij.designer.designSurface.DecorationLayer;
 import com.intellij.designer.designSurface.StaticDecorator;
 import com.intellij.designer.model.RadComponent;
@@ -46,10 +49,11 @@
     if (container instanceof RadViewComponent) {
       DesignerGraphics graphics = new DesignerGraphics(g, layer);
       RadViewComponent parent = (RadViewComponent)container;
-
+      AndroidDesignerEditorPanel panel = AndroidDesignerUtils.getPanel(layer.getArea());
       List<RadViewComponent> childNodes = RadViewComponent.getViewComponents(selection);
       boolean showDependents = parent.getChildren().size() > childNodes.size();
-      ConstraintPainter.paintSelectionFeedback(graphics, parent, childNodes, showDependents);
+      ConstraintPainter
+        .paintSelectionFeedback(graphics, parent, childNodes, showDependents, TextDirection.fromAndroidDesignerEditorPanel(panel));
     }
   }
 }
diff --git a/android/src/com/intellij/android/designer/model/layout/relative/ResizeHandler.java b/android/src/com/intellij/android/designer/model/layout/relative/ResizeHandler.java
index 54aebb7..16fc857 100644
--- a/android/src/com/intellij/android/designer/model/layout/relative/ResizeHandler.java
+++ b/android/src/com/intellij/android/designer/model/layout/relative/ResizeHandler.java
@@ -204,10 +204,10 @@
       assert myHorizontalEdgeType == null;
     }
 
-    if (myVerticalEdgeType == SegmentType.LEFT) {
+    if (myTextDirection.isLeftSegment(myVerticalEdgeType)) {
       vEdge = new Segment(b.x, b.y, y2(b), child, childId, myVerticalEdgeType, NO_MARGIN);
     }
-    else if (myVerticalEdgeType == SegmentType.RIGHT) {
+    else if (myTextDirection.isRightSegment(myVerticalEdgeType)) {
       vEdge = new Segment(x2(b), b.y, y2(b), child, childId, myVerticalEdgeType, NO_MARGIN);
     }
     else {
@@ -253,10 +253,10 @@
         // Snap
         snapVertical(vEdge, match.edge.at, newBounds);
 
-        if (vEdge.edgeType == SegmentType.LEFT) {
+        if (myTextDirection.isLeftSegment(vEdge.edgeType)) {
           myCurrentLeftMatch = match;
         }
-        else if (vEdge.edgeType == SegmentType.RIGHT) {
+        else if (myTextDirection.isRightSegment(vEdge.edgeType)) {
           myCurrentRightMatch = match;
         }
         else {
diff --git a/android/src/org/jetbrains/android/AndroidIdIndex.java b/android/src/org/jetbrains/android/AndroidIdIndex.java
index 193bff5..394d52e 100644
--- a/android/src/org/jetbrains/android/AndroidIdIndex.java
+++ b/android/src/org/jetbrains/android/AndroidIdIndex.java
@@ -91,6 +91,12 @@
     @Override
     public Set<String> read(@NotNull DataInput in) throws IOException {
       final int size = in.readInt();
+
+      if (size < 0 || size > 65535) { // 65K: maximum number of resources for a given type
+        // Something is very wrong (corrupt index); trigger an index rebuild.
+        throw new IOException("Corrupt Index: Size " + size);
+      }
+
       final Set<String> result = new HashSet<String>(size);
 
       for (int i = 0; i < size; i++) {
diff --git a/android/src/org/jetbrains/android/AndroidValueResourcesIndex.java b/android/src/org/jetbrains/android/AndroidValueResourcesIndex.java
index 864fec0..efcf096 100644
--- a/android/src/org/jetbrains/android/AndroidValueResourcesIndex.java
+++ b/android/src/org/jetbrains/android/AndroidValueResourcesIndex.java
@@ -200,6 +200,11 @@
     public Set<MyResourceInfo> read(@NotNull DataInput in) throws IOException {
       final int size = in.readInt();
 
+      if (size < 0 || size > 65535) {
+        // Something is very wrong; trigger an index rebuild
+        throw new IOException("Corrupt Index: Size " + size);
+      }
+
       if (size == 0) {
         return Collections.emptySet();
       }
diff --git a/android/src/org/jetbrains/android/inspections/lint/IntellijLintProject.java b/android/src/org/jetbrains/android/inspections/lint/IntellijLintProject.java
index 91d0f9e..fe8389a 100644
--- a/android/src/org/jetbrains/android/inspections/lint/IntellijLintProject.java
+++ b/android/src/org/jetbrains/android/inspections/lint/IntellijLintProject.java
@@ -20,6 +20,7 @@
 import com.android.sdklib.AndroidTargetHash;
 import com.android.sdklib.AndroidVersion;
 import com.android.tools.idea.gradle.IdeaAndroidProject;
+import com.android.tools.idea.gradle.util.GradleUtil;
 import com.android.tools.idea.model.AndroidModuleInfo;
 import com.android.tools.idea.model.ManifestInfo;
 import com.android.tools.lint.client.api.LintClient;
@@ -302,37 +303,6 @@
     // the gradle data instead
   }
 
-  protected static boolean dependsOn(@NonNull Dependencies dependencies, @NonNull String artifact) {
-    for (AndroidLibrary library : dependencies.getLibraries()) {
-      if (dependsOn(library, artifact)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  protected static boolean dependsOn(@NonNull AndroidLibrary library, @NonNull String artifact) {
-    if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
-      if (library.getJarFile().getName().startsWith("support-v4-")) {
-        return true;
-      }
-
-    } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
-      File bundle = library.getBundle();
-      if (bundle.getName().startsWith("appcompat-v7-")) {
-        return true;
-      }
-    }
-
-    for (AndroidLibrary dependency : library.getLibraryDependencies()) {
-      if (dependsOn(dependency, artifact)) {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
   protected static boolean depsDependsOn(@NonNull Project project, @NonNull String artifact) {
     // Checks project dependencies only; used when there is no model
     for (Project dependency : project.getDirectLibraries()) {
@@ -815,9 +785,7 @@
       if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
         if (mSupportLib == null) {
           if (myFacet.isGradleProject() && myFacet.getIdeaAndroidProject() != null) {
-            IdeaAndroidProject gradleProject = myFacet.getIdeaAndroidProject();
-            Dependencies dependencies = gradleProject.getSelectedVariant().getMainArtifact().getDependencies();
-            mSupportLib = dependsOn(dependencies, artifact);
+            mSupportLib = GradleUtil.dependsOn(myFacet.getIdeaAndroidProject(), artifact);
           } else {
             mSupportLib = depsDependsOn(this, artifact);
           }
@@ -826,15 +794,19 @@
       } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
         if (mAppCompat == null) {
           if (myFacet.isGradleProject() && myFacet.getIdeaAndroidProject() != null) {
-            IdeaAndroidProject gradleProject = myFacet.getIdeaAndroidProject();
-            Dependencies dependencies = gradleProject.getSelectedVariant().getMainArtifact().getDependencies();
-            mAppCompat = dependsOn(dependencies, artifact);
+            mAppCompat = GradleUtil.dependsOn(myFacet.getIdeaAndroidProject(), artifact);
           } else {
             mAppCompat = depsDependsOn(this, artifact);
           }
         }
         return mAppCompat;
       } else {
+        // Some other (not yet directly cached result)
+        if (myFacet.isGradleProject() && myFacet.getIdeaAndroidProject() != null
+           && GradleUtil.dependsOn(myFacet.getIdeaAndroidProject(), artifact)) {
+          return true;
+        }
+
         return super.dependsOn(artifact);
       }
     }
@@ -955,15 +927,20 @@
     public Boolean dependsOn(@NonNull String artifact) {
       if (SUPPORT_LIB_ARTIFACT.equals(artifact)) {
         if (mSupportLib == null) {
-          mSupportLib = dependsOn(myLibrary, artifact);
+          mSupportLib = GradleUtil.dependsOn(myLibrary, artifact, true);
         }
         return mSupportLib;
       } else if (APPCOMPAT_LIB_ARTIFACT.equals(artifact)) {
         if (mAppCompat == null) {
-          mAppCompat = dependsOn(myLibrary, artifact);
+          mAppCompat = GradleUtil.dependsOn(myLibrary, artifact, true);
         }
         return mAppCompat;
       } else {
+        // Some other (not yet directly cached result)
+        if (GradleUtil.dependsOn(myLibrary, artifact, true)) {
+          return true;
+        }
+
         return super.dependsOn(artifact);
       }
     }
diff --git a/android/src/org/jetbrains/android/sdk/AndroidSdkUtils.java b/android/src/org/jetbrains/android/sdk/AndroidSdkUtils.java
index 564277c..0a45b0e 100644
--- a/android/src/org/jetbrains/android/sdk/AndroidSdkUtils.java
+++ b/android/src/org/jetbrains/android/sdk/AndroidSdkUtils.java
@@ -60,6 +60,7 @@
 import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.*;
 import com.intellij.util.ObjectUtils;
@@ -91,6 +92,11 @@
   public static final String SDK_NAME_PREFIX = "Android ";
   public static final String DEFAULT_JDK_NAME = "JDK";
 
+  // TODO: Update these to a stable link
+  private static final String MAC_SDK_URL = "http://dl.google.com/android/android-sdk_r22.6.2-macosx.zip";
+  private static final String LINUX_SDK_URL = "http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz";
+  private static final String WINDOWS_SDK_URL = "http://dl.google.com/android/android-sdk_r22.6.2-windows.zip";
+
   private static AndroidSdkData ourSdkData;
 
   private AndroidSdkUtils() {
@@ -371,6 +377,7 @@
     sdkModificator.setSdkAdditionalData(data);
 
     if (addRoots) {
+      sdkModificator.removeAllRoots();
       for (OrderRoot orderRoot : getLibraryRootsForTarget(target, androidSdk.getHomePath(), true)) {
         sdkModificator.addRoot(orderRoot.getFile(), orderRoot.getType());
       }
@@ -472,12 +479,8 @@
         continue;
       }
       AndroidSdkAdditionalData data = (AndroidSdkAdditionalData)originalData;
-      AndroidPlatform androidPlatform = data.getAndroidPlatform();
-      if (androidPlatform == null) {
-        continue;
-      }
       String sdkHomePath = sdk.getHomePath();
-      if (!foundSdkHomePaths.contains(sdkHomePath) && targetHash.equals(androidPlatform.getTarget().hashString())) {
+      if (!foundSdkHomePaths.contains(sdkHomePath) && targetHash.equals(data.getBuildTargetHashString())) {
         if (VersionCheck.isCompatibleVersion(sdkHomePath)) {
           return sdk;
         }
@@ -489,7 +492,6 @@
     }
 
     if (!notCompatibleSdks.isEmpty()) {
-      // We got here because we have SDKs but none of them have a compatible Tools version. Pick the first one.
       return notCompatibleSdks.get(0);
     }
 
@@ -901,6 +903,22 @@
     return bridge;
   }
 
+  /**
+   * @return URL for the Android SDK download for the current platform. <code>null</code> means the platform was not recognized
+   */
+  @Nullable
+  public static String getSdkDownloadUrl() {
+    if (SystemInfo.isLinux) {
+      return LINUX_SDK_URL;
+    } else if (SystemInfo.isWindows) {
+      return WINDOWS_SDK_URL;
+    } else if (SystemInfo.isMac) {
+      return MAC_SDK_URL;
+    } else {
+      return null;
+    }
+  }
+
   private static class MyMonitorBridgeConnectionTask extends Task.Modal {
     private final Future<AndroidDebugBridge> myFuture;
     private boolean myCancelled; // set/read only on EDT
diff --git a/android/testSrc/com/android/tools/idea/gradle/eclipse/GradleImportTest.java b/android/testSrc/com/android/tools/idea/gradle/eclipse/GradleImportTest.java
index 524f650..8900c55 100644
--- a/android/testSrc/com/android/tools/idea/gradle/eclipse/GradleImportTest.java
+++ b/android/testSrc/com/android/tools/idea/gradle/eclipse/GradleImportTest.java
@@ -17,6 +17,8 @@
 import com.google.common.io.Files;
 import org.jetbrains.android.AndroidTestCase;
 
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
 import java.io.StringWriter;
@@ -262,6 +264,10 @@
     new File(unhandled.getParentFile(), "unhandledDir4").mkdirs();
     new File(projectDir, "lint.xml").createNewFile();
 
+    // Make sure we handle less common file extensions: see issue 78459
+    createIcon(projectDir, "other_icon.PNG");
+    createLayout(projectDir, "other_layout.XML");
+
     // Project being imported
     assertEquals(""
                  + ".classpath\n"
@@ -280,6 +286,10 @@
                  + "res\n"
                  + "  drawable\n"
                  + "    ic_launcher.xml\n"
+                 + "  drawable-hdpi\n"
+                 + "    other_icon.PNG\n"
+                 + "  layout\n"
+                 + "    other_layout.XML\n"
                  + "  values\n"
                  + "    strings.xml\n"
                  + "src\n"
@@ -310,6 +320,8 @@
                                  + "* lint.xml => app/lint.xml\n"
                                  + "* res/ => app/src/main/res/\n"
                                  + "* src/ => app/src/main/java/\n"
+                                 + "* other_icon.PNG => other_icon.png\n"
+                                 + "* other_layout.XML => other_layout.xml\n"
                                  + MSG_FOOTER,
                                  true /* checkBuild */);
 
@@ -328,6 +340,10 @@
                  + "      res\n"
                  + "        drawable\n"
                  + "          ic_launcher.xml\n"
+                 + "        drawable-hdpi\n"
+                 + "          other_icon.png\n"
+                 + "        layout\n"
+                 + "          other_layout.xml\n"
                  + "        values\n"
                  + "          strings.xml\n"
                  + "build.gradle\n"
@@ -3854,9 +3870,9 @@
     sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
               + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
               + "    package=\"").append(packageName).append("\"\n"
-                                                             + "    android:versionCode=\"1\"\n"
-                                                             + "    android:versionName=\"1.0\" >\n"
-                                                             + "\n");
+              + "    android:versionCode=\"1\"\n"
+              + "    android:versionName=\"1.0\" >\n"
+              + "\n");
     if (minSdkVersion != -1 || targetSdkVersion != -1) {
       sb.append("    <uses-sdk\n");
       if (minSdkVersion >= 1) {
@@ -3956,18 +3972,39 @@
                 + "</resources>", strings, UTF_8);
   }
 
-  @SuppressWarnings("ResultOfMethodCallIgnored")
   private static void createDefaultIcon(File dir) throws IOException {
-    File strings = new File(dir, "res" + separator + "drawable" + separator +
-                                 "ic_launcher.xml");
+    createIcon(dir, "ic_launcher.xml");
+  }
+
+  @SuppressWarnings("ResultOfMethodCallIgnored")
+  private static void createIcon(File dir, String name) throws IOException {
+    boolean isPng = name.toLowerCase(Locale.US).endsWith(DOT_PNG);
+    String folder = isPng ? "drawable-hdpi" : "drawable";
+    File icon = new File(dir, "res" + separator + folder + separator + name);
+    icon.getParentFile().mkdirs();
+    if (isPng) {
+      //noinspection UndesirableClassUsage
+      BufferedImage image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
+      ImageIO.write(image, "PNG", icon);
+    } else {
+      assertTrue("Unsupported file extension for " + name, name.toLowerCase(Locale.US).endsWith(DOT_XML));
+      Files.write("" +
+                  "<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n" +
+                  "    <solid android:color=\"#00000000\"/>\n" +
+                  "    <stroke android:width=\"1dp\" color=\"#ff000000\"/>\n" +
+                  "    <padding android:left=\"1dp\" android:top=\"1dp\"\n" +
+                  "        android:right=\"1dp\" android:bottom=\"1dp\" />\n" +
+                  "</shape>", icon, UTF_8);
+    }
+  }
+
+  @SuppressWarnings("ResultOfMethodCallIgnored")
+  private static void createLayout(File dir, String name) throws IOException {
+    File strings = new File(dir, "res" + separator + "layout" + separator + name);
     strings.getParentFile().mkdirs();
     Files.write(""
-                + "<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
-                + "    <solid android:color=\"#00000000\"/>\n"
-                + "    <stroke android:width=\"1dp\" color=\"#ff000000\"/>\n"
-                + "    <padding android:left=\"1dp\" android:top=\"1dp\"\n"
-                + "        android:right=\"1dp\" android:bottom=\"1dp\" />\n"
-                + "</shape>", strings, UTF_8);
+                + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<merge/>\n", strings, UTF_8);
   }
 
   private static void deleteDir(File root) {
diff --git a/android/testSrc/com/android/tools/idea/gradle/project/SdkSyncTest.java b/android/testSrc/com/android/tools/idea/gradle/project/SdkSyncTest.java
index 0bcce2a..bea24af 100644
--- a/android/testSrc/com/android/tools/idea/gradle/project/SdkSyncTest.java
+++ b/android/testSrc/com/android/tools/idea/gradle/project/SdkSyncTest.java
@@ -42,7 +42,7 @@
   }
 
   public void testSyncIdeAndProjectAndroidHomesWithIdeSdkAndNoProjectSdk() throws Exception {
-    DefaultSdks.setDefaultAndroidHome(myAndroidSdkPath);
+    DefaultSdks.setDefaultAndroidHome(myAndroidSdkPath, null);
 
     SdkSync.syncIdeAndProjectAndroidHomes(myLocalProperties);
 
@@ -50,7 +50,7 @@
   }
 
   public void testSyncIdeAndProjectAndroidHomesWithIdeSdkAndInvalidProjectSdk() throws Exception {
-    DefaultSdks.setDefaultAndroidHome(myAndroidSdkPath);
+    DefaultSdks.setDefaultAndroidHome(myAndroidSdkPath, null);
 
     myLocalProperties.setAndroidSdkPath(new File("randomPath"));
     myLocalProperties.save();
@@ -95,6 +95,7 @@
       SdkSync.syncIdeAndProjectAndroidHomes(myLocalProperties, task);
       fail("Expecting ExternalSystemException");
     } catch (ExternalSystemException e) {
+      // expected
     }
 
     assertNull(DefaultSdks.getDefaultAndroidHome());
diff --git a/android/testSrc/com/android/tools/idea/rendering/AarResourceClassGeneratorTest.java b/android/testSrc/com/android/tools/idea/rendering/AarResourceClassGeneratorTest.java
index b60fd5b..03dee6d 100644
--- a/android/testSrc/com/android/tools/idea/rendering/AarResourceClassGeneratorTest.java
+++ b/android/testSrc/com/android/tools/idea/rendering/AarResourceClassGeneratorTest.java
@@ -40,7 +40,7 @@
       "values/styles.xml", "" +
                            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                            "<resources>\n" +
-                           "    <style name=\"MyTheme\" parent=\"android:Theme.Light\">\n" +
+                           "    <style name=\"MyTheme.Dark\" parent=\"android:Theme.Light\">\n" +
                            "        <item name=\"android:textColor\">#999999</item>\n" +
                            "        <item name=\"foo\">?android:colorForeground</item>\n" +
                            "    </style>\n" +
@@ -155,6 +155,15 @@
     Object gravityValue = field1.get(null);
     Object layoutColumnSpanValue = clz.getField("layout_columnSpan").get(null);
 
+    // Test style class
+    name = "my.test.pkg.R$style";
+    clz = generateClass(generator, name);
+    assertNotNull(clz);
+    r = clz.newInstance();
+    assertEquals(name, clz.getName());
+    assertTrue(Modifier.isPublic(clz.getModifiers()));
+    assertTrue(Modifier.isFinal(clz.getModifiers()));
+    assertFalse(Modifier.isInterface(clz.getModifiers()));
 
     // Test styleable class!
     name = "my.test.pkg.R$styleable";
diff --git a/android/testSrc/com/android/tools/idea/sdk/DefaultSdksTest.java b/android/testSrc/com/android/tools/idea/sdk/DefaultSdksTest.java
index 10199db..a833cc2 100644
--- a/android/testSrc/com/android/tools/idea/sdk/DefaultSdksTest.java
+++ b/android/testSrc/com/android/tools/idea/sdk/DefaultSdksTest.java
@@ -91,7 +91,7 @@
     localProperties.setAndroidSdkPath("");
     localProperties.save();
 
-    List<Sdk> sdks = DefaultSdks.setDefaultAndroidHome(myAndroidSdkPath);
+    List<Sdk> sdks = DefaultSdks.setDefaultAndroidHome(myAndroidSdkPath, null);
     assertOneSdkPerAvailableTarget(sdks);
 
     localProperties = new LocalProperties(myProject);
diff --git a/android/testSrc/com/android/tools/idea/sdk/SdkLoggerIntegrationTest.java b/android/testSrc/com/android/tools/idea/sdk/SdkLoggerIntegrationTest.java
new file mode 100644
index 0000000..156aea1
--- /dev/null
+++ b/android/testSrc/com/android/tools/idea/sdk/SdkLoggerIntegrationTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 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.tools.idea.sdk;
+
+import com.intellij.testFramework.fixtures.IdeaProjectTestFixture;
+import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory;
+import com.intellij.testFramework.fixtures.JavaTestFixtureFactory;
+import com.intellij.testFramework.fixtures.TestFixtureBuilder;
+import org.jetbrains.android.AndroidTestBase;
+import org.mockito.Mockito;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public final class SdkLoggerIntegrationTest extends AndroidTestBase {
+  private static void verifyParsing(String message, String title, String description, int progress) {
+    SdkLoggerIntegration logger = process(message);
+    verify(logger).setTitle(title);
+    verify(logger).setDescription(description);
+    verify(logger).setProgress(progress);
+    verify(logger, never()).lineAdded("");
+  }
+
+  private static SdkLoggerIntegration process(String message) {
+    SdkLoggerIntegration mock = Mockito.spy(new DummySdkLoggerIntegration());
+    mock.info("%s\n", message);
+    return mock;
+  }
+
+  private static void verifyIgnored(String message) {
+    SdkLoggerIntegration mock = process(message);
+    verify(mock, never()).setTitle("");
+    verify(mock, never()).setDescription("");
+    verify(mock, never()).lineAdded("");
+    verify(mock, never()).setProgress(0);
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    final TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder =
+      IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getName());
+    myFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(projectBuilder.getFixture());
+    myFixture.setUp();
+    myFixture.setTestDataPath(getTestDataPath());
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    myFixture.tearDown();
+    super.tearDown();
+  }
+
+  public void testIgnoredStrings() {
+    verifyIgnored("Refresh Sources:");
+    verifyIgnored("  Fetching http://dl-ssl.google.com/android/repository/addons_list-2.xml");
+    verifyIgnored("  Validate XML");
+    verifyIgnored("  Parse XML");
+    verifyIgnored("  Fetched Add-ons List successfully");
+    verifyIgnored("-------------------------------");
+    verifyIgnored("License id: android-sdk-license-cafebabe");
+    verifyIgnored("Used by: \n" +
+                  " - Android SDK Platform-tools, revision 21\n" +
+                  "  - SDK Platform Android 5.0, API 21, revision 1");
+  }
+
+  public void testDownloadProgress() {
+    SdkLoggerIntegration mock = process("  Downloading SDK Platform Android 5.0, API 21, revision 1");
+    verify(mock).setTitle("Downloading SDK Platform Android 5.0, API 21, revision 1");
+    verify(mock, never()).setDescription("");
+    verify(mock, never()).setProgress(0);
+    verify(mock, never()).lineAdded("");
+
+    verifyParsing("     (14%, 4674 KiB/s, 12 seconds left)", "", "14%, 4674 KiB/s, 12 seconds left", 14);
+  }
+
+  public void testUnpackingProgress() {
+    verifyParsing("  Unzipping SDK Platform Android 5.0, API 21, revision 1 (1%)",
+                  "Unzipping SDK Platform Android 5.0, API 21, revision 1",
+                  "1%", 1);
+    verifyParsing("  Unzipping SDK Platform Android 5.0, API 21, revision 1 (54%)",
+                  "Unzipping SDK Platform Android 5.0, API 21, revision 1", "54%", 54);
+  }
+
+  /**
+   * This concrete class is needed as Mockito cannot spy on abstract classes.
+   */
+  private static class DummySdkLoggerIntegration extends SdkLoggerIntegration {
+    @Override
+    protected void setProgress(int progress) {
+      // Needed for mock object
+    }
+
+    @Override
+    protected void setDescription(String description) {
+      // Needed for mock object
+    }
+
+    @Override
+    protected void setTitle(String title) {
+      // Needed for mock object
+    }
+
+    @Override
+    protected void lineAdded(String string) {
+      // Needed for mock object
+    }
+  }
+}
\ No newline at end of file
diff --git a/android/testSrc/com/android/tools/idea/templates/FmHasDependencyMethodTest.java b/android/testSrc/com/android/tools/idea/templates/FmHasDependencyMethodTest.java
new file mode 100644
index 0000000..111a0ca
--- /dev/null
+++ b/android/testSrc/com/android/tools/idea/templates/FmHasDependencyMethodTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 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.tools.idea.templates;
+
+import com.google.common.collect.Maps;
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateBooleanModel;
+import freemarker.template.TemplateModelException;
+import junit.framework.TestCase;
+
+import java.util.*;
+
+@SuppressWarnings("javadoc")
+public class FmHasDependencyMethodTest extends TestCase {
+
+  private static Map<String, Object> createMap(Object... keysAndValues) {
+    HashMap<String, Object> map = Maps.newHashMap();
+    for (int i = 0; i < keysAndValues.length; i+= 2) {
+      String key = (String)keysAndValues[i];
+      Object value = keysAndValues[i + 1];
+      map.put(key, value);
+    }
+
+    return map;
+  }
+
+  @SuppressWarnings("rawtypes")
+  private static void check(boolean expected, String s, Map<String, Object> paramMap) throws TemplateModelException {
+    FmHasDependencyMethod method = new FmHasDependencyMethod(paramMap);
+    List list = Collections.singletonList(new SimpleScalar(s));
+    assertEquals(expected, ((TemplateBooleanModel)method.exec(list)).getAsBoolean());
+  }
+
+  public void testEmpty() throws Exception {
+    check(false, "com.android.support:support-v4", createMap());
+  }
+
+  public void testProvidedDependencies() throws Exception {
+    check(false, "com.android.support:support-v4", createMap(
+      TemplateMetadata.ATTR_DEPENDENCIES_LIST,
+      Arrays.asList("com.android.support:appcompat-v7:21.0.0")));
+
+    check(true, "com.android.support:support-v4", createMap(
+      TemplateMetadata.ATTR_DEPENDENCIES_LIST,
+      Arrays.asList("com.android.support:appcompat-v7:21.0.0", "com.android.support:support-v4:v21.+")));
+  }
+
+  public void testAppCompatBasedOnMinAndCompileApis() throws Exception {
+    // Too old: appcompat requires API 8
+    check(false, "com.android.support:appcompat-v7", createMap(TemplateMetadata.ATTR_BUILD_API, 21,
+                                                               TemplateMetadata.ATTR_MIN_API_LEVEL, 4));
+    // Too high: not needed with minSdkVersion 21
+    check(false, "com.android.support:appcompat-v7", createMap(TemplateMetadata.ATTR_BUILD_API, 21,
+                                                               TemplateMetadata.ATTR_MIN_API_LEVEL, 21));
+
+    // Not needed when minSdkVersion >= 14 and compileSdkVersion < 21
+    check(false, "com.android.support:appcompat-v7", createMap(TemplateMetadata.ATTR_BUILD_API, 18,
+                                                               TemplateMetadata.ATTR_MIN_API_LEVEL, 15));
+
+    // Use it with minSdkVersion 21
+    check(true, "com.android.support:appcompat-v7", createMap(TemplateMetadata.ATTR_BUILD_API, 21,
+                                                               TemplateMetadata.ATTR_MIN_API_LEVEL, 15));
+  }
+}
diff --git a/android/testSrc/com/android/tools/idea/welcome/InstallComponentsPathTest.java b/android/testSrc/com/android/tools/idea/welcome/InstallComponentsPathTest.java
new file mode 100644
index 0000000..370d70f
--- /dev/null
+++ b/android/testSrc/com/android/tools/idea/welcome/InstallComponentsPathTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2014 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.tools.idea.welcome;
+
+import com.android.SdkConstants;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.updater.SdkUpdaterNoWindow;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.descriptors.PkgDesc;
+import com.android.sdklib.repository.descriptors.PkgType;
+import com.android.sdklib.repository.local.LocalPkgInfo;
+import com.android.tools.idea.sdk.SdkLoggerIntegration;
+import com.android.utils.ILogger;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.collect.*;
+import com.google.common.io.Files;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.testFramework.fixtures.IdeaProjectTestFixture;
+import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory;
+import com.intellij.testFramework.fixtures.JavaTestFixtureFactory;
+import com.intellij.testFramework.fixtures.TestFixtureBuilder;
+import org.jetbrains.android.AndroidTestBase;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Assume;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Predicates.in;
+import static com.google.common.base.Predicates.not;
+
+/**
+ * Test installing Android SDK logic from the first run wizard.
+ * <p/>
+ * This test is not meant to be ran on build farm, only on the local system.
+ */
+public class InstallComponentsPathTest extends AndroidTestBase {
+  private File tempDir;
+
+  @Override
+  public void setUp() throws Exception {
+    Assume.assumeTrue("Android SDK install test is disabled", Boolean.getBoolean("test.androidsdk.install"));
+    super.setUp();
+    final TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder =
+      IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getName());
+    myFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(projectBuilder.getFixture());
+    myFixture.setUp();
+    myFixture.setTestDataPath(getTestDataPath());
+    tempDir = Files.createTempDir();
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    FileUtil.delete(tempDir);
+    myFixture.tearDown();
+    super.tearDown();
+  }
+
+  public void testDownloadSeed() throws WizardException {
+    File destination = new File(tempDir, "android-sdk-seed-install");
+    boolean result = InstallComponentsPath.downloadAndUnzipSdkSeed(new InstallContext(tempDir), destination, 1);
+    assertTrue("Operation was canceled", result);
+    assertTrue("Destination should exist", destination.isDirectory());
+
+    File[] children = destination.listFiles();
+    assertTrue("Destination is empty", children != null && children.length > 0);
+    Set<String> expectedDirs = Sets.newHashSet(SdkConstants.FD_ADDONS, SdkConstants.FD_PLATFORMS, SdkConstants.FD_TOOLS);
+    for (File child : children) {
+      expectedDirs.remove(child.getName());
+    }
+    assertEquals("Missing folders: " + Joiner.on(", ").join(expectedDirs), 0, expectedDirs.size());
+  }
+
+  public void testNewInstall() throws IOException, WizardException {
+    File destination = new File(tempDir, getName());
+    InstallContext context = new InstallContext(tempDir);
+    InstallComponentsPath.downloadAndUnzipSdkSeed(context, destination, 1);
+    ILogger log = new LoggerForTest();
+    SdkManager manager = SdkManager.createManager(destination.getAbsolutePath(), log);
+    if (manager == null) {
+      throw new IOException("SDK not found");
+    }
+
+    PkgDesc.Builder[] packages = AndroidSdk.getPackages();
+    Set<String> toInstall = Sets.newHashSet();
+    for (PkgDesc.Builder sdkPackage : packages) {
+      if (sdkPackage != null) {
+        toInstall.add(sdkPackage.create().getInstallId());
+      }
+    }
+
+    InstallComponentsPath.setupSdkComponents(context, destination, Collections.singleton(new AndroidSdk()), 1);
+    manager.reloadSdk(log);
+    LocalPkgInfo[] installedPkgs = manager.getLocalSdk().getPkgsInfos(EnumSet.allOf(PkgType.class));
+
+    assertEquals(9, toInstall.size());
+    final Map<String, LocalPkgInfo> installed = Maps.newHashMap();
+    for (LocalPkgInfo info : installedPkgs) {
+      installed.put(info.getDesc().getInstallId(), info);
+    }
+
+    System.out.println("Packages list: \n\t" + Joiner.on("\n\t").join(toInstall));
+
+    // TODO: This package is not available for install
+    toInstall.remove("sample-21");
+    // TODO: Why is this always installed?
+    installed.remove("extra-intel-hardware_accelerated_execution_manager");
+
+    Set<String> all = ImmutableSet.copyOf(Iterables.concat(toInstall, installed.keySet()));
+
+    Set<String> notInstalled = ImmutableSet.copyOf(Iterables.filter(all, not(in(installed.keySet()))));
+    Set<String> shouldntBeenInstalled = ImmutableSet.copyOf(Iterables.filter(all, not(in(toInstall))));
+
+    assertEquals("Not installed: " + Joiner.on(", ").join(notInstalled), 0, notInstalled.size());
+    assertEquals("Were installed: " + Joiner.on(", ").join(Iterables.transform(shouldntBeenInstalled, new Function<String, String>() {
+      @Override
+      public String apply(String input) {
+        return String.format("%s (%s)", input, installed.get(input).getShortDescription());
+      }
+    })), 0, shouldntBeenInstalled.size());
+  }
+
+  private static class LoggerForTest extends SdkLoggerIntegration {
+    private String myTitle = null;
+
+    @Override
+    protected void setProgress(int progress) {
+      // No need.
+    }
+
+    @Override
+    protected void setDescription(String description) {
+      // No spamming
+    }
+
+    @Override
+    protected void setTitle(String title) {
+      if (!StringUtil.isEmptyOrSpaces(title) && !Objects.equal(title, myTitle)) {
+        System.out.println(title);
+        myTitle = title;
+      }
+    }
+
+    @Override
+    protected void lineAdded(String string) {
+      System.out.println(string);
+    }
+  }
+}
\ No newline at end of file
diff --git a/android/testSrc/org/jetbrains/android/sdk/AndroidSdkDataTest.java b/android/testSrc/org/jetbrains/android/sdk/AndroidSdkDataTest.java
index 40cb496..65d30c6 100644
--- a/android/testSrc/org/jetbrains/android/sdk/AndroidSdkDataTest.java
+++ b/android/testSrc/org/jetbrains/android/sdk/AndroidSdkDataTest.java
@@ -56,7 +56,7 @@
     ApplicationManager.getApplication().runWriteAction(new Runnable() {
       @Override
       public void run() {
-        DefaultSdks.setDefaultAndroidHome(sdkDir);
+        DefaultSdks.setDefaultAndroidHome(sdkDir, null);
         ProjectRootManager.getInstance(getProject()).setProjectSdk(DefaultSdks.getEligibleAndroidSdks().get(0));
       }
     });