Merge "rcp build: don't copy plugin/bin folder"
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java
index 0dde05f..594912b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java
@@ -21,6 +21,7 @@
 import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler.Solution;
 import com.android.ide.eclipse.adt.Messages;
 import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.repository.packages.FullRevision;
 import com.android.sdklib.repository.PkgProps;
 
 import org.osgi.framework.Constants;
@@ -48,7 +49,7 @@
     /**
      * The minimum version of the SDK Tools that this version of ADT requires.
      */
-    private final static int MIN_TOOLS_REV = 17;
+    private final static FullRevision MIN_TOOLS_REV = new FullRevision(17);
 
     /**
      * Pattern to get the minimum plugin version supported by the SDK. This is read from
@@ -57,7 +58,7 @@
     private final static Pattern sPluginVersionPattern = Pattern.compile(
             "^plugin.version=(\\d+)\\.(\\d+)\\.(\\d+).*$"); //$NON-NLS-1$
     private final static Pattern sSourcePropPattern = Pattern.compile(
-            "^" + PkgProps.PKG_MAJOR_REV + "=(\\d+).*$"); //$NON-NLS-1$
+            "^" + PkgProps.PKG_REVISION + "=(.*)$"); //$NON-NLS-1$
 
     /**
      * Checks the plugin and the SDK have compatible versions.
@@ -136,7 +137,7 @@
 
         // now check whether the tools are new enough.
         String osTools = osSdkPath + SdkConstants.OS_SDK_TOOLS_FOLDER;
-        int toolsRevision = Integer.MAX_VALUE;
+        FullRevision toolsRevision = new FullRevision(Integer.MAX_VALUE);
         try {
             reader = new FileReader(osTools + SdkConstants.FN_SOURCE_PROP);
             BufferedReader bReader = new BufferedReader(reader);
@@ -144,7 +145,9 @@
             while ((line = bReader.readLine()) != null) {
                 Matcher m = sSourcePropPattern.matcher(line);
                 if (m.matches()) {
-                    toolsRevision = Integer.parseInt(m.group(1));
+                    try {
+                        toolsRevision = FullRevision.parseRevision(m.group(1));
+                    } catch (NumberFormatException ignore) {}
                     break;
                 }
             }
@@ -163,7 +166,7 @@
             }
         }
 
-        if (toolsRevision < MIN_TOOLS_REV) {
+        if (toolsRevision.compareTo(MIN_TOOLS_REV) < 0) {
             // this is a warning only as we need to parse the SDK to allow updating
             // of the tools!
             return errorHandler.handleWarning(
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
index 58e156e..3c976b0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
@@ -246,6 +246,18 @@
         mIgnoreXmlUpdate = ignore;
     }
 
+    /**
+     * Returns whether XML model events are ignored or not. This is the case
+     * when we are deliberately modifying the document in a way which does not
+     * change the semantics (such as formatting), or when we have already
+     * directly updated the model ourselves.
+     *
+     * @return true if XML events should be ignored
+     */
+    public boolean getIgnoreXmlUpdate() {
+        return mIgnoreXmlUpdate;
+    }
+
     // ---- Base Class Overrides, Interfaces Implemented ----
 
     @Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
index 325e284..4bc7641 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
@@ -487,6 +487,9 @@
                     public void selectionChanged(SelectionChangedEvent event) {
                         ISelection selection = event.getSelection();
                         getEditor().getSite().getSelectionProvider().setSelection(selection);
+                        if (getEditor().getIgnoreXmlUpdate()) {
+                            return;
+                        }
                         SelectionManager manager =
                                 mGraphicalEditor.getCanvasControl().getSelectionManager();
                         manager.setSelection(selection);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
index 2da7678..3fda20d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
@@ -21,6 +21,8 @@
 import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
 import static org.eclipse.wst.xml.core.internal.provisional.contenttype.ContentTypeIdForXML.ContentTypeID_XML;
 
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
 import com.android.util.Pair;
@@ -70,7 +72,8 @@
      * @param node2 the second node to test
      * @return the nearest common parent of the two given nodes
      */
-    public static Node getCommonAncestor(Node node1, Node node2) {
+    @Nullable
+    public static Node getCommonAncestor(@NonNull Node node1, @NonNull Node node2) {
         while (node2 != null) {
             Node current = node1;
             while (current != null && current != node2) {
@@ -92,13 +95,14 @@
      * @param node the node to search from
      * @return all elements in the subtree formed by the node parameter
      */
-    public static List<Element> getAllElements(Node node) {
+    @NonNull
+    public static List<Element> getAllElements(@NonNull Node node) {
         List<Element> elements = new ArrayList<Element>(64);
         addElements(node, elements);
         return elements;
     }
 
-    private static void addElements(Node node, List<Element> elements) {
+    private static void addElements(@NonNull Node node, @NonNull List<Element> elements) {
         if (node instanceof Element) {
             elements.add((Element) node);
         }
@@ -116,7 +120,7 @@
      * @param node the node to test
      * @return the depth in the document
      */
-    public static int getDepth(Node node) {
+    public static int getDepth(@NonNull Node node) {
         int depth = -1;
         while (node != null) {
             depth++;
@@ -132,7 +136,7 @@
      * @param node the node to test for element children
      * @return true if the node has one or more element children
      */
-    public static boolean hasElementChildren(Node node) {
+    public static boolean hasElementChildren(@NonNull Node node) {
         NodeList children = node.getChildNodes();
         for (int i = 0, n = children.getLength(); i < n; i++) {
             if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
@@ -151,7 +155,8 @@
      * @param offset The offset to look up the node for
      * @return The node containing the offset, or null
      */
-    public static Node getNode(IDocument document, int offset) {
+    @Nullable
+    public static Node getNode(@NonNull IDocument document, int offset) {
         Node node = null;
         IModelManager modelManager = StructuredModelManager.getModelManager();
         if (modelManager == null) {
@@ -204,7 +209,8 @@
      *         return the parent. Note that the method can also return null if no
      *         document or model could be obtained or if the offset is invalid.
      */
-    public static Pair<Node, Node> getNodeContext(IDocument document, int offset) {
+    @Nullable
+    public static Pair<Node, Node> getNodeContext(@NonNull IDocument document, int offset) {
         Node node = null;
         IModelManager modelManager = StructuredModelManager.getModelManager();
         if (modelManager == null) {
@@ -283,7 +289,8 @@
      * @return the node which surrounds the given offset, or the node adjacent to the offset
      *    where the side depends on the forward parameter
      */
-    public static Node getNode(IDocument document, int offset, boolean forward) {
+    @Nullable
+    public static Node getNode(@NonNull IDocument document, int offset, boolean forward) {
         Node node = getNode(document, offset);
 
         if (node instanceof IndexedRegion) {
@@ -318,8 +325,9 @@
      * @param endOffset the ending offset of the range
      * @return a pair of begin+end elements, or null
      */
-    public static Pair<Element, Element> getElementRange(IDocument document, int beginOffset,
-            int endOffset) {
+    @Nullable
+    public static Pair<Element, Element> getElementRange(@NonNull IDocument document,
+            int beginOffset, int endOffset) {
         Element beginElement = null;
         Element endElement = null;
         Node beginNode = getNode(document, beginOffset, true);
@@ -383,7 +391,8 @@
      * @param node the starting node
      * @return the next sibling element, or null
      */
-    public static Element getNextElement(Node node) {
+    @Nullable
+    public static Element getNextElement(@NonNull Node node) {
         while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
             node = node.getNextSibling();
         }
@@ -397,7 +406,8 @@
      * @param node the starting node
      * @return the previous sibling element, or null
      */
-    public static Element getPreviousElement(Node node) {
+    @Nullable
+    public static Element getPreviousElement(@NonNull Node node) {
         while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
             node = node.getPreviousSibling();
         }
@@ -411,7 +421,8 @@
      * @param node the starting node
      * @return the closest parent element, or null
      */
-    public static Element getParentElement(Node node) {
+    @Nullable
+    public static Element getParentElement(@NonNull Node node) {
         while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
             node = node.getParentNode();
         }
@@ -426,7 +437,8 @@
      * @param attrValue the value to be escaped
      * @return the escaped value
      */
-    public static String toXmlAttributeValue(String attrValue) {
+    @NonNull
+    public static String toXmlAttributeValue(@NonNull String attrValue) {
         for (int i = 0, n = attrValue.length(); i < n; i++) {
             char c = attrValue.charAt(i);
             if (c == '"' || c == '\'' || c == '<' || c == '&') {
@@ -446,7 +458,8 @@
      * @param sb the string builder
      * @param attrValue the attribute value to be appended and escaped
      */
-    public static void appendXmlAttributeValue(StringBuilder sb, String attrValue) {
+    public static void appendXmlAttributeValue(@NonNull StringBuilder sb,
+            @NonNull String attrValue) {
         int n = attrValue.length();
         // &, ", ' and < are illegal in attributes; see http://www.w3.org/TR/REC-xml/#NT-AttValue
         // (' legal in a " string and " is legal in a ' string but here we'll stay on the safe
@@ -474,7 +487,7 @@
      * @param sb the string builder
      * @param textValue the text value to be appended and escaped
      */
-    public static void appendXmlTextValue(StringBuilder sb, String textValue) {
+    public static void appendXmlTextValue(@NonNull StringBuilder sb, @NonNull String textValue) {
         for (int i = 0, n = textValue.length(); i < n; i++) {
             char c = textValue.charAt(i);
             if (c == '<') {
@@ -488,7 +501,7 @@
     }
 
     /** Utility used by {@link #getFreeWidgetId(Element)} */
-    private static void addLowercaseIds(Element root, Set<String> seen) {
+    private static void addLowercaseIds(@NonNull Element root, @NonNull Set<String> seen) {
         if (root.hasAttributeNS(ANDROID_URI, ATTR_ID)) {
             String id = root.getAttributeNS(ANDROID_URI, ATTR_ID);
             if (id.startsWith(NEW_ID_PREFIX)) {
@@ -514,7 +527,10 @@
      * @return a unique id, never null, which does not include the {@code @id/} prefix
      * @see DescriptorsUtils#getFreeWidgetId
      */
-    public static String getFreeWidgetId(Element element, Set<String> reserved, String prefix) {
+    public static String getFreeWidgetId(
+            @NonNull Element element,
+            @Nullable Set<String> reserved,
+            @Nullable String prefix) {
         Set<String> ids = new HashSet<String>();
         if (reserved != null) {
             for (String id : reserved) {
@@ -545,7 +561,8 @@
      * @param element the parent element
      * @return a list of child elements, possibly empty but never null
      */
-    public static List<Element> getChildren(Element element) {
+    @NonNull
+    public static List<Element> getChildren(@NonNull Element element) {
         // Convenience to avoid lots of ugly DOM access casting
         NodeList children = element.getChildNodes();
         // An iterator would have been more natural (to directly drive the child list
@@ -568,7 +585,7 @@
      * @param elements the elements to be tested
      * @return true if the elements are contiguous siblings with no gaps
      */
-    public static boolean isContiguous(List<Element> elements) {
+    public static boolean isContiguous(@NonNull List<Element> elements) {
         if (elements.size() > 1) {
             // All elements must be siblings (e.g. same parent)
             Node parent = elements.get(0).getParentNode();
@@ -621,7 +638,7 @@
      * @param element2 the second element to compare
      * @return true if the two element hierarchies are logically equal
      */
-    public static boolean isEquivalent(Element element1, Element element2) {
+    public static boolean isEquivalent(@Nullable Element element1, @Nullable Element element2) {
         if (element1 == null || element2 == null) {
             return false;
         }
@@ -713,7 +730,8 @@
      * @param document the document to search for an equivalent element in
      * @return an equivalent element, or null
      */
-    public static Element findCorresponding(Element element, Document document) {
+    @Nullable
+    public static Element findCorresponding(@NonNull Element element, @NonNull Document document) {
         // Make sure the method is called correctly -- the element is for a different
         // document than the one we are searching
         assert element.getOwnerDocument() != document;
@@ -736,7 +754,8 @@
     }
 
     /** Helper method for {@link #findCorresponding(Element, Document)} */
-    private static Element findCorresponding(Element element, String targetId) {
+    @Nullable
+    private static Element findCorresponding(@NonNull Element element, @NonNull String targetId) {
         String id = element.getAttributeNS(ANDROID_URI, ATTR_ID);
         if (id != null) { // Work around DOM bug
             if (id.equals(targetId)) {
@@ -772,7 +791,8 @@
      * @param xml the XML content to be parsed (must be well formed)
      * @return the DOM document, or null
      */
-    public static Document parseStructuredDocument(String xml) {
+    @Nullable
+    public static Document parseStructuredDocument(@NonNull String xml) {
         IStructuredModel model = createStructuredModel(xml);
         if (model instanceof IDOMModel) {
             IDOMModel domModel = (IDOMModel) model;
@@ -788,7 +808,8 @@
      * @param xml the XML content to be parsed (must be well formed)
      * @return the structured model
      */
-    public static IStructuredModel createStructuredModel(String xml) {
+    @Nullable
+    public static IStructuredModel createStructuredModel(@NonNull String xml) {
         IStructuredModel model = createEmptyModel();
         IStructuredDocument document = model.getStructuredDocument();
         model.aboutToChangeModel();
@@ -803,6 +824,7 @@
      *
      * @return a new Eclipse XML model
      */
+    @NonNull
     public static IStructuredModel createEmptyModel() {
         IModelManager modelManager = StructuredModelManager.getModelManager();
         return modelManager.createUnManagedStructuredModelFor(ContentTypeID_XML);
@@ -813,6 +835,7 @@
      *
      * @return an empty Eclipse XML document
      */
+    @Nullable
     public static Document createEmptyDocument() {
         IStructuredModel model = createEmptyModel();
         if (model instanceof IDOMModel) {
@@ -832,7 +855,8 @@
      *            silently return null
      * @return the DOM document, or null
      */
-    public static Document parseDocument(String xml, boolean logParserErrors) {
+    @Nullable
+    public static Document parseDocument(@NonNull String xml, boolean logParserErrors) {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         InputSource is = new InputSource(new StringReader(xml));
         factory.setNamespaceAware(true);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
index 8f950b9..49157a7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
@@ -18,6 +18,7 @@
 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
 import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
 
+import com.android.annotations.NonNull;
 import com.android.ide.common.api.INode;
 import com.android.ide.common.api.RuleAction;
 import com.android.ide.common.api.RuleAction.Choices;
@@ -31,6 +32,7 @@
 import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
 import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog;
+import com.google.common.base.Strings;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.jface.window.Window;
@@ -72,6 +74,7 @@
     private ToolItem mZoomInButton;
     private ToolItem mZoomFitButton;
     private ToolItem mLintButton;
+    private List<RuleAction> mPrevActions;
 
     /**
      * Creates a new {@link LayoutActionBar} and adds it to the given parent.
@@ -98,14 +101,14 @@
         mLintToolBar.setLayoutData(lintData);
     }
 
+    @Override
+    public void dispose() {
+        super.dispose();
+        mPrevActions = null;
+    }
+
     /** Updates the layout contents based on the current selection */
     void updateSelection() {
-        // Get rid of any previous children
-        for (ToolItem c : mLayoutToolBar.getItems()) {
-            c.dispose();
-        }
-        mLayoutToolBar.pack();
-
         NodeProxy parent = null;
         LayoutCanvas canvas = mEditor.getCanvasControl();
         SelectionManager selectionManager = canvas.getSelectionManager();
@@ -167,12 +170,94 @@
             }
         }
 
-        addActions(actions, index, label);
+        if (!updateActions(actions)) {
+            updateToolbar(actions, index, label);
+        }
+        mPrevActions = actions;
+    }
 
+    /** Update the toolbar widgets */
+    private void updateToolbar(final List<RuleAction> actions, final int labelIndex,
+            final String label) {
+        if (mLayoutToolBar == null || mLayoutToolBar.isDisposed()) {
+            return;
+        }
+        for (ToolItem c : mLayoutToolBar.getItems()) {
+            c.dispose();
+        }
+        mLayoutToolBar.pack();
+        addActions(actions, labelIndex, label);
         mLayoutToolBar.pack();
         mLayoutToolBar.layout();
     }
 
+    /**
+     * Attempts to update the existing toolbar actions, if the action list is
+     * similar to the current list. Returns false if this cannot be done and the
+     * contents must be replaced.
+     */
+    private boolean updateActions(@NonNull List<RuleAction> actions) {
+        List<RuleAction> before = mPrevActions;
+        List<RuleAction> after = actions;
+
+        if (before == null) {
+            return false;
+        }
+
+        if (!before.equals(after) || after.size() > mLayoutToolBar.getItemCount()) {
+            return false;
+        }
+
+        int actionIndex = 0;
+        for (int i = 0, max = mLayoutToolBar.getItemCount(); i < max; i++) {
+            ToolItem item = mLayoutToolBar.getItem(i);
+            int style = item.getStyle();
+            Object data = item.getData();
+            if (data != null) {
+                // One action can result in multiple toolbar items (e.g. a choice action
+                // can result in multiple radio buttons), so we've have to replace all of
+                // them with the corresponding new action
+                RuleAction prevAction = before.get(actionIndex);
+                while (prevAction != data) {
+                    actionIndex++;
+                    if (actionIndex == before.size()) {
+                        return false;
+                    }
+                    prevAction = before.get(actionIndex);
+                    if (prevAction == data) {
+                        break;
+                    } else if (!(prevAction instanceof RuleAction.Separator)) {
+                        return false;
+                    }
+                }
+                RuleAction newAction = after.get(actionIndex);
+                assert newAction.equals(prevAction); // Maybe I can do this lazily instead?
+
+                // Update action binding to the new action
+                item.setData(newAction);
+
+                // Sync button states: the checked state is not considered part of
+                // RuleAction equality
+                if ((style & SWT.CHECK) != 0) {
+                    assert newAction instanceof Toggle;
+                    Toggle toggle = (Toggle) newAction;
+                    item.setSelection(toggle.isChecked());
+                } else if ((style & SWT.RADIO) != 0) {
+                    assert newAction instanceof Choices;
+                    Choices choices = (Choices) newAction;
+                    String current = choices.getCurrent();
+                    String id = (String) item.getData(ATTR_ID);
+                    boolean selected = Strings.nullToEmpty(current).equals(id);
+                    item.setSelection(selected);
+                }
+            } else {
+                assert (style & SWT.SEPARATOR) != 0;
+            }
+        }
+
+        return true;
+    }
+
     private void addActions(List<RuleAction> actions, int labelIndex, String label) {
         if (actions.size() > 0) {
             // Flag used to indicate that if there are any actions -after- this, it
@@ -226,7 +311,7 @@
         }
     }
 
-    private void addToggle(final Toggle toggle) {
+    private void addToggle(Toggle toggle) {
         final ToolItem button = new ToolItem(mLayoutToolBar, SWT.CHECK);
 
         URL iconUrl = toggle.getIconUrl();
@@ -237,10 +322,12 @@
         } else {
             button.setText(title);
         }
+        button.setData(toggle);
 
         button.addSelectionListener(new SelectionAdapter() {
             @Override
             public void widgetSelected(SelectionEvent e) {
+                Toggle toggle = (Toggle) button.getData();
                 toggle.getCallback().action(toggle, getSelectedNodes(),
                         toggle.getId(), button.getSelection());
                 updateSelection();
@@ -263,7 +350,7 @@
     }
 
 
-    private void addPlainAction(final RuleAction menuAction) {
+    private void addPlainAction(RuleAction menuAction) {
         final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH);
 
         URL iconUrl = menuAction.getIconUrl();
@@ -274,10 +361,12 @@
         } else {
             button.setText(title);
         }
+        button.setData(menuAction);
 
         button.addSelectionListener(new SelectionAdapter() {
             @Override
             public void widgetSelected(SelectionEvent e) {
+                RuleAction menuAction = (RuleAction) button.getData();
                 menuAction.getCallback().action(menuAction, getSelectedNodes(), menuAction.getId(),
                         false);
                 updateSelection();
@@ -285,7 +374,7 @@
         });
     }
 
-    private void addRadio(final RuleAction.Choices choices) {
+    private void addRadio(RuleAction.Choices choices) {
         List<URL> icons = choices.getIconUrls();
         List<String> titles = choices.getTitles();
         List<String> ids = choices.getIds();
@@ -301,10 +390,13 @@
             final ToolItem item = new ToolItem(mLayoutToolBar, SWT.RADIO);
             item.setToolTipText(title);
             item.setImage(IconFactory.getInstance().getIcon(iconUrl));
+            item.setData(choices);
+            item.setData(ATTR_ID, id);
             item.addSelectionListener(new SelectionAdapter() {
                 @Override
                 public void widgetSelected(SelectionEvent e) {
                     if (item.getSelection()) {
+                        RuleAction.Choices choices = (Choices) item.getData();
                         choices.getCallback().action(choices, getSelectedNodes(), id, null);
                         updateSelection();
                     }
@@ -317,7 +409,7 @@
         }
     }
 
-    private void addDropdown(final RuleAction.Choices choices) {
+    private void addDropdown(RuleAction.Choices choices) {
         final ToolItem combo = new ToolItem(mLayoutToolBar, SWT.DROP_DOWN);
         URL iconUrl = choices.getIconUrl();
         if (iconUrl != null) {
@@ -326,6 +418,7 @@
         } else {
             combo.setText(choices.getTitle());
         }
+        combo.setData(choices);
 
         Listener menuListener = new Listener() {
             @Override
@@ -335,7 +428,7 @@
                 point = combo.getDisplay().map(mLayoutToolBar, null, point);
 
                 Menu menu = new Menu(mLayoutToolBar.getShell(), SWT.POP_UP);
-
+                RuleAction.Choices choices = (Choices) combo.getData();
                 List<URL> icons = choices.getIconUrls();
                 List<String> titles = choices.getTitles();
                 List<String> ids = choices.getIds();
@@ -360,6 +453,7 @@
                     item.addSelectionListener(new SelectionAdapter() {
                         @Override
                         public void widgetSelected(SelectionEvent e) {
+                            RuleAction.Choices choices = (Choices) combo.getData();
                             choices.getCallback().action(choices, getSelectedNodes(), id, null);
                             updateSelection();
                         }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java
index bef4fba..e349a1c 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvasViewer.java
@@ -114,6 +114,9 @@
      */
     @Override
     public void setSelection(ISelection selection, boolean reveal) {
+        if (mEditorDelegate.getEditor().getIgnoreXmlUpdate()) {
+            return;
+        }
         mCanvas.getSelectionManager().setSelection(selection);
     }
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/PreferenceInitializer.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/PreferenceInitializer.java
index fa73dbc..cce470f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/PreferenceInitializer.java
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/PreferenceInitializer.java
@@ -23,6 +23,8 @@
 import com.android.ddmlib.DdmPreferences;
 import com.android.ddmuilib.DdmUiPreferences;
 
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.content.IContentType;
 import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.swt.SWT;
@@ -106,7 +108,15 @@
         store.setDefault(ATTR_LOGCAT_FONT,
                 new FontData("Courier", 10, SWT.NORMAL).toString()); //$NON-NLS-1$
 
+        // When obtaining hprof files from the device, default to opening the file
+        // only if there is a registered content type for the hprof extension.
         store.setDefault(ATTR_HPROF_ACTION, HProfHandler.ACTION_SAVE);
+        for (IContentType contentType: Platform.getContentTypeManager().getAllContentTypes()) {
+            if (contentType.isAssociatedWith(HProfHandler.DOT_HPROF)) {
+                store.setDefault(ATTR_HPROF_ACTION, HProfHandler.ACTION_OPEN);
+                break;
+            }
+        }
 
         store.setDefault(ATTR_TIME_OUT, DdmPreferences.DEFAULT_TIMEOUT);
 
diff --git a/monitor/Android.mk b/monitor/Android.mk
index a0926a7..2661429 100644
--- a/monitor/Android.mk
+++ b/monitor/Android.mk
@@ -1,7 +1,7 @@
 # Copyright 2012 The Android Open Source Project
 
 # Expose the Monitor RCP only for the SDK builds.
-ifneq (,$(is_sdk_build)$(filter sdk,$(TARGET_PRODUCT)))
+ifneq (,$(is_sdk_build)$(filter sdk sdk_x86,$(TARGET_PRODUCT)))
 
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
index 996aee4..38d3bbb 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/AndroidVersion.java
@@ -16,6 +16,7 @@
 
 package com.android.sdklib;
 
+import com.android.annotations.Nullable;
 import com.android.sdklib.repository.PkgProps;
 
 import java.util.Properties;
@@ -61,7 +62,7 @@
      */
     public AndroidVersion(int apiLevel, String codename) {
         mApiLevel = apiLevel;
-        mCodename = codename;
+        mCodename = sanitizeCodename(codename);
     }
 
     /**
@@ -73,11 +74,12 @@
     public AndroidVersion(Properties properties, int defaultApiLevel, String defaultCodeName) {
         if (properties == null) {
             mApiLevel = defaultApiLevel;
-            mCodename = defaultCodeName;
+            mCodename = sanitizeCodename(defaultCodeName);
         } else {
             mApiLevel = Integer.parseInt(properties.getProperty(PkgProps.VERSION_API_LEVEL,
-                    Integer.toString(defaultApiLevel)));
-            mCodename = properties.getProperty(PkgProps.VERSION_CODENAME, defaultCodeName);
+                                                                Integer.toString(defaultApiLevel)));
+            mCodename = sanitizeCodename(
+                            properties.getProperty(PkgProps.VERSION_CODENAME, defaultCodeName));
         }
     }
 
@@ -95,7 +97,8 @@
         if (apiLevel != null) {
             try {
                 mApiLevel = Integer.parseInt(apiLevel);
-                mCodename = properties.getProperty(PkgProps.VERSION_CODENAME, null/*defaultValue*/);
+                mCodename = sanitizeCodename(properties.getProperty(PkgProps.VERSION_CODENAME,
+                                                                    null/*defaultValue*/));
                 return;
             } catch (NumberFormatException e) {
                 error = e;
@@ -298,4 +301,25 @@
     public boolean isGreaterOrEqualThan(int api) {
         return compareTo(api, null /*codename*/) >= 0;
     }
+
+    /**
+     * Sanitizes the codename string according to the following rules:
+     * - A codename should be {@code null} for a release version or it should be a non-empty
+     *   string for an actual preview.
+     * - In input, spacing is trimmed since it is irrelevant.
+     * - An empty string or the special codename "REL" means a release version
+     *   and is converted to {@code null}.
+     *
+     * @param codename A possible-null codename.
+     * @return Null for a release version or a non-empty codename.
+     */
+    private @Nullable String sanitizeCodename(@Nullable String codename) {
+        if (codename != null) {
+            codename = codename.trim();
+            if (codename.length() == 0 || SdkConstants.CODENAME_RELEASE.equals(codename)) {
+                codename = null;
+            }
+        }
+        return codename;
+    }
 }
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
index 02688c0..ac8de23 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java
@@ -53,8 +53,7 @@
      *
      * @param sdkOsPath the root folder of the SDK
      * @param platformOSPath the root folder of the platform component
-     * @param apiLevel the API Level
-     * @param codeName the codename. can be null.
+     * @param apiVersion the API Level + codename.
      * @param versionName the version name of the platform.
      * @param revision the revision of the platform component.
      * @param layoutlibVersion The {@link LayoutlibVersion}. May be null.
@@ -65,8 +64,7 @@
     PlatformTarget(
             String sdkOsPath,
             String platformOSPath,
-            int apiLevel,
-            String codeName,
+            AndroidVersion apiVersion,
             String versionName,
             int revision,
             LayoutlibVersion layoutlibVersion,
@@ -77,7 +75,7 @@
         }
         mRootFolderOsPath = platformOSPath;
         mProperties = Collections.unmodifiableMap(properties);
-        mVersion = new AndroidVersion(apiLevel, codeName);
+        mVersion = apiVersion;
         mVersionName = versionName;
         mRevision = revision;
         mLayoutlibVersion = layoutlibVersion;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
index 6e6c657..b5ff9da 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
@@ -263,6 +263,9 @@
     /** Name of the cache folder in the $HOME/.android. */
     public final static String FD_CACHE = "cache";                      //$NON-NLS-1$
 
+    /** API codename of a release (non preview) system image or platform. **/
+    public final static String CODENAME_RELEASE = "REL";                //$NON-NLS-1$
+
     /** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */
     public final static String NS_RESOURCES =
         "http://schemas.android.com/apk/res/android";                   //$NON-NLS-1$
@@ -377,7 +380,6 @@
             FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE_CLANG;
 
     /* Folder paths relative to a addon folder */
-
     /** Path of the images directory relative to a folder folder.
      *  This is an OS path, ending with a separator. */
     public final static String OS_ADDON_LIBS_FOLDER = FD_ADDON_LIBS + File.separator;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
index a786728..5c570ce 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
@@ -437,12 +437,10 @@
                 }
             }
 
-            // codename (optional)
-            String apiCodename = platformProp.get(PROP_VERSION_CODENAME);
-            if (apiCodename != null && apiCodename.equals("REL")) {
-                apiCodename = null; // REL means it's a release version and therefore the
-                                    // codename is irrelevant at this point.
-            }
+            // Codename must be either null or a platform codename.
+            // REL means it's a release version and therefore the codename should be null.
+            AndroidVersion apiVersion =
+                new AndroidVersion(apiNumber, platformProp.get(PROP_VERSION_CODENAME));
 
             // version string
             String apiName = platformProp.get(PkgProps.PLATFORM_VERSION);
@@ -463,7 +461,7 @@
             int revision = 1;
             LayoutlibVersion layoutlibVersion = null;
             try {
-                revision = Integer.parseInt(platformProp.get(PkgProps.PKG_MAJOR_REV));
+                revision = Integer.parseInt(platformProp.get(PkgProps.PKG_REVISION));
             } catch (NumberFormatException e) {
                 // do nothing, we'll keep the default value of 1.
             }
@@ -489,14 +487,13 @@
             }
 
             ISystemImage[] systemImages =
-                getPlatformSystemImages(sdkOsPath, platformFolder, apiNumber, apiCodename);
+                getPlatformSystemImages(sdkOsPath, platformFolder, apiVersion);
 
             // create the target.
             PlatformTarget target = new PlatformTarget(
                     sdkOsPath,
                     platformFolder.getAbsolutePath(),
-                    apiNumber,
-                    apiCodename,
+                    apiVersion,
                     apiName,
                     revision,
                     layoutlibVersion,
@@ -574,16 +571,14 @@
      *
      * @param sdkOsPath The path to the SDK.
      * @param root Root of the platform target being loaded.
-     * @param apiNumber API level of platform being loaded
-     * @param apiCodename Optional codename of platform being loaded
+     * @param version API level + codename of platform being loaded.
      * @return an array of ISystemImage containing all the system images for the target.
      *              The list can be empty.
     */
     private static ISystemImage[] getPlatformSystemImages(
             String sdkOsPath,
             File root,
-            int apiNumber,
-            String apiCodename) {
+            AndroidVersion version) {
         Set<ISystemImage> found = new TreeSet<ISystemImage>();
         Set<String> abiFound = new HashSet<String>();
 
@@ -592,8 +587,6 @@
         // The actual directory names are irrelevant.
         // If we find multiple occurrences of the same platform/abi, the first one read wins.
 
-        AndroidVersion version = new AndroidVersion(apiNumber, apiCodename);
-
         File[] firstLevelFiles = new File(sdkOsPath, SdkConstants.FD_SYSTEM_IMAGES).listFiles();
         if (firstLevelFiles != null) {
             for (File firstLevel : firstLevelFiles) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java
index a90fa0a..40235c2 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java
@@ -16,6 +16,11 @@
 

 package com.android.sdklib.internal.repository.packages;

 

+import com.android.annotations.NonNull;

+

+import java.util.regex.Matcher;

+import java.util.regex.Pattern;

+

 

 /**

  * Package multi-part revision number composed of a tuple

@@ -32,6 +37,10 @@
     public static final int IMPLICIT_MICRO_REV = 0;

     public static final int NOT_A_PREVIEW      = 0;

 

+    private final static Pattern FULL_REVISION_PATTERN =

+        //                   1=major       2=minor       3=micro              4=preview

+        Pattern.compile("\\s*([0-9]+)(?:\\.([0-9]+)(?:\\.([0-9]+))?)?\\s*(?:rc([0-9]+))?\\s*");

+

     private final int mMajor;

     private final int mMinor;

     private final int mMicro;

@@ -73,6 +82,52 @@
     }

 

     /**

+     * Parses a string of format "major.minor.micro rcPreview" and returns

+     * a new {@link FullRevision} for it. All the fields except major are

+     * optional.

+     * <p/>

+     * The parsing is equivalent to the pseudo-BNF/regexp:

+     * <pre>

+     *   Major/Minor/Micro/Preview := [0-9]+

+     *   Revision := Major ('.' Minor ('.' Micro)? )? \s* ('rc'Preview)?

+     * </pre>

+     *

+     * @param revision A non-null revision to parse.

+     * @return A new non-null {@link FullRevision}.

+     * @throws NumberFormatException if the parsing failed.

+     */

+    public static @NonNull FullRevision parseRevision(@NonNull String revision)

+            throws NumberFormatException {

+

+        if (revision == null) {

+            throw new NumberFormatException("revision is <null>"); //$NON-NLS-1$

+        }

+

+        Throwable cause = null;

+        try {

+            Matcher m = FULL_REVISION_PATTERN.matcher(revision);

+            if (m != null && m.matches()) {

+                int major = Integer.parseInt(m.group(1));

+                String s = m.group(2);

+                int minor = s == null ? IMPLICIT_MINOR_REV : Integer.parseInt(s);

+                s = m.group(3);

+                int micro = s == null ? IMPLICIT_MICRO_REV : Integer.parseInt(s);

+                s = m.group(4);

+                int preview = s == null ? NOT_A_PREVIEW : Integer.parseInt(s);

+

+                return new FullRevision(major, minor, micro, preview);

+            }

+        } catch (Throwable t) {

+            cause = t;

+        }

+

+        NumberFormatException n = new NumberFormatException(

+                "Invalid full revision: " + revision); //$NON-NLS-1$

+        n.initCause(cause);

+        throw n;

+    }

+

+    /**

      * Returns the version in a fixed format major.minor.micro

      * with an optional "rc preview#". For example it would

      * return "18.0.0", "18.1.0" or "18.1.2 rc5".

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
index 10c4ce0..6028873 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
@@ -102,12 +102,19 @@
         super(source, props, revision, license, description, descUrl,

                 archiveOs, archiveArch, archiveOsPath);

 

-        int major = getPropertyInt(props, PkgProps.PKG_MAJOR_REV,revision);

-        int minor = getPropertyInt(props, PkgProps.PKG_MINOR_REV, FullRevision.IMPLICIT_MINOR_REV);

-        int micro = getPropertyInt(props, PkgProps.PKG_MICRO_REV, FullRevision.IMPLICIT_MINOR_REV);

-        int preview = getPropertyInt(props, PkgProps.PKG_PREVIEW_REV, FullRevision.NOT_A_PREVIEW);

+        String revStr = getProperty(props, PkgProps.PKG_REVISION, null);

 

-        mPreviewVersion = new FullRevision(major, minor, micro, preview);

+        FullRevision rev = null;

+        if (revStr != null) {

+            try {

+                rev = FullRevision.parseRevision(revStr);

+            } catch (NumberFormatException ignore) {}

+        }

+        if (rev == null) {

+            rev = new FullRevision(revision);

+        }

+

+        mPreviewVersion = rev;

     }

 

     @Override

@@ -118,11 +125,7 @@
     @Override

     public void saveProperties(Properties props) {

         super.saveProperties(props);

-

-        props.setProperty(PkgProps.PKG_MAJOR_REV,   Integer.toString(mPreviewVersion.getMajor()));

-        props.setProperty(PkgProps.PKG_MINOR_REV,   Integer.toString(mPreviewVersion.getMinor()));

-        props.setProperty(PkgProps.PKG_MICRO_REV,   Integer.toString(mPreviewVersion.getMicro()));

-        props.setProperty(PkgProps.PKG_PREVIEW_REV, Integer.toString(mPreviewVersion.getPreview()));

+        props.setProperty(PkgProps.PKG_REVISION, mPreviewVersion.toString());

     }

 

     @Override

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java
index 2678b1f..9ca9e22 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java
@@ -16,6 +16,8 @@
 

 package com.android.sdklib.internal.repository.packages;

 

+import com.android.annotations.NonNull;

+

 

 /**

  * Package revision number composed of a <em>single</em> major revision.

@@ -34,4 +36,21 @@
     public String toString() {

         return super.toShortString();

     }

+

+    /**

+     * Parses a single-integer string and returns a new {@link MajorRevision} for it.

+     *

+     * @param revision A non-null revision to parse.

+     * @return A new non-null {@link MajorRevision}.

+     * @throws NumberFormatException if the parsing failed.

+     */

+    public static @NonNull MajorRevision parseRevision(@NonNull String revision)

+            throws NumberFormatException {

+

+        if (revision == null) {

+            throw new NumberFormatException("revision is <null>"); //$NON-NLS-1$

+        }

+

+        return new MajorRevision(Integer.parseInt(revision.trim()));

+    }

 }

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
index 7e61e5f..1348bb9 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
@@ -77,7 +77,19 @@
         super(source, props, revision, license, description, descUrl,

                 archiveOs, archiveArch, archiveOsPath);

 

-        mRevision = new MajorRevision(getPropertyInt(props, PkgProps.PKG_MAJOR_REV, revision));

+        String revStr = getProperty(props, PkgProps.PKG_REVISION, null);

+

+        MajorRevision rev = null;

+        if (revStr != null) {

+            try {

+                rev = MajorRevision.parseRevision(revStr);

+            } catch (NumberFormatException ignore) {}

+        }

+        if (rev == null) {

+            rev = new MajorRevision(revision);

+        }

+

+        mRevision = rev;

     }

 

     /**

@@ -93,7 +105,7 @@
     @Override

     public void saveProperties(Properties props) {

         super.saveProperties(props);

-        props.setProperty(PkgProps.PKG_MAJOR_REV, Integer.toString(mRevision.getMajor()));

+        props.setProperty(PkgProps.PKG_REVISION, mRevision.toString());

     }

 

     @Override

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java
index 4406b42..571a4a6 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/PkgProps.java
@@ -29,6 +29,7 @@
 public class PkgProps {

 

     // Base Package

+    public static final String PKG_REVISION             = "Pkg.Revision";           //$NON-NLS-1$

     public static final String PKG_LICENSE              = "Pkg.License";            //$NON-NLS-1$

     public static final String PKG_DESC                 = "Pkg.Desc";               //$NON-NLS-1$

     public static final String PKG_DESC_URL             = "Pkg.DescUrl";            //$NON-NLS-1$

@@ -37,13 +38,6 @@
     public static final String PKG_SOURCE_URL           = "Pkg.SourceUrl";          //$NON-NLS-1$

     public static final String PKG_OBSOLETE             = "Pkg.Obsolete";           //$NON-NLS-1$

 

-    // FullRevision

-    // Note that MajorRev keeps the legacy "Pkg.Revision" property name for legacy compatibility.

-    public static final String PKG_MAJOR_REV            = "Pkg.Revision";           //$NON-NLS-1$

-    public static final String PKG_MINOR_REV            = "Pkg.MinorRev";           //$NON-NLS-1$

-    public static final String PKG_MICRO_REV            = "Pkg.MicroRev";           //$NON-NLS-1$

-    public static final String PKG_PREVIEW_REV          = "Pkg.PreviewRev";         //$NON-NLS-1$

-

     // AndroidVersion

 

     public static final String VERSION_API_LEVEL        = "AndroidVersion.ApiLevel";//$NON-NLS-1$

diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/AndroidVersionTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/AndroidVersionTest.java
new file mode 100755
index 0000000..5270bd1
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/AndroidVersionTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link AndroidVersion}.
+ */
+public class AndroidVersionTest extends TestCase {
+
+    public final void testAndroidVersion() {
+        AndroidVersion v = new AndroidVersion(1, "  CODENAME   ");
+        assertEquals(1, v.getApiLevel());
+        assertEquals("CODENAME", v.getApiString());
+        assertTrue(v.isPreview());
+        assertEquals("CODENAME", v.getCodename());
+        assertEquals("CODENAME".hashCode(), v.hashCode());
+        assertEquals("API 1, CODENAME preview", v.toString());
+
+        v = new AndroidVersion(15, "REL");
+        assertEquals(15, v.getApiLevel());
+        assertEquals("15", v.getApiString());
+        assertFalse(v.isPreview());
+        assertNull(v.getCodename());
+        assertTrue(v.equals(15));
+        assertEquals(15, v.hashCode());
+        assertEquals("API 15", v.toString());
+
+        v = new AndroidVersion(15, null);
+        assertEquals(15, v.getApiLevel());
+        assertEquals("15", v.getApiString());
+        assertFalse(v.isPreview());
+        assertNull(v.getCodename());
+        assertTrue(v.equals(15));
+        assertEquals(15, v.hashCode());
+        assertEquals("API 15", v.toString());
+
+        // An empty codename is like a null codename
+        v = new AndroidVersion(15, "   ");
+        assertFalse(v.isPreview());
+        assertNull(v.getCodename());
+        assertEquals("15", v.getApiString());
+
+        v = new AndroidVersion(15, "");
+        assertFalse(v.isPreview());
+        assertNull(v.getCodename());
+        assertEquals("15", v.getApiString());
+
+        assertTrue(v.isGreaterOrEqualThan(0));
+        assertTrue(v.isGreaterOrEqualThan(14));
+        assertTrue(v.isGreaterOrEqualThan(15));
+        assertFalse(v.isGreaterOrEqualThan(16));
+        assertFalse(v.isGreaterOrEqualThan(Integer.MAX_VALUE));
+   }
+
+}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.java
index d816256..9bf2703 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.java
@@ -34,14 +34,7 @@
     public static Properties createProps(FullRevision revision) {
         Properties props = new Properties();
         if (revision != null) {
-            props.setProperty(PkgProps.PKG_MAJOR_REV,
-                              Integer.toString(revision.getMajor()));
-            props.setProperty(PkgProps.PKG_MINOR_REV,
-                              Integer.toString(revision.getMinor()));
-            props.setProperty(PkgProps.PKG_MICRO_REV,
-                              Integer.toString(revision.getMicro()));
-            props.setProperty(PkgProps.PKG_PREVIEW_REV,
-                              Integer.toString(revision.getPreview()));
+            props.setProperty(PkgProps.PKG_REVISION, revision.toString());
         }
         return props;
     }
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionTest.java
index 97e76bb..d072d05 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionTest.java
@@ -20,16 +20,6 @@
 
 public class FullRevisionTest extends TestCase {
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
     public final void testFullRevision() {
         FullRevision p = new FullRevision(5);
         assertEquals(5, p.getMajor());
@@ -38,7 +28,9 @@
         assertEquals(FullRevision.NOT_A_PREVIEW, p.getPreview());
         assertFalse (p.isPreview());
         assertEquals("5", p.toShortString());
+        assertEquals(p, FullRevision.parseRevision("5"));
         assertEquals("5.0.0", p.toString());
+        assertEquals(p, FullRevision.parseRevision("5.0.0"));
 
         p = new FullRevision(5, 0, 0, 6);
         assertEquals(5, p.getMajor());
@@ -47,7 +39,9 @@
         assertEquals(6, p.getPreview());
         assertTrue  (p.isPreview());
         assertEquals("5 rc6", p.toShortString());
+        assertEquals(p, FullRevision.parseRevision("5 rc6"));
         assertEquals("5.0.0 rc6", p.toString());
+        assertEquals(p, FullRevision.parseRevision("5.0.0 rc6"));
 
         p = new FullRevision(6, 7, 0);
         assertEquals(6, p.getMajor());
@@ -56,7 +50,9 @@
         assertEquals(0, p.getPreview());
         assertFalse (p.isPreview());
         assertEquals("6.7", p.toShortString());
+        assertEquals(p, FullRevision.parseRevision("6.7"));
         assertEquals("6.7.0", p.toString());
+        assertEquals(p, FullRevision.parseRevision("6.7.0"));
 
         p = new FullRevision(10, 11, 12, FullRevision.NOT_A_PREVIEW);
         assertEquals(10, p.getMajor());
@@ -66,6 +62,7 @@
         assertFalse (p.isPreview());
         assertEquals("10.11.12", p.toShortString());
         assertEquals("10.11.12", p.toString());
+        assertEquals(p, FullRevision.parseRevision("10.11.12"));
 
         p = new FullRevision(10, 11, 12, 13);
         assertEquals(10, p.getMajor());
@@ -75,6 +72,48 @@
         assertTrue  (p.isPreview());
         assertEquals("10.11.12 rc13", p.toShortString());
         assertEquals("10.11.12 rc13", p.toString());
+        assertEquals(p, FullRevision.parseRevision("10.11.12 rc13"));
+        assertEquals(p, FullRevision.parseRevision("   10.11.12 rc13"));
+        assertEquals(p, FullRevision.parseRevision("10.11.12 rc13   "));
+        assertEquals(p, FullRevision.parseRevision("   10.11.12   rc13   "));
+    }
+
+    public final void testParseError() {
+        String errorMsg = null;
+        try {
+            FullRevision.parseRevision("not a number");
+            fail("FullRevision.parseRevision should thrown NumberFormatException");
+        } catch (NumberFormatException e) {
+            errorMsg = e.getMessage();
+        }
+        assertEquals("Invalid full revision: not a number", errorMsg);
+
+        errorMsg = null;
+        try {
+            FullRevision.parseRevision("5 .6 .7");
+            fail("FullRevision.parseRevision should thrown NumberFormatException");
+        } catch (NumberFormatException e) {
+            errorMsg = e.getMessage();
+        }
+        assertEquals("Invalid full revision: 5 .6 .7", errorMsg);
+
+        errorMsg = null;
+        try {
+            FullRevision.parseRevision("5.0.0 preview 1");
+            fail("FullRevision.parseRevision should thrown NumberFormatException");
+        } catch (NumberFormatException e) {
+            errorMsg = e.getMessage();
+        }
+        assertEquals("Invalid full revision: 5.0.0 preview 1", errorMsg);
+
+        errorMsg = null;
+        try {
+            FullRevision.parseRevision("  5.1.2 rc 42  ");
+            fail("FullRevision.parseRevision should thrown NumberFormatException");
+        } catch (NumberFormatException e) {
+            errorMsg = e.getMessage();
+        }
+        assertEquals("Invalid full revision:   5.1.2 rc 42  ", errorMsg);
     }
 
     public final void testCompareTo() {
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MajorRevisionTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MajorRevisionTest.java
new file mode 100755
index 0000000..b77caad
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MajorRevisionTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository.packages;
+
+import junit.framework.TestCase;
+
+public class MajorRevisionTest extends TestCase {
+
+    public final void testMajorRevision() {
+        MajorRevision p = new MajorRevision(5);
+        assertEquals(5, p.getMajor());
+        assertEquals(FullRevision.IMPLICIT_MINOR_REV, p.getMinor());
+        assertEquals(FullRevision.IMPLICIT_MICRO_REV, p.getMicro());
+        assertEquals(FullRevision.NOT_A_PREVIEW, p.getPreview());
+        assertFalse (p.isPreview());
+        assertEquals("5", p.toShortString());
+        assertEquals(p, MajorRevision.parseRevision("5"));
+        assertEquals("5", p.toString());
+
+        assertEquals(new FullRevision(5, 0, 0, 0), p);
+    }
+
+    public final void testParseError() {
+        String errorMsg = null;
+        try {
+            MajorRevision.parseRevision("5.0.0");
+            fail("MajorRevision.parseRevision should thrown NumberFormatException");
+        } catch (NumberFormatException e) {
+            errorMsg = e.getMessage();
+        }
+        assertEquals("For input string: \"5.0.0\"", errorMsg);
+    }
+
+    public final void testCompareTo() {
+        MajorRevision s4 = new MajorRevision(4);
+        MajorRevision i4 = new MajorRevision(4);
+        FullRevision  g5 = new FullRevision (5, 1, 0, 6);
+        MajorRevision y5 = new MajorRevision(5);
+        FullRevision  c5 = new FullRevision (5, 1, 0, 6);
+        FullRevision  o5 = new FullRevision (5, 0, 0, 7);
+        FullRevision  p5 = new FullRevision (5, 1, 0, 0);
+
+        assertEquals(s4, i4);                   // 4.0.0-0 == 4.0.0-0
+        assertEquals(g5, c5);                   // 5.1.0-6 == 5.1.0-6
+
+        assertFalse(y5.equals(p5));             // 5.0.0-0 != 5.1.0-0
+        assertFalse(g5.equals(p5));             // 5.1.0-6 != 5.1.0-0
+        assertTrue (s4.compareTo(i4) == 0);     // 4.0.0-0 == 4.0.0-0
+        assertTrue (s4.compareTo(y5)  < 0);     // 4.0.0-0  < 5.0.0-0
+        assertTrue (y5.compareTo(y5) == 0);     // 5.0.0-0 == 5.0.0-0
+        assertTrue (y5.compareTo(p5)  < 0);     // 5.0.0-0  < 5.1.0-0
+        assertTrue (o5.compareTo(y5)  < 0);     // 5.0.0-7  < 5.0.0-0
+        assertTrue (p5.compareTo(p5) == 0);     // 5.1.0-0 == 5.1.0-0
+        assertTrue (c5.compareTo(p5)  < 0);     // 5.1.0-6  < 5.1.0-0
+        assertTrue (p5.compareTo(c5)  > 0);     // 5.1.0-0  > 5.1.0-6
+        assertTrue (p5.compareTo(o5)  > 0);     // 5.1.0-0  > 5.0.0-7
+        assertTrue (c5.compareTo(o5)  > 0);     // 5.1.0-6  > 5.0.0-7
+        assertTrue (o5.compareTo(o5) == 0);     // 5.0.0-7  > 5.0.0-7
+    }
+
+}
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PackageTest.java
index 6023b5a..f4d7b56 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PackageTest.java
@@ -136,7 +136,7 @@
         Properties props = new Properties();
 
         // Package properties
-        props.setProperty(PkgProps.PKG_MAJOR_REV, "42");
+        props.setProperty(PkgProps.PKG_REVISION, "42");
         props.setProperty(PkgProps.PKG_LICENSE, "The License");
         props.setProperty(PkgProps.PKG_DESC, "Some description.");
         props.setProperty(PkgProps.PKG_DESC_URL, "http://description/url");
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AboutDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AboutDialog.java
index 6352bdb..35e3420 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AboutDialog.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AboutDialog.java
@@ -107,7 +107,7 @@
                 }

             }

 

-            String revision = p.getProperty(PkgProps.PKG_MAJOR_REV);

+            String revision = p.getProperty(PkgProps.PKG_REVISION);

             if (revision != null) {

                 return revision;

             }