am 226f95c4: Fix typo in javadoc.

* commit '226f95c416fa52c879bb2d5e19a23e252ee8b120':
  Fix typo in javadoc.
diff --git a/.gitignore b/.gitignore
index 80b6f7c..019a6f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
 *.pyc
 Thumbs.db
 *.class
+*.DS_Store
 testapps/testSensors/proguard.cfg
 
 # Hide temporary files created by the build_server script
diff --git a/anttargetprint/.classpath b/anttargetprint/.classpath
index 901231a..fa310aa 100644
--- a/anttargetprint/.classpath
+++ b/anttargetprint/.classpath
@@ -2,6 +2,6 @@
 <classpath>
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
-	<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/apigenerator/.classpath b/apigenerator/.classpath
index bd75119..613f84d 100644
--- a/apigenerator/.classpath
+++ b/apigenerator/.classpath
@@ -2,7 +2,7 @@
 <classpath>
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
 	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
 	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/common"/>
diff --git a/apps/SdkController/assets/intro_help.html b/apps/SdkController/assets/intro_help.html
index 000efa4..c957f73 100755
--- a/apps/SdkController/assets/intro_help.html
+++ b/apps/SdkController/assets/intro_help.html
@@ -32,7 +32,7 @@
 <ol>

 <li>Connect your device to your computer via USB. Make sure to enable <i>USB Debugging</i> in <i>Settings &gt; Developer Options</i>. </li>

 <li>Start this application on your device. </li>

-<li>On the computer in a shell, run: <br/><i>adb forward tcp:1968 tcp:1968</i> </li>

+<li>On the computer in a shell, run: <br/><i>adb forward tcp:1970 localhost:android.sdk.controller</i> </li>

 <li>Finally <b>run an emulator</b> with an AVD targetting <b>API 15</b>. </li>

 </ol>

 <a href="https://sites.google.com/a/android.com/tools/recent/sensoremulation">Read more.</a>

diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
index f22f12f..06b29f1 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/MultiTouchActivity.java
@@ -30,15 +30,16 @@
 import android.widget.TextView;

 

 import com.android.tools.sdkcontroller.R;

-import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;

-import com.android.tools.sdkcontroller.handlers.MultiTouchHandler;

+import com.android.tools.sdkcontroller.handlers.MultiTouchChannel;

+import com.android.tools.sdkcontroller.lib.Channel;

+import com.android.tools.sdkcontroller.lib.ProtocolConstants;

 import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder;

 import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener;

 import com.android.tools.sdkcontroller.utils.ApiHelper;

 import com.android.tools.sdkcontroller.views.MultiTouchView;

 

 /**

- * Activity that controls and displays the {@link MultiTouchHandler}.

+ * Activity that controls and displays the {@link MultiTouchChannel}.

  */

 public class MultiTouchActivity extends BaseBindingActivity

         implements android.os.Handler.Callback {

@@ -47,14 +48,7 @@
     private static String TAG = MultiTouchActivity.class.getSimpleName();

     private static boolean DEBUG = true;

 

-    /** Received frame is JPEG image. */

-    private static final int FRAME_JPEG = 1;

-    /** Received frame is RGB565 bitmap. */

-    private static final int FRAME_RGB565 = 2;

-    /** Received frame is RGB888 bitmap. */

-    private static final int FRAME_RGB888 = 3;

-

-    private volatile MultiTouchHandler mHandler;

+    private volatile MultiTouchChannel mHandler;

 

     private TextView mTextError;

     private TextView mTextStatus;

@@ -108,8 +102,9 @@
     @Override

     protected void onServiceConnected() {

         if (DEBUG) Log.d(TAG, "onServiceConnected");

-        mHandler = (MultiTouchHandler) getServiceBinder().getHandler(HandlerType.MultiTouch);

+        mHandler = (MultiTouchChannel) getServiceBinder().getChannel(Channel.MULTITOUCH_CHANNEL);

         if (mHandler != null) {

+            mHandler.setViewSize(mImageView.getWidth(), mImageView.getHeight());

             mHandler.addUiHandler(mUiHandler);

         }

     }

@@ -150,7 +145,7 @@
                     if (binder != null) {

                         boolean connected = binder.isEmuConnected();

                         mImageView.setEnabled(connected);

-                        updateStatus(connected ? "Emulated connected" : "Emulator disconnected");

+                        updateStatus(connected ? "Emulator connected" : "Emulator disconnected");

                     }

                 }

             });

@@ -169,43 +164,67 @@
          */

         @Override

         public boolean onTouch(View v, MotionEvent event) {

-            StringBuilder sb = new StringBuilder();

+            ByteBuffer bb = null;

             final int action = event.getAction();

             final int action_code = action & MotionEvent.ACTION_MASK;

             final int action_pid_index = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;

+            int msg_type = 0;

+            MultiTouchChannel h = mHandler;

 

             // Build message for the emulator.

             switch (action_code) {

                 case MotionEvent.ACTION_MOVE:

-                    sb.append("action=move");

-                    for (int n = 0; n < event.getPointerCount(); n++) {

-                        mImageView.constructEventMessage(sb, event, n);

+                    if (h != null) {

+                        bb = ByteBuffer.allocate(

+                                event.getPointerCount() * ProtocolConstants.MT_EVENT_ENTRY_SIZE);

+                        bb.order(h.getEndian());

+                        for (int n = 0; n < event.getPointerCount(); n++) {

+                            mImageView.constructEventMessage(bb, event, n);

+                        }

+                        msg_type = ProtocolConstants.MT_MOVE;

                     }

                     break;

                 case MotionEvent.ACTION_DOWN:

-                    sb.append("action=down");

-                    mImageView.constructEventMessage(sb, event, action_pid_index);

+                    if (h != null) {

+                        bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);

+                        bb.order(h.getEndian());

+                        mImageView.constructEventMessage(bb, event, action_pid_index);

+                        msg_type = ProtocolConstants.MT_FISRT_DOWN;

+                    }

                     break;

                 case MotionEvent.ACTION_UP:

-                    sb.append("action=up pid=").append(event.getPointerId(action_pid_index));

+                    if (h != null) {

+                        bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);

+                        bb.order(h.getEndian());

+                        bb.putInt(event.getPointerId(action_pid_index));

+                        msg_type = ProtocolConstants.MT_LAST_UP;

+                    }

                     break;

                 case MotionEvent.ACTION_POINTER_DOWN:

-                    sb.append("action=pdown");

-                    mImageView.constructEventMessage(sb, event, action_pid_index);

+                    if (h != null) {

+                        bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);

+                        bb.order(h.getEndian());

+                        mImageView.constructEventMessage(bb, event, action_pid_index);

+                        msg_type = ProtocolConstants.MT_POINTER_DOWN;

+                    }

                     break;

                 case MotionEvent.ACTION_POINTER_UP:

-                    sb.append("action=pup pid=").append(event.getPointerId(action_pid_index));

+                    if (h != null) {

+                        bb = ByteBuffer.allocate(ProtocolConstants.MT_EVENT_ENTRY_SIZE);

+                        bb.order(h.getEndian());

+                        bb.putInt(event.getPointerId(action_pid_index));

+                        msg_type = ProtocolConstants.MT_POINTER_UP;

+                    }

                     break;

                 default:

                     Log.w(TAG, "Unknown action type: " + action_code);

                     return true;

             }

 

-            if (DEBUG) Log.d(TAG, sb.toString());

+            if (DEBUG && bb != null) Log.d(TAG, bb.toString());

 

-            MultiTouchHandler h = mHandler;

-            if (h != null) {

-                h.sendEventToEmulator(sb.toString() + '\0');

+            if (h != null && bb != null) {

+                h.postMessage(msg_type, bb);

             }

             return true;

         }

@@ -215,18 +234,18 @@
     @Override

     public boolean handleMessage(Message msg) {

         switch (msg.what) {

-        case MultiTouchHandler.EVENT_MT_START:

-            MultiTouchHandler h = mHandler;

+        case MultiTouchChannel.EVENT_MT_START:

+            MultiTouchChannel h = mHandler;

             if (h != null) {

-                mHandler.setViewSize(mImageView.getWidth(), mImageView.getHeight());

+                mImageView.setEnabled(true);

                 mImageView.setOnTouchListener(mTouchListener);

             }

             break;

-        case MultiTouchHandler.EVENT_MT_STOP:

+        case MultiTouchChannel.EVENT_MT_STOP:

             mImageView.setOnTouchListener(null);

             break;

-        case MultiTouchHandler.EVENT_FRAME_BUFFER:

-            onFrameBuffer((byte[]) msg.obj);

+        case MultiTouchChannel.EVENT_FRAME_BUFFER:

+            onFrameBuffer(((ByteBuffer) msg.obj).array());

             break;

         }

         return true; // we consumed this message

@@ -267,7 +286,7 @@
         // Update application display.

         updateDisplay(disp_width, disp_height);

 

-        if (format == FRAME_JPEG) {

+        if (format == ProtocolConstants.MT_FRAME_JPEG) {

             /*

              * Framebuffer is in JPEG format.

              */

@@ -293,7 +312,7 @@
             }

 

             // Convert the blob bitmap into bitmap that we will display.

-            if (format == FRAME_RGB565) {

+            if (format == ProtocolConstants.MT_FRAME_RGB565) {

                 for (int n = 0; n < pixel_num; n++) {

                     // Blob bitmap is in RGB565 format.

                     final int color = bb.getShort();

@@ -302,7 +321,7 @@
                     final int b = ((color & 0x1f) << 3) | ((color & 0x1f) >> 2);

                     mColors[n] = Color.rgb(r, g, b);

                 }

-            } else if (format == FRAME_RGB888) {

+            } else if (format == ProtocolConstants.MT_FRAME_RGB888) {

                 for (int n = 0; n < pixel_num; n++) {

                     // Blob bitmap is in RGB565 format.

                     final int r = bb.getChar();

diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
index 5055c23..001398b 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/activities/SensorActivity.java
@@ -35,14 +35,14 @@
 import android.widget.TextView;

 

 import com.android.tools.sdkcontroller.R;

-import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;

-import com.android.tools.sdkcontroller.handlers.SensorsHandler;

-import com.android.tools.sdkcontroller.handlers.SensorsHandler.MonitoredSensor;

+import com.android.tools.sdkcontroller.handlers.SensorChannel;

+import com.android.tools.sdkcontroller.handlers.SensorChannel.MonitoredSensor;

+import com.android.tools.sdkcontroller.lib.Channel;

 import com.android.tools.sdkcontroller.service.ControllerService.ControllerBinder;

 import com.android.tools.sdkcontroller.service.ControllerService.ControllerListener;

 

 /**

- * Activity that displays and controls the sensors from {@link SensorsHandler}.

+ * Activity that displays and controls the sensors from {@link SensorChannel}.

  * For each sensor it displays a checkbox that is enabled if the sensor is supported

  * by the emulator. The user can select whether the sensor is active. It also displays

  * data from the sensor when available.

@@ -61,10 +61,10 @@
     private TextView mTextStatus;

     private TextView mTextTargetHz;

     private TextView mTextActualHz;

-    private SensorsHandler mSensorHandler;

+    private SensorChannel mSensorHandler;

 

     private final Map<MonitoredSensor, DisplayInfo> mDisplayedSensors =

-        new HashMap<SensorsHandler.MonitoredSensor, SensorActivity.DisplayInfo>();

+        new HashMap<SensorChannel.MonitoredSensor, SensorActivity.DisplayInfo>();

     private final android.os.Handler mUiHandler = new android.os.Handler(this);

     private int mTargetSampleRate;

     private long mLastActualUpdateMs;

@@ -173,7 +173,7 @@
             removeSensorUi();

         }

 

-        mSensorHandler = (SensorsHandler) getServiceBinder().getHandler(HandlerType.Sensor);

+        mSensorHandler = (SensorChannel) getServiceBinder().getChannel(Channel.SENSOR_CHANNEL);

         if (mSensorHandler != null) {

             mSensorHandler.addUiHandler(mUiHandler);

             mUiHandler.sendEmptyMessage(MSG_UPDATE_ACTUAL_HZ);

@@ -261,19 +261,19 @@
     public boolean handleMessage(Message msg) {

         DisplayInfo info = null;

         switch (msg.what) {

-        case SensorsHandler.SENSOR_STATE_CHANGED:

+        case SensorChannel.SENSOR_STATE_CHANGED:

             info = mDisplayedSensors.get(msg.obj);

             if (info != null) {

                 info.updateState();

             }

             break;

-        case SensorsHandler.SENSOR_DISPLAY_MODIFIED:

+        case SensorChannel.SENSOR_DISPLAY_MODIFIED:

             info = mDisplayedSensors.get(msg.obj);

             if (info != null) {

                 info.updateValue();

             }

             if (mSensorHandler != null) {

-                updateStatus(Integer.toString(mSensorHandler.getEventSentCount()) + " events sent");

+                updateStatus(Integer.toString(mSensorHandler.getMsgSentCount()) + " events sent");

 

                 // Update the "actual rate" field if the value has changed

                 long ms = mSensorHandler.getActualUpdateMs();

diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java
deleted file mode 100755
index b15b8c1..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/BaseHandler.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*

- * 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.tools.sdkcontroller.handlers;

-

-import java.util.ArrayList;

-import java.util.List;

-import java.util.concurrent.BlockingQueue;

-import java.util.concurrent.LinkedBlockingQueue;

-import java.util.concurrent.atomic.AtomicInteger;

-

-import android.content.Context;

-import android.os.Message;

-import android.util.Log;

-

-import com.android.tools.sdkcontroller.lib.EmulatorConnection;

-import com.android.tools.sdkcontroller.lib.EmulatorListener;

-import com.android.tools.sdkcontroller.service.ControllerService;

-

-

-/**

- * An abstract base class for all "action handlers".

- * <p/>

- * The {@link ControllerService} can deal with several handlers, each have a specific

- * purpose as described by {@link HandlerType}.

- * <p/>

- * The {@link BaseHandler} class adds support for activities to connect by providing

- * an {@link android.os.Handler} (which we'll call a "UI Handler" to differentiate it

- * from our "Service Handler"). The service handler will provide events via this

- * UI handler directly on the activity's UI thread.

- * <p/>

- * The {@link BaseHandler} keeps track of the current {@link EmulatorConnection} given

- * via {@link #onStart(EmulatorConnection, Context)}.

- * <p/>

- * The {@link BaseHandler} provides a simple way for activities to send event messages

- * back to the emulator by using {@link #sendEventToEmulator(String)}. This method

- * is safe to call from any thread, even the UI thread.

- */

-public abstract class BaseHandler {

-

-    protected static final boolean DEBUG = false;

-    protected static final String TAG = null;

-

-    private EmulatorConnection mConnection;

-

-    private final AtomicInteger mEventCount = new AtomicInteger(0);

-    private volatile boolean mRunEventQueue = true;

-    private final BlockingQueue<String> mEventQueue = new LinkedBlockingQueue<String>();

-    private static String EVENT_QUEUE_END = "@end@";

-    private final List<android.os.Handler> mUiHandlers = new ArrayList<android.os.Handler>();

-    private final HandlerType mHandlerType;

-    private final Thread mEventThread;

-    private int mPort;

-

-    /**

-     * The type of action that this handler manages.

-     */

-    public enum HandlerType {

-        /** A handler to send multitouch events from the device to the emulator and display

-         *  the emulator screen on the device. */

-        MultiTouch,

-        /** A handler to send sensor events from the device to the emulaotr. */

-        Sensor

-    }

-

-    /**

-     * Initializes a new base handler.

-     *

-     * @param type A non-null {@link HandlerType} value.

-     * @param port A non-null communication port number.

-     */

-    protected BaseHandler(HandlerType type, int port) {

-        mHandlerType = type;

-        mPort = port;

-

-        final String name = type.toString();

-        mEventThread = new Thread(new Runnable() {

-            @Override

-            public void run() {

-                if (DEBUG) Log.d(TAG, "EventThread.started-" + name);

-                while(mRunEventQueue) {

-                    try {

-                        String msg = mEventQueue.take();

-                        if (msg != null && mConnection != null && !msg.equals(EVENT_QUEUE_END)) {

-                            mConnection.sendNotification(msg);

-                            mEventCount.incrementAndGet();

-                        }

-                    } catch (InterruptedException e) {

-                        Log.e(TAG, "EventThread-" + name, e);

-                    }

-                }

-                if (DEBUG) Log.d(TAG, "EventThread.terminate-" + name);

-            }

-        }, "EventThread-" + name);

-    }

-

-    /**

-     * Returns the type of this handler, as given to the constructor.

-     *

-     * @return One of the {@link HandlerType} values.

-     */

-    public HandlerType getType() {

-        return mHandlerType;

-    }

-

-    /**

-     * Returns he communication port used by this handler to communicate with the emulator,

-     * as given to the constructor.

-     * <p/>

-     * Note that right now we have 2 handlers that each use their own port. The goal is

-     * to move to a single-connection mechanism where all the handlers' data will be

-     * multiplexed on top of a single {@link EmulatorConnection}.

-     *

-     * @return A non-null port value.

-     */

-    public int getPort() {

-        return mPort;

-    }

-

-    /**

-     * Returns the last {@link EmulatorConnection} passed to

-     * {@link #onStart(EmulatorConnection, Context)}.

-     * It becomes null when {@link #onStop()} is called.

-     *

-     * @return The current {@link EmulatorConnection}.

-     */

-    public EmulatorConnection getConnection() {

-        return mConnection;

-    }

-

-    /**

-     * Called once the {@link EmulatorConnection} has been successfully initialized.

-     * <p/>

-     * Note that this will <em>not</em> be called if the {@link EmulatorConnection}

-     * fails to bind to the underlying socket.

-     * <p/>

-     * This base implementation keeps tracks of the connection.

-     *

-     * @param connection The connection that has just been created.

-     *   A handler might want to use this to send data to the emulator via

-     *   {@link EmulatorConnection#sendNotification(String)}. However handlers

-     *   need to be particularly careful in <em>not</em> sending network data

-     *   from the main UI thread.

-     * @param context The controller service' context.

-     * @see #getConnection()

-     */

-    public void onStart(EmulatorConnection connection, Context context) {

-        assert connection != null;

-        mConnection = connection;

-        mRunEventQueue = true;

-        mEventThread.start();

-    }

-

-    /**

-     * Called once the {@link EmulatorConnection} is being disconnected.

-     * This nullifies the connection returned by {@link #getConnection()}.

-     */

-    public void onStop() {

-        // Stop the message queue

-        mConnection = null;

-        if (mRunEventQueue) {

-            mRunEventQueue = false;

-            mEventQueue.offer(EVENT_QUEUE_END);

-        }

-    }

-

-    public int getEventSentCount() {

-        return mEventCount.get();

-    }

-

-    /**

-     * Utility for handlers or activities to sends a string event to the emulator.

-     * This method is safe for the activity to call from any thread, including the UI thread.

-     *

-     * @param msg Event message. Must not be null.

-     */

-    public void sendEventToEmulator(String msg) {

-        try {

-            mEventQueue.put(msg);

-        } catch (InterruptedException e) {

-            Log.e(TAG, "EventQueue.put", e);

-        }

-    }

-

-    // ------------

-    // Interaction from the emulator connection towards the handler

-

-    /**

-     * Emulator query being forwarded to the handler.

-     *

-     * @see EmulatorListener#onEmulatorQuery(String, String)

-     */

-    public abstract String onEmulatorQuery(String query, String param);

-

-    /**

-     * Emulator blob query being forwarded to the handler.

-     *

-     * @see EmulatorListener#onEmulatorBlobQuery(byte[])

-     */

-    public abstract String onEmulatorBlobQuery(byte[] array);

-

-    // ------------

-    // Interaction from handler towards listening UI

-

-    /**

-     * Indicates any UI handler is currently registered with the handler.

-     * If no UI is displaying the handler's state, maybe the handler can skip UI related tasks.

-     *

-     * @return True if there's at least one UI handler registered.

-     */

-    public boolean hasUiHandler() {

-        return !mUiHandlers.isEmpty();

-    }

-

-    /**

-     * Registers a new UI handler.

-     *

-     * @param uiHandler A non-null UI handler to register.

-     *   Ignored if the UI handler is null or already registered.

-     */

-    public void addUiHandler(android.os.Handler uiHandler) {

-        assert uiHandler != null;

-        if (uiHandler != null) {

-            if (!mUiHandlers.contains(uiHandler)) {

-                mUiHandlers.add(uiHandler);

-            }

-        }

-    }

-

-    /**

-     * Unregisters an UI handler.

-     *

-     * @param uiHandler A non-null UI listener to unregister.

-     *   Ignored if the listener is null or already registered.

-     */

-    public void removeUiHandler(android.os.Handler uiHandler) {

-        assert uiHandler != null;

-        mUiHandlers.remove(uiHandler);

-    }

-

-    /**

-     * Protected method to be used by handlers to send an event to all UI handlers.

-     *

-     * @param event An integer event code with no specific parameters.

-     *   To be defined by the handler itself.

-     */

-    protected void notifyUiHandlers(int event) {

-        for (android.os.Handler uiHandler : mUiHandlers) {

-            uiHandler.sendEmptyMessage(event);

-        }

-    }

-

-    /**

-     * Protected method to be used by handlers to send an event to all UI handlers.

-     *

-     * @param msg An event with parameters. To be defined by the handler itself.

-     */

-    protected void notifyUiHandlers(Message msg) {

-        for (android.os.Handler uiHandler : mUiHandlers) {

-            uiHandler.sendMessage(msg);

-        }

-    }

-

-}

diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java
new file mode 100755
index 0000000..fc70ae1
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchChannel.java
@@ -0,0 +1,172 @@
+/*

+ * 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.tools.sdkcontroller.handlers;

+

+import android.graphics.Point;

+import android.os.Message;

+import android.util.Log;

+

+import com.android.tools.sdkcontroller.lib.Channel;

+import com.android.tools.sdkcontroller.lib.ProtocolConstants;

+import com.android.tools.sdkcontroller.service.ControllerService;

+

+import java.nio.ByteBuffer;

+

+/**

+ * Implements multi-touch emulation.

+ */

+public class MultiTouchChannel extends Channel {

+

+    @SuppressWarnings("hiding")

+    private static final String TAG = MultiTouchChannel.class.getSimpleName();

+    /**

+     * A new frame buffer has been received from the emulator.

+     * Parameter {@code obj} is a {@code byte[] array} containing the screen data.

+     */

+    public static final int EVENT_FRAME_BUFFER = 1;

+    /**

+     * A multi-touch "start" command has been received from the emulator.

+     * Parameter {@code obj} is the string parameter from the start command.

+     */

+    public static final int EVENT_MT_START = 2;

+    /**

+     * A multi-touch "stop" command has been received from the emulator. There

+     * is no {@code obj} parameter associated.

+     */

+    public static final int EVENT_MT_STOP = 3;

+

+    private static final Point mViewSize = new Point(0, 0);

+

+    /**

+     * Constructs MultiTouchChannel instance.

+     */

+    public MultiTouchChannel(ControllerService service) {

+        super(service, Channel.MULTITOUCH_CHANNEL);

+    }

+

+    /**

+     * Sets size of the display view for emulated screen updates.

+     *

+     * @param width View width in pixels.

+     * @param height View height in pixels.

+     */

+    public void setViewSize(int width, int height) {

+        mViewSize.set(width, height);

+    }

+

+    /*

+     * Channel abstract implementation.

+     */

+

+    /**

+     * This method is invoked when this channel is fully connected with its

+     * counterpart in the emulator.

+     */

+    @Override

+    public void onEmulatorConnected() {

+        if (hasUiHandler()) {

+            enable();

+            notifyUiHandlers(EVENT_MT_START);

+        }

+    }

+

+    /**

+     * This method is invoked when this channel loses connection with its

+     * counterpart in the emulator.

+     */

+    @Override

+    public void onEmulatorDisconnected() {

+        if (hasUiHandler()) {

+            disable();

+            notifyUiHandlers(EVENT_MT_STOP);

+        }

+    }

+

+    /**

+     * A message has been received from the emulator.

+     *

+     * @param msg_type Message type.

+     * @param msg_data Packet received from the emulator.

+     */

+    @Override

+    public void onEmulatorMessage(int msg_type, ByteBuffer msg_data) {

+        switch (msg_type) {

+            case ProtocolConstants.MT_FB_UPDATE:

+                Message msg = Message.obtain();

+                msg.what = EVENT_FRAME_BUFFER;

+                msg.obj = msg_data;

+                notifyUiHandlers(msg);

+                break;

+

+            default:

+                Log.e(TAG, "Unknown message type " + msg_type);

+        }

+    }

+

+    /**

+     * A query has been received from the emulator.

+     *

+     * @param query_id Identifies the query. This ID must be used when replying

+     *            to the query.

+     * @param query_type Query type.

+     * @param query_data Query data.

+     */

+    @Override

+    public void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data) {

+        Loge("Unexpected query " + query_type + " in multi-touch");

+        sendQueryResponse(query_id, (byte[]) null);

+    }

+

+    /**

+     * Registers a new UI handler.

+     *

+     * @param uiHandler A non-null UI handler to register. Ignored if the UI

+     *            handler is null or already registered.

+     */

+    @Override

+    public void addUiHandler(android.os.Handler uiHandler) {

+        final boolean first_handler = !hasUiHandler();

+        super.addUiHandler(uiHandler);

+        if (first_handler && isConnected()) {

+            enable();

+            notifyUiHandlers(EVENT_MT_START);

+        }

+    }

+

+    /**

+     * Unregisters an UI handler.

+     *

+     * @param uiHandler A non-null UI listener to unregister. Ignored if the

+     *            listener is null or already registered.

+     */

+    @Override

+    public void removeUiHandler(android.os.Handler uiHandler) {

+        super.removeUiHandler(uiHandler);

+        if (isConnected() && !hasUiHandler()) {

+            disable();

+        }

+    }

+

+    /***************************************************************************

+     * Logging wrappers

+     **************************************************************************/

+

+    private void Loge(String log) {

+        mService.addError(log);

+        Log.e(TAG, log);

+    }

+}

diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java
deleted file mode 100755
index 6f64485..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/MultiTouchHandler.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*

- * 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.tools.sdkcontroller.handlers;

-

-import android.content.Context;

-import android.graphics.Point;

-import android.os.Message;

-import android.util.Log;

-

-import com.android.tools.sdkcontroller.lib.EmulatorConnection;

-

-

-public class MultiTouchHandler extends BaseHandler {

-

-    @SuppressWarnings("hiding")

-    private static final String TAG = MultiTouchHandler.class.getSimpleName();

-    /**

-     * A new frame buffer has been received from the emulator.

-     * Parameter {@code obj} is a {@code byte[] array} containing the screen data.

-     */

-    public static final int EVENT_FRAME_BUFFER = 1;

-    /**

-     * A multi-touch "start" command has been received from the emulator.

-     * Parameter {@code obj} is the string parameter from the start command.

-     */

-    public static final int EVENT_MT_START = 2;

-    /**

-     * A multi-touch "stop" command has been received from the emulator.

-     * There is no {@code obj} parameter associated.

-     */

-    public static final int EVENT_MT_STOP = 3;

-

-    private static final Point mViewSize = new Point(0, 0);

-

-    public MultiTouchHandler() {

-        super(HandlerType.MultiTouch, EmulatorConnection.MULTITOUCH_PORT);

-    }

-

-    public void setViewSize(int width, int height) {

-        mViewSize.set(width, height);

-    }

-

-    @Override

-    public void onStart(EmulatorConnection connection, Context context) {

-        super.onStart(connection, context);

-    }

-

-    @Override

-    public void onStop() {

-        super.onStop();

-    }

-

-    /**

-     * Called when a query is received from the emulator. NOTE: This method is

-     * called from the I/O loop.

-     *

-     * @param query Name of the query received from the emulator. The allowed

-     *            queries are: - 'start' - Starts delivering touch screen events

-     *            to the emulator. - 'stop' - Stops delivering touch screen

-     *            events to the emulator.

-     * @param param Query parameters.

-     * @return Zero-terminated reply string. String must be formatted as such:

-     *         "ok|ko[:reply data]"

-     */

-    @Override

-    public String onEmulatorQuery(String query, String param) {

-        if (query.contentEquals("start")) {

-            Message msg = Message.obtain();

-            msg.what = EVENT_MT_START;

-            msg.obj = param;

-            notifyUiHandlers(msg);

-            return "ok:" + mViewSize.x + "x" + mViewSize.y + "\0";

-

-        } else if (query.contentEquals("stop")) {

-            notifyUiHandlers(EVENT_MT_STOP);

-            return "ok\0";

-

-        } else {

-            Log.e(TAG, "Unknown query " + query + "(" + param + ")");

-            return "ko:Unknown query\0";

-        }

-    }

-

-    /**

-     * Called when a BLOB query is received from the emulator.

-     * <p/>

-     * This query is used to deliver framebuffer updates in the emulator. The

-     * blob contains an update header, followed by the bitmap containing updated

-     * rectangle. The header is defined as MTFrameHeader structure in

-     * external/qemu/android/multitouch-port.h

-     * <p/>

-     * NOTE: This method is called from the I/O loop, so all communication with

-     * the emulator will be "on hold" until this method returns.

-     *

-     * @param array contains BLOB data for the query.

-     * @return Empty string: this query doesn't require any response.

-     */

-    @Override

-    public String onEmulatorBlobQuery(byte[] array) {

-        Message msg = Message.obtain();

-        msg.what = EVENT_FRAME_BUFFER;

-        msg.obj = array;

-        notifyUiHandlers(msg);

-        return "";

-    }

-

-}

diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
similarity index 66%
rename from apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java
rename to apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
index 498b86d..d1ab836 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorsHandler.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
@@ -16,6 +16,7 @@
 

 package com.android.tools.sdkcontroller.handlers;

 

+import java.nio.ByteBuffer;

 import java.util.ArrayList;

 import java.util.List;

 

@@ -28,13 +29,17 @@
 import android.os.SystemClock;

 import android.util.Log;

 

-import com.android.tools.sdkcontroller.lib.EmulatorConnection;

+import com.android.tools.sdkcontroller.lib.Channel;

+import com.android.tools.sdkcontroller.lib.ProtocolConstants;

+import com.android.tools.sdkcontroller.service.ControllerService;

 

-

-public class SensorsHandler extends BaseHandler {

+/**

+ * Implements sensors emulation.

+ */

+public class SensorChannel extends Channel {

 

     @SuppressWarnings("hiding")

-    private static String TAG = SensorsHandler.class.getSimpleName();

+    private static String TAG = SensorChannel.class.getSimpleName();

     @SuppressWarnings("hiding")

     private static boolean DEBUG = false;

     /**

@@ -46,31 +51,63 @@
      * Default value should match res/values/strings.xml > sensors_default_sample_rate.

      */

     private long mUpdateTargetMs = 1000/20; // 20 fps in milliseconds

+    /** Accumulates average update frequency. */

     private long mGlobalAvgUpdateMs = 0;

 

-

-    /**

-     * Sensor "enabled by emulator" state has changed.

-     * Parameter {@code obj} is the {@link MonitoredSensor}.

-     */

-    public static final int SENSOR_STATE_CHANGED = 1;

-    /**

-     * Sensor display value has changed.

-     * Parameter {@code obj} is the {@link MonitoredSensor}.

-     */

-    public static final int SENSOR_DISPLAY_MODIFIED = 2;

-

     /** Array containing monitored sensors. */

     private final List<MonitoredSensor> mSensors = new ArrayList<MonitoredSensor>();

+    /** Sensor manager. */

     private SensorManager mSenMan;

 

-    public SensorsHandler() {

-        super(HandlerType.Sensor, EmulatorConnection.SENSORS_PORT);

+    /*

+     * Messages exchanged with the UI.

+     */

+

+    /**

+     * Sensor "enabled by emulator" state has changed. Parameter {@code obj} is

+     * the {@link MonitoredSensor}.

+     */

+    public static final int SENSOR_STATE_CHANGED = 1;

+    /**

+     * Sensor display value has changed. Parameter {@code obj} is the

+     * {@link MonitoredSensor}.

+     */

+    public static final int SENSOR_DISPLAY_MODIFIED = 2;

+

+    /**

+     * Constructs SensorChannel instance.

+     *

+     * @param service Service context.

+     */

+    public SensorChannel(ControllerService service) {

+        super(service, Channel.SENSOR_CHANNEL);

+        mSenMan = (SensorManager) service.getSystemService(Context.SENSOR_SERVICE);

+        // Iterate through the available sensors, adding them to the array.

+        List<Sensor> sensors = mSenMan.getSensorList(Sensor.TYPE_ALL);

+        int cur_index = 0;

+        for (int n = 0; n < sensors.size(); n++) {

+            Sensor avail_sensor = sensors.get(n);

+

+            // There can be multiple sensors of the same type. We need only one.

+            if (!isSensorTypeAlreadyMonitored(avail_sensor.getType())) {

+                // The first sensor we've got for the given type is not

+                // necessarily the right one. So, use the default sensor

+                // for the given type.

+                Sensor def_sens = mSenMan.getDefaultSensor(avail_sensor.getType());

+                MonitoredSensor to_add = new MonitoredSensor(def_sens);

+                cur_index++;

+                mSensors.add(to_add);

+                if (DEBUG)

+                    Log.d(TAG, String.format(

+                            "Monitoring sensor #%02d: Name = '%s', Type = 0x%x",

+                            cur_index, def_sens.getName(), def_sens.getType()));

+            }

+        }

     }

 

     /**

      * Returns the list of sensors found on the device.

-     * The list is computed once by {@link #onStart(EmulatorConnection, Context)}.

+     * The list is computed once by {@link #SensorChannel(ControllerService)}.

      *

      * @return A non-null possibly-empty list of sensors.

      */

@@ -100,188 +137,146 @@
         return mGlobalAvgUpdateMs;

     }

 

+    /*

+     * Channel abstract implementation.

+     */

+

+    /**

+     * This method is invoked when this channel is fully connected with its

+     * counterpart in the emulator.

+     */

     @Override

-    public void onStart(EmulatorConnection connection, Context context) {

-        super.onStart(connection, context);

-

-        // Iterate through the available sensors, adding them to the array.

-        SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);

-        mSenMan = sm;

-        List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ALL);

-        int cur_index = 0;

-        for (int n = 0; n < sensors.size(); n++) {

-            Sensor avail_sensor = sensors.get(n);

-

-            // There can be multiple sensors of the same type. We need only one.

-            if (!isSensorTypeAlreadyMonitored(avail_sensor.getType())) {

-                // The first sensor we've got for the given type is not

-                // necessarily the right one. So, use the default sensor

-                // for the given type.

-                Sensor def_sens = sm.getDefaultSensor(avail_sensor.getType());

-                MonitoredSensor to_add = new MonitoredSensor(def_sens);

-                cur_index++;

-                mSensors.add(to_add);

-                if (DEBUG) Log.d(TAG, String.format(

-                        "Monitoring sensor #%02d: Name = '%s', Type = 0x%x",

-                        cur_index, def_sens.getName(), def_sens.getType()));

-            }

-        }

+    public void onEmulatorConnected() {

+        // Emulation is now possible. Note though that it will start only after

+        // emulator tells us so with SENSORS_START command.

+        enable();

     }

 

+    /**

+     * This method is invoked when this channel loses connection with its

+     * counterpart in the emulator.

+     */

     @Override

-    public void onStop() {

+    public void onEmulatorDisconnected() {

+        // Stop sensor event callbacks.

         stopSensors();

-        super.onStop();

     }

 

     /**

-     * Called when a query is received from the emulator. NOTE: This method is

-     * called from the I/O loop.

+     * A query has been received from the emulator.

      *

-     * @param query Name of the query received from the emulator. The allowed

-     *            queries are: 'list' - Lists sensors that are monitored by this

-     *            application. The application replies to this command with a

-     *            string: 'List:<name1>\n<name2>\n...<nameN>\n\0" 'start' -

-     *            Starts monitoring sensors. There is no reply for this command.

-     *            'stop' - Stops monitoring sensors. There is no reply for this

-     *            command. 'enable:<sensor|all> - Enables notifications for a

-     *            sensor / all sensors. 'disable:<sensor|all> - Disables

-     *            notifications for a sensor / all sensors.

-     * @param param Query parameters.

-     * @return Zero-terminated reply string. String must be formatted as such:

-     *         "ok|ko[:reply data]"

+     * @param query_id Identifies the query. This ID should be used when

+     *            replying to the query.

+     * @param query_type Query type.

+     * @param query_data Query data.

      */

     @Override

-    public String onEmulatorQuery(String query, String param) {

-        if (query.contentEquals("list")) {

-            return onQueryList();

-        } else if (query.contentEquals("start")) {

-            return onQueryStart();

-        } else if (query.contentEquals("stop")) {

-            return onQueryStop();

-        } else if (query.contentEquals("enable")) {

-            return onQueryEnable(param);

-        } else if (query.contentEquals("disable")) {

-            return onQueryDisable(param);

-        } else {

-            Log.e(TAG, "Unknown query " + query + "(" + param + ")");

-            return "ko:Query is unknown\0";

+    public void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data) {

+        switch (query_type) {

+            case ProtocolConstants.SENSORS_QUERY_LIST:

+                // Preallocate large response buffer.

+                ByteBuffer resp = ByteBuffer.allocate(1024);

+                resp.order(getEndian());

+                // Iterate through the list of monitored sensors, dumping them

+                // into the response buffer.

+                for (MonitoredSensor sensor : mSensors) {

+                    // Entry for each sensor must contain:

+                    // - an integer for its ID

+                    // - a zero-terminated emulator-friendly name.

+                    final byte[] name = sensor.getEmulatorFriendlyName().getBytes();

+                    final int required_size = 4 + name.length + 1;

+                    resp = ExpandIf(resp, required_size);

+                    resp.putInt(sensor.getType());

+                    resp.put(name);

+                    resp.put((byte) 0);

+                }

+                // Terminating entry contains single -1 integer.

+                resp = ExpandIf(resp, 4);

+                resp.putInt(-1);

+                sendQueryResponse(query_id, resp);

+                return;

+

+            default:

+                Loge("Unknown query " + query_type);

+                return;

         }

     }

 

     /**

-     * Called when a BLOB query is received from the emulator. NOTE: This method

-     * is called from the I/O loop, so all communication with the emulator will

-     * be "on hold" until this method returns.

+     * A message has been received from the emulator.

      *

-     * @param array contains BLOB data for the query.

-     * @return Zero-terminated reply string. String must be formatted as such:

-     *         "ok|ko[:reply data]"

+     * @param msg_type Message type.

+     * @param msg_data Packet received from the emulator.

      */

     @Override

-    public String onEmulatorBlobQuery(byte[] array) {

-        return "ko:Unexpected\0";

-    }

-

-    /***************************************************************************

-     * Query handlers

-     **************************************************************************/

-

-    /**

-     * Handles 'list' query.

-     *

-     * @return List of emulator-friendly names for sensors that are available on

-     *         the device.

-     */

-    private String onQueryList() {

-        // List monitored sensors.

-        String list = "ok:";

-        for (MonitoredSensor sensor : mSensors) {

-            list += sensor.getEmulatorFriendlyName();

-            list += "\n";

+    public void onEmulatorMessage(int msg_type, ByteBuffer msg_data) {

+        switch (msg_type) {

+            case ProtocolConstants.SENSORS_START:

+                Log.v(TAG, "Starting sensors emulation.");

+                startSensors();

+                break;

+            case ProtocolConstants.SENSORS_STOP:

+                Log.v(TAG, "Stopping sensors emulation.");

+                stopSensors();

+                break;

+            case ProtocolConstants.SENSORS_ENABLE:

+                String enable_name = new String(msg_data.array());

+                Log.v(TAG, "Enabling sensor: " + enable_name);

+                onEnableSensor(enable_name);

+                break;

+            case ProtocolConstants.SENSORS_DISABLE:

+                String disable_name = new String(msg_data.array());

+                Log.v(TAG, "Disabling sensor: " + disable_name);

+                onDisableSensor(disable_name);

+                break;

+            default:

+                Loge("Unknown message type " + msg_type);

+                break;

         }

-        list += '\0'; // Response must end with zero-terminator.

-        return list;

     }

 

     /**

-     * Handles 'start' query.

+     * Handles 'enable' message.

      *

-     * @return Empty string. This is a "command" query that doesn't assume any

-     *         response.

+     * @param name Emulator-friendly name of a sensor to enable, or "all" to

+     *            enable all sensors.

      */

-    private String onQueryStart() {

-        startSensors();

-        return "ok\0";

-    }

-

-    /**

-     * Handles 'stop' query.

-     *

-     * @return Empty string. This is a "command" query that doesn't assume any

-     *         response.

-     */

-    private String onQueryStop() {

-        stopSensors();

-        return "ok\0";

-    }

-

-    /**

-     * Handles 'enable' query.

-     *

-     * @param param Sensor selector: - all Enables all available sensors, or -

-     *            <name> Emulator-friendly name of a sensor to enable.

-     * @return "ok" / "ko": success / failure.

-     */

-    private String onQueryEnable(String param) {

-        if (param.contentEquals("all")) {

+    private void onEnableSensor(String name) {

+        if (name.contentEquals("all")) {

             // Enable all sensors.

             for (MonitoredSensor sensor : mSensors) {

                 sensor.enableSensor();

             }

-            return "ok\0";

-        }

-

-        // Lookup sensor by emulator-friendly name.

-        MonitoredSensor sensor = getSensorByEFN(param);

-        if (sensor != null) {

-            sensor.enableSensor();

-            return "ok\0";

         } else {

-            return "ko:Sensor not found\0";

+            // Lookup sensor by emulator-friendly name.

+            final MonitoredSensor sensor = getSensorByEFN(name);

+            if (sensor != null) {

+                sensor.enableSensor();

+            }

         }

     }

 

     /**

-     * Handles 'disable' query.

+     * Handles 'disable' message.

      *

-     * @param param Sensor selector: - all Disables all available sensors, or -

-     *            <name> Emulator-friendly name of a sensor to disable.

-     * @return "ok" / "ko": success / failure.

+     * @param name Emulator-friendly name of a sensor to disable, or "all" to

+     *            disable all sensors.

      */

-    private String onQueryDisable(String param) {

-        if (param.contentEquals("all")) {

+    private void onDisableSensor(String name) {

+        if (name.contentEquals("all")) {

             // Disable all sensors.

             for (MonitoredSensor sensor : mSensors) {

                 sensor.disableSensor();

             }

-            return "ok\0";

-        }

-

-        // Lookup sensor by emulator-friendly name.

-        MonitoredSensor sensor = getSensorByEFN(param);

-        if (sensor != null) {

-            sensor.disableSensor();

-            return "ok\0";

         } else {

-            return "ko:Sensor not found\0";

+            // Lookup sensor by emulator-friendly name.

+            MonitoredSensor sensor = getSensorByEFN(name);

+            if (sensor != null) {

+                sensor.disableSensor();

+            }

         }

     }

 

-    /***************************************************************************

-     * Internals

-     **************************************************************************/

-

     /**

      * Start listening to all monitored sensors.

      */

@@ -300,6 +295,10 @@
         }

     }

 

+    /***************************************************************************

+     * Internals

+     **************************************************************************/

+

     /**

      * Checks if a sensor for the given type is already monitored.

      *

@@ -351,8 +350,7 @@
         private String mEmulatorFriendlyName;

         /** Formats string to show in the TextView. */

         private String mTextFmt;

-        private int mExpectedLen;

-        private int mNbValues = 0;

+        /** Sensor values. */

         private float[] mValues = new float[3];

         /**

          * Enabled state. This state is controlled by the emulator, that

@@ -362,6 +360,7 @@
         private boolean mEnabledByEmulator = false;

         /** User-controlled enabled state. */

         private boolean mEnabledByUser = true;

+        /** Sensor event listener for this sensor. */

         private final OurSensorEventListener mListener = new OurSensorEventListener();

 

         /**

@@ -377,124 +376,112 @@
             // we can't really use sensor.getName() here, since the value it

             // returns (although resembles the purpose) is a bit vaguer than it

             // should be. Also choose an appropriate format for the strings that

-            // display sensor's value, and strings that are sent to the

-            // emulator.

+            // display sensor's value.

             switch (sensor.getType()) {

                 case Sensor.TYPE_ACCELEROMETER:

                     mUiName = "Accelerometer";

-                    // 3 floats.

                     mTextFmt = "%+.2f %+.2f %+.2f";

                     mEmulatorFriendlyName = "acceleration";

-                    mExpectedLen = 3;

                     break;

                 case 9: // Sensor.TYPE_GRAVITY is missing in API 7

-                    // 3 floats.

                     mUiName = "Gravity";

                     mTextFmt = "%+.2f %+.2f %+.2f";

                     mEmulatorFriendlyName = "gravity";

-                    mExpectedLen = 3;

                     break;

                 case Sensor.TYPE_GYROSCOPE:

                     mUiName = "Gyroscope";

-                    // 3 floats.

                     mTextFmt = "%+.2f %+.2f %+.2f";

                     mEmulatorFriendlyName = "gyroscope";

-                    mExpectedLen = 3;

                     break;

                 case Sensor.TYPE_LIGHT:

                     mUiName = "Light";

-                    // 1 integer.

                     mTextFmt = "%.0f";

                     mEmulatorFriendlyName = "light";

-                    mExpectedLen = 1;

                     break;

                 case 10: // Sensor.TYPE_LINEAR_ACCELERATION is missing in API 7

                     mUiName = "Linear acceleration";

-                    // 3 floats.

                     mTextFmt = "%+.2f %+.2f %+.2f";

                     mEmulatorFriendlyName = "linear-acceleration";

-                    mExpectedLen = 3;

                     break;

                 case Sensor.TYPE_MAGNETIC_FIELD:

                     mUiName = "Magnetic field";

-                    // 3 floats.

                     mTextFmt = "%+.2f %+.2f %+.2f";

                     mEmulatorFriendlyName = "magnetic-field";

-                    mExpectedLen = 3;

                     break;

                 case Sensor.TYPE_ORIENTATION:

                     mUiName = "Orientation";

-                    // 3 integers.

                     mTextFmt = "%+03.0f %+03.0f %+03.0f";

                     mEmulatorFriendlyName = "orientation";

-                    mExpectedLen = 3;

                     break;

                 case Sensor.TYPE_PRESSURE:

                     mUiName = "Pressure";

-                    // 1 integer.

                     mTextFmt = "%.0f";

                     mEmulatorFriendlyName = "pressure";

-                    mExpectedLen = 1;

                     break;

                 case Sensor.TYPE_PROXIMITY:

                     mUiName = "Proximity";

-                    // 1 integer.

                     mTextFmt = "%.0f";

                     mEmulatorFriendlyName = "proximity";

-                    mExpectedLen = 1;

                     break;

                 case 11: // Sensor.TYPE_ROTATION_VECTOR is missing in API 7

                     mUiName = "Rotation";

-                    // 3 floats.

                     mTextFmt = "%+.2f %+.2f %+.2f";

                     mEmulatorFriendlyName = "rotation";

-                    mExpectedLen = 3;

                     break;

                 case Sensor.TYPE_TEMPERATURE:

                     mUiName = "Temperature";

-                    // 1 integer.

                     mTextFmt = "%.0f";

                     mEmulatorFriendlyName = "temperature";

-                    mExpectedLen = 1;

                     break;

                 default:

                     mUiName = "<Unknown>";

                     mTextFmt = "N/A";

                     mEmulatorFriendlyName = "unknown";

-                    mExpectedLen = 0;

-                    if (DEBUG) Log.e(TAG, "Unknown sensor type " + mSensor.getType() +

+                    if (DEBUG) Loge("Unknown sensor type " + mSensor.getType() +

                             " for sensor " + mSensor.getName());

                     break;

             }

         }

 

+        /**

+         * Get name for this sensor to display.

+         *

+         * @return Name for this sensor to display.

+         */

         public String getUiName() {

             return mUiName;

         }

 

+        /**

+         * Gets current sensor value to display.

+         *

+         * @return Current sensor value to display.

+         */

         public String getValue() {

-            String val = mValue;

-

-            if (val == null) {

-                int len = mNbValues;

+            if (mValue == null) {

                 float[] values = mValues;

-                if (len == 3) {

-                    val = String.format(mTextFmt, values[0], values[1],values[2]);

-                } else if (len == 2) {

-                    val = String.format(mTextFmt, values[0], values[1]);

-                } else if (len == 1) {

-                    val = String.format(mTextFmt, values[0]);

-                }

-                mValue = val;

+                mValue = String.format(mTextFmt, values[0], values[1], values[2]);

             }

-

-            return val == null ? "??" : val;

+            return mValue == null ? "??" : mValue;

         }

 

+        /**

+         * Checks if monitoring of this this sensor has been enabled by

+         * emulator.

+         *

+         * @return true if monitoring of this this sensor has been enabled by

+         *         emulator, or false if emulator didn't enable this sensor.

+         */

         public boolean isEnabledByEmulator() {

             return mEnabledByEmulator;

         }

 

+        /**

+         * Checks if monitoring of this this sensor has been enabled by user.

+         *

+         * @return true if monitoring of this this sensor has been enabled by

+         *         user, or false if user didn't enable this sensor.

+         */

         public boolean isEnabledByUser() {

             return mEnabledByUser;

         }

@@ -513,8 +500,6 @@
             }

         }

 

-        // ---------

-

         /**

          * Gets sensor type.

          *

@@ -560,7 +545,6 @@
         private void enableSensor() {

             if (DEBUG) Log.d(TAG, ">>> Sensor " + getEmulatorFriendlyName() + " is enabled.");

             mEnabledByEmulator = true;

-            mNbValues = 0;

             mValue = null;

 

             Message msg = Message.obtain();

@@ -586,10 +570,11 @@
 

         private class OurSensorEventListener implements SensorEventListener {

             /** Last update's time-stamp in local thread millisecond time. */

-            private long mLastUpdateTS;

+            private long mLastUpdateTS = 0;

             /** Last display update time-stamp. */

-            private long mLastDisplayTS;

-            private final StringBuilder mTempStr = new StringBuilder();

+            private long mLastDisplayTS = 0;

+            /** Preallocated buffer for change notification message. */

+            private final ByteBuffer mChangeMsg = ByteBuffer.allocate(64);

 

             /**

              * Handles "sensor changed" event.

@@ -597,7 +582,7 @@
              */

             @Override

             public void onSensorChanged(SensorEvent event) {

-                long now = SystemClock.currentThreadTimeMillis();

+                long now = SystemClock.elapsedRealtime();

 

                 long deltaMs = 0;

                 if (mLastUpdateTS != 0) {

@@ -608,31 +593,21 @@
                     }

                 }

 

-                // Format message that will be sent to the emulator.

+                // Format and post message for the emulator.

                 float[] values = event.values;

                 final int len = values.length;

 

-                // A 3printfs with 3 * %g takes around 9-15 ms on an ADP2, or 3-4 ms on a GN.

-                // However doing 3 * StringBuilder.append(float) takes < ~1 ms on ADP2.

-                StringBuilder sb = mTempStr;

-                sb.setLength(0);

-                sb.append(mEmulatorFriendlyName);

-

-                if (len != mExpectedLen) {

-                    Log.e(TAG, "Unexpected number of values " + len

-                            + " in onSensorChanged for sensor " + mSensor.getName());

-                    return;

-                } else {

-                    sb.append(':').append(values[0]);

-                    if (len > 1) {

-                        sb.append(':').append(values[1]);

-                        if (len > 2) {

-                            sb.append(':').append(values[2]);

-                        }

+                mChangeMsg.order(getEndian());

+                mChangeMsg.position(0);

+                mChangeMsg.putInt(getType());

+                mChangeMsg.putFloat(values[0]);

+                if (len > 1) {

+                    mChangeMsg.putFloat(values[1]);

+                    if (len > 2) {

+                        mChangeMsg.putFloat(values[2]);

                     }

                 }

-                sb.append('\0');

-                sendEventToEmulator(sb.toString());

+                postMessage(ProtocolConstants.SENSORS_SENSOR_EVENT, mChangeMsg);

 

                 // Computes average update time for this sensor and average globally.

                 if (mLastUpdateTS != 0) {

@@ -648,14 +623,13 @@
                 if (hasUiHandler()) {

                     if (mLastDisplayTS != 0) {

                         long uiDeltaMs = now - mLastDisplayTS;

-                        if (uiDeltaMs < 1000/4 /*4fps in ms*/) {

+                        if (uiDeltaMs < 1000 / 4 /* 4fps in ms */) {

                             // Skip this UI update

                             return;

                         }

                     }

                     mLastDisplayTS = now;

 

-                    mNbValues = len;

                     mValues[0] = values[0];

                     if (len > 1) {

                         mValues[1] = values[1];

@@ -672,7 +646,7 @@
                 }

 

                 if (DEBUG) {

-                    long now2 = SystemClock.currentThreadTimeMillis();

+                    long now2 = SystemClock.elapsedRealtime();

                     long processingTimeMs = now2 - now;

                     Log.d(TAG, String.format("glob %d - local %d > target %d - processing %d -- %s",

                             mGlobalAvgUpdateMs, deltaMs, mUpdateTargetMs, processingTimeMs,

@@ -681,8 +655,8 @@
             }

 

             /**

-             * Handles "sensor accuracy changed" event. This is an implementation of

-             * the SensorEventListener interface.

+             * Handles "sensor accuracy changed" event.

+             * This is an implementation of the SensorEventListener interface.

              */

             @Override

             public void onAccuracyChanged(Sensor sensor, int accuracy) {

@@ -690,4 +664,12 @@
         }

     } // MonitoredSensor

 

+    /***************************************************************************

+     * Logging wrappers

+     **************************************************************************/

+

+    private void Loge(String log) {

+        mService.addError(log);

+        Log.e(TAG, log);

+    }

 }

diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java
new file mode 100644
index 0000000..639f4cf
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Channel.java
@@ -0,0 +1,795 @@
+/*
+ * 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.tools.sdkcontroller.lib;
+
+import android.os.Message;
+import android.util.Log;
+
+import com.android.tools.sdkcontroller.service.ControllerService;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Encapsulates basics of a connection with the emulator.
+ * This class must be used as a base class for all the channelss that provide
+ * particular type of emulation (such as sensors, multi-touch, etc.)
+ * <p/>
+ * Essentially, Channel is an implementation of a particular emulated functionality,
+ * that defines logical format of the data transferred between the emulator and
+ * SDK controller. For instance, "sensors" is a channel that emulates sensors,
+ * and transfers sensor value changes from the device to the emulator. "Multi-touch"
+ * is a channel that supports multi-touch emulation, and transfers multi-touch
+ * events to the emulator, while receiving frame buffer updates from the emulator.
+ * <p/>
+ * Besides connection with the emulator, each channel may contain one or more UI
+ * components associated with it. This class provides some basics for UI support,
+ * including:
+ * <p/>
+ * - Providing a way to register / unregister a UI component with the channel.
+ * <p/>
+ * - Implementing posting of messages to emulator in opposite to direct message
+ * sent. This is due to requirement that UI threads are prohibited from doing
+ * network I/O.
+ */
+public abstract class Channel {
+
+    /**
+     * Encapsulates a message posted to be sent to the emulator from a worker
+     * thread. This class is used to describe a message that is posted in UI
+     * thread, and then picked up in the worker thread.
+     */
+    private class SdkControllerMessage {
+        /** Message type. */
+        private int mMessageType;
+        /** Message data (can be null). */
+        private byte[] mMessage;
+        /** Message data size */
+        private int mMessageSize;
+
+        /**
+         * Construct message from an array.
+         *
+         * @param type Message type.
+         * @param message Message data. Message data size is defined by size of
+         *            the array.
+         */
+        public SdkControllerMessage(int type, byte[] message) {
+            mMessageType = type;
+            mMessage = message;
+            mMessageSize = (message != null) ? message.length : 0;
+        }
+
+        /**
+         * Construct message from a ByteBuffer.
+         *
+         * @param type Message type.
+         * @param message Message data. Message data size is defined by
+         *            position() property of the ByteBuffer.
+         */
+        public SdkControllerMessage(int type, ByteBuffer message) {
+            mMessageType = type;
+            if (message != null) {
+                mMessage = message.array();
+                mMessageSize = message.position();
+            } else {
+                mMessage = null;
+                mMessageSize = 0;
+            }
+        }
+
+        /**
+         * Gets message type.
+
+         *
+         * @return Message type.
+         */
+        public int getMessageType() {
+            return mMessageType;
+        }
+
+        /**
+         * Gets message buffer.
+         *
+         * @return Message buffer.
+         */
+        public byte[] getMessage() {
+            return mMessage;
+        }
+
+        /**
+         * Gets message buffer size.
+         *
+         * @return Message buffer size.
+         */
+        public int getMessageSize() {
+            return mMessageSize;
+        }
+    } // SdkControllerMessage
+
+    /*
+     * Names for currently implemented SDK controller channels.
+     */
+
+    /** Name for a channel that handles sensors emulation */
+    public static final String SENSOR_CHANNEL = "sensors";
+    /** Name for a channel that handles multi-touch emulation */
+    public static final String MULTITOUCH_CHANNEL = "multi-touch";
+
+    /*
+     * Types of messages internally used by Channel class.
+     */
+
+    /** Service-side emulator is connected. */
+    private static final int MSG_CONNECTED = -1;
+    /** Service-side emulator is disconnected. */
+    private static final int MSG_DISCONNECTED = -2;
+    /** Service-side emulator is enabled. */
+    private static final int MSG_ENABLED = -3;
+    /** Service-side emulator is disabled. */
+    private static final int MSG_DISABLED = -4;
+
+    /** Tag for logging messages. */
+    private static final String TAG = "SdkControllerChannel";
+    /** Controls debug log. */
+    private static final boolean DEBUG = false;
+
+    /** Service that has created this object. */
+    protected ControllerService mService;
+
+    /*
+     * Socket stuff.
+     */
+
+    /** Socket to use to to communicate with the emulator. */
+    private Socket mSocket = null;
+    /** Channel name ("sensors", "multi-touch", etc.) */
+    private String mChannelName;
+    /** Endianness of data transferred in this channel. */
+    private ByteOrder mEndian;
+
+    /*
+     * Message posting support.
+     */
+
+    /** Total number of messages posted in this channel */
+    private final AtomicInteger mMsgCount = new AtomicInteger(0);
+    /** Flags whether or not message thread is running. */
+    private volatile boolean mRunMsgQueue = true;
+    /** Queue of messages pending transmission. */
+    private final BlockingQueue<SdkControllerMessage>
+            mMsgQueue = new LinkedBlockingQueue<SdkControllerMessage>();
+    /** Message thread */
+    private final Thread mMsgThread;
+
+    /*
+     * UI support.
+     */
+
+    /** Lists UI handlers attached to this channel. */
+    private final List<android.os.Handler> mUiHandlers = new ArrayList<android.os.Handler>();
+
+    /*
+     * Abstract methods.
+     */
+
+    /**
+     * This method is invoked when this channel is fully connected with its
+     * counterpart in the emulator.
+     */
+    public abstract void onEmulatorConnected();
+
+    /**
+     * This method is invoked when this channel loses connection with its
+     * counterpart in the emulator.
+     */
+    public abstract void onEmulatorDisconnected();
+
+    /**
+     * A message has been received from the emulator.
+     *
+     * @param msg_type Message type.
+     * @param msg_data Message data. Message data size is defined by the length
+     *            of the array wrapped by the ByteBuffer.
+     */
+    public abstract void onEmulatorMessage(int msg_type, ByteBuffer msg_data);
+
+    /**
+     * A query has been received from the emulator.
+     *
+     * @param query_id Identifies the query. This ID must be used when replying
+     *            to the query.
+     * @param query_type Query type.
+     * @param query_data Query data. Query data size is defined by the length of
+     *            the array wrapped by the ByteBuffer.
+     */
+    public abstract void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data);
+
+    /*
+     * Channel implementation.
+     */
+
+    /**
+     * Constructs Channel instance.
+     *
+     * @param name Channel name.
+     */
+    public Channel(ControllerService service, String name) {
+        mService = service;
+        mChannelName = name;
+        // Start the worker thread for posted messages.
+        mMsgThread = new Thread(new Runnable() {
+                @Override
+            public void run() {
+                if (DEBUG) Log.d(TAG, "MsgThread.started-" + mChannelName);
+                while (mRunMsgQueue) {
+                    try {
+                        SdkControllerMessage msg = mMsgQueue.take();
+                        if (msg != null) {
+                            sendMessage(
+                                    msg.getMessageType(), msg.getMessage(), msg.getMessageSize());
+                            mMsgCount.incrementAndGet();
+                        }
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "MsgThread-" + mChannelName, e);
+                    }
+                }
+                if (DEBUG) Log.d(TAG, "MsgThread.terminate-" + mChannelName);
+            }
+        }, "MsgThread-" + name);
+        mMsgThread.start();
+        if (DEBUG) Log.d(TAG, "Channel is constructed for " + mChannelName);
+    }
+
+    /**
+     * Gets name for this channel.
+     *
+     * @return Emulator name.
+     */
+    public String getChannelName() {
+        return mChannelName;
+    }
+
+    /**
+     * Gets endianness for this channel.
+     *
+     * @return Channel endianness.
+     */
+    public ByteOrder getEndian() {
+        return mEndian;
+    }
+
+    /**
+     * Gets number of messages sent via postMessage method.
+     *
+     * @return Number of messages sent via postMessage method.
+     */
+    public int getMsgSentCount() {
+        return mMsgCount.get();
+    }
+
+    /**
+     * Checks if this channel is connected with the emulator.
+     *
+     * @return true if this channel is connected with the emulator, or false if it is
+     *         not connected.
+     */
+    public boolean isConnected() {
+        // Use local copy of the socket, ensuring it's not going to NULL while
+        // we're working with it. If it gets closed, while we're in the middle
+        // of data transfer - it's OK, since it will produce an exception, and
+        // the caller will gracefully handle it.
+        //
+        // Same technique is used everywhere in this class where mSocket member
+        // is touched.
+        Socket socket = mSocket;
+        return socket != null && socket.isConnected();
+    }
+
+    /**
+     * Establishes connection with the emulator. This method is called by Connection
+     * object when emulator successfully connects to this channel, or this channel
+     * gets registered, and there is a pending socket connection for it.
+     *
+     * @param socket Channel connection socket.
+     */
+    public void connect(Socket socket) {
+        mSocket = socket;
+        mEndian = socket.getEndian();
+        Logv("Channel " + mChannelName + " is now connected with the emulator.");
+        // Notify the emulator that connection is established.
+        sendMessage(MSG_CONNECTED, (byte[]) null);
+
+        // Let the derived class know that emulator is connected, and start the
+        // I/O loop in which we will receive data from the emulator. Note that
+        // we start the loop after onEmulatorConnected call, since we don't want
+        // to start dispatching messages before the derived class could set
+        // itself up for receiving them.
+        onEmulatorConnected();
+        new Thread(new Runnable() {
+                @Override
+            public void run() {
+                runIOLooper();
+            }
+        }, "ChannelIoLoop").start();
+        mService.notifyStatusChanged();
+    }
+
+    /**
+     * Disconnects this channel from the emulator.
+     *
+     * @return true if this channel has been disconnected in this call, or false if
+     *         channel has been already disconnected when this method has been called.
+     */
+    public boolean disconnect() {
+        // This is the only place in this class where we will null the
+        // socket object. Since this method can be called concurrently from
+        // different threads, lets do this under the lock.
+        Socket socket;
+        synchronized (this) {
+            socket = mSocket;
+            mSocket = null;
+        }
+        if (socket != null) {
+            // Notify the emulator about channel disconnection before we close
+            // the communication socket.
+            try {
+                sendMessage(socket, MSG_DISCONNECTED, null, 0);
+            } catch (IOException e) {
+                // Ignore I/O exception at this point. We don't care about
+                // it, since the socket is being closed anyways.
+            }
+            // This will eventually stop I/O looper thread.
+            socket.close();
+            mService.notifyStatusChanged();
+        }
+        return socket != null;
+    }
+
+    /**
+     * Enables the emulation. Typically, this method is called for channels that are
+     * dependent on UI to handle the emulation. For instance, multi-touch emulation is
+     * disabled until at least one UI component is attached to the channel. So, for
+     * multi-touch emulation this method is called when UI gets attached to the channel.
+     */
+    public void enable() {
+        postMessage(MSG_ENABLED, (byte[]) null);
+        mService.notifyStatusChanged();
+    }
+
+    /**
+     * Disables the emulation. Just the opposite to enable(). For multi-touch this
+     * method is called when UI detaches from the channel.
+     */
+    public void disable() {
+        postMessage(MSG_DISABLED, (byte[]) null);
+        mService.notifyStatusChanged();
+    }
+
+    /**
+     * Sends message to the emulator.
+     *
+     * @param socket Socket to send the message to.
+     * @param msg_type Message type.
+     * @param msg Message data to send.
+     * @param len Byte size of message data.
+     * @throws IOException
+     */
+    private void sendMessage(Socket socket, int msg_type, byte[] msg, int len)
+            throws IOException {
+        // In async environment we must have message header and message data in
+        // one block to prevent messages from other threads getting between the
+        // header and the data. So, we can't sent header, and then the data. We
+        // must combine them in one data block instead.
+        ByteBuffer bb = ByteBuffer.allocate(ProtocolConstants.MESSAGE_HEADER_SIZE + len);
+        bb.order(mEndian);
+
+        // Initialize message header.
+        bb.putInt(ProtocolConstants.PACKET_SIGNATURE);
+        bb.putInt(ProtocolConstants.MESSAGE_HEADER_SIZE + len);
+        bb.putInt(ProtocolConstants.PACKET_TYPE_MESSAGE);
+        bb.putInt(msg_type);
+
+        // Save message data (if there is any).
+        if (len != 0) {
+            bb.put(msg, 0, len);
+        }
+
+        socket.send(bb.array());
+    }
+
+    /**
+     * Sends message to the emulator.
+     *
+     * @param msg_type Message type.
+     * @param msg Message data to send. Message size is defined by the size of
+     *            the array.
+     * @return true on success, or false if data transmission has failed.
+     */
+    public boolean sendMessage(int msg_type, byte[] msg, int msg_len) {
+        try {
+            Socket socket = mSocket;
+            if (socket != null) {
+                sendMessage(socket, msg_type, msg, msg_len);
+                return true;
+            } else {
+                Logw("sendMessage is called on disconnected Channel " + mChannelName);
+            }
+        } catch (IOException e) {
+            Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
+            onIoFailure();
+        }
+        return false;
+    }
+
+    /**
+     * Sends message to the emulator.
+     *
+     * @param msg_type Message type.
+     * @param msg Message data to send. Message size is defined by the size of
+     *            the array.
+     * @return true on success, or false if data transmission has failed.
+     */
+    public boolean sendMessage(int msg_type, byte[] msg) {
+        try {
+            Socket socket = mSocket;
+            if (socket != null) {
+                if (msg != null) {
+                    sendMessage(socket, msg_type, msg, msg.length);
+                } else {
+                    sendMessage(socket, msg_type, null, 0);
+                }
+                return true;
+            } else {
+                Logw("sendMessage is called on disconnected Channel " + mChannelName);
+            }
+        } catch (IOException e) {
+            Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
+            onIoFailure();
+        }
+        return false;
+    }
+
+    /**
+     * Sends message to the emulator.
+     *
+     * @param msg_type Message type.
+     * @param msg Message data to send. Message size is defined by the
+     *            position() property of the ByteBuffer.
+     * @return true on success, or false if data transmission has failed.
+     */
+    public boolean sendMessage(int msg_type, ByteBuffer msg) {
+        try {
+            Socket socket = mSocket;
+            if (socket != null) {
+                if (msg != null) {
+                    sendMessage(socket, msg_type, msg.array(), msg.position());
+                } else {
+                    sendMessage(socket, msg_type, null, 0);
+                }
+                return true;
+            } else {
+                Logw("sendMessage is called on disconnected Channel " + mChannelName);
+            }
+        } catch (IOException e) {
+            Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
+            onIoFailure();
+        }
+        return false;
+    }
+
+    /**
+     * Posts message to the emulator.
+     *
+     * @param msg_type Message type.
+     * @param msg Message data to post. Message size is defined by the size of
+     *            the array.
+     */
+    public void postMessage(int msg_type, byte[] msg) {
+        try {
+            mMsgQueue.put(new SdkControllerMessage(msg_type, msg));
+        } catch (InterruptedException e) {
+            Log.e(TAG, "mMessageQueue.put", e);
+        }
+    }
+
+    /**
+     * Posts message to the emulator.
+     *
+     * @param msg_type Message type.
+     * @param msg Message data to post. Message size is defined by the
+     *            position() property of the ByteBuffer.
+     */
+    public void postMessage(int msg_type, ByteBuffer msg) {
+        try {
+            mMsgQueue.put(new SdkControllerMessage(msg_type, msg));
+        } catch (InterruptedException e) {
+            Log.e(TAG, "mMessageQueue.put", e);
+        }
+    }
+
+    /**
+     * Sends query response to the emulator.
+     *
+     * @param query_id Query identifier.
+     * @param qresp Response to the query.
+     * @param len Byte size of query response data.
+     * @return true on success, or false if data transmission has failed.
+     */
+    public boolean sendQueryResponse(int query_id, byte[] qresp, int len) {
+        // Just like with messages, we must combine header and data in a single
+        // transmitting block.
+        ByteBuffer bb = ByteBuffer.allocate(ProtocolConstants.QUERY_RESP_HEADER_SIZE + len);
+        bb.order(mEndian);
+
+        // Initialize response header.
+        bb.putInt(ProtocolConstants.PACKET_SIGNATURE);
+        bb.putInt(ProtocolConstants.QUERY_RESP_HEADER_SIZE + len);
+        bb.putInt(ProtocolConstants.PACKET_TYPE_QUERY_RESPONSE);
+        bb.putInt(query_id);
+
+        // Save response data (if there is any).
+        if (qresp != null && len != 0) {
+            bb.put(qresp, 0, len);
+        }
+
+        // Send the response.
+        try {
+            Socket socket = mSocket;
+            if (socket != null) {
+                socket.send(bb.array());
+                return true;
+            } else {
+                Logw("sendQueryResponse is called on disconnected Channel "
+                        + mChannelName);
+            }
+        } catch (IOException e) {
+            Loge("Exception " + e + " in sendQueryResponse for Channel " + mChannelName);
+            onIoFailure();
+        }
+        return false;
+    }
+
+    /**
+     * Sends query response to the emulator.
+     *
+     * @param query_id Query identifier.
+     * @param qresp Response to the query. Query response size is defined by the
+     *            size of the array.
+     * @return true on success, or false if data transmission has failed.
+     */
+    public boolean sendQueryResponse(int query_id, byte[] qresp) {
+        return (qresp != null) ? sendQueryResponse(query_id, qresp, qresp.length) :
+                sendQueryResponse(query_id, null, 0);
+    }
+
+    /**
+     * Sends query response to the emulator.
+     *
+     * @param query_id Query identifier.
+     * @param qresp Response to the query. Query response size is defined by the
+     *            position() property of the ByteBuffer.
+     * @return true on success, or false if data transmission has failed.
+     */
+    public boolean sendQueryResponse(int query_id, ByteBuffer qresp) {
+        return (qresp != null) ? sendQueryResponse(query_id, qresp.array(), qresp.position()) :
+                sendQueryResponse(query_id, null, 0);
+    }
+
+    /**
+     * Handles an I/O failure occurred in the channel.
+     */
+    private void onIoFailure() {
+        // All I/O failures cause disconnection.
+        if (disconnect()) {
+            // Success of disconnect() indicates that I/O failure is not the
+            // result of a disconnection request, but is in deed an I/O
+            // failure. Report lost connection to the derived class.
+            Loge("Connection with the emulator has been lost in Channel " + mChannelName);
+            onEmulatorDisconnected();
+        }
+    }
+
+    /**
+     * Loops on the local socket, handling connection attempts.
+     */
+    private void runIOLooper() {
+        if (DEBUG) Log.d(TAG, "In I/O looper for Channel " + mChannelName);
+        // Initialize byte buffer large enough to receive packet header.
+        ByteBuffer header = ByteBuffer.allocate(ProtocolConstants.PACKET_HEADER_SIZE);
+        header.order(mEndian);
+        try {
+            // Since disconnection (which will null the mSocket) can be
+            // requested from outside of this thread, it's simpler just to make
+            // a copy of mSocket here, and work with that copy. Otherwise we
+            // will have to go through a complex synchronization algorithm that
+            // would decrease performance on normal runs. If socket gets closed
+            // while we're in the middle of transfer, an exception will occur,
+            // which we will catch and handle properly.
+            Socket socket = mSocket;
+            while (socket != null) {
+                // Reset header position.
+                header.position(0);
+                // This will receive total packet size + packet type.
+                socket.receive(header.array());
+                // First - signature.
+                final int signature = header.getInt();
+                assert signature == ProtocolConstants.PACKET_SIGNATURE;
+                // Next - packet size (including header).
+                int remains = header.getInt() - ProtocolConstants.PACKET_HEADER_SIZE;
+                // After the size comes packet type.
+                final int packet_type = header.getInt();
+
+                // Get the remainder of the data, and dispatch the packet to
+                // an appropriate handler.
+                switch (packet_type) {
+                    case ProtocolConstants.PACKET_TYPE_MESSAGE:
+                        // Read message header (one int: message type).
+                        final int ext = ProtocolConstants.MESSAGE_HEADER_SIZE - ProtocolConstants.PACKET_HEADER_SIZE;
+                        header.position(0);
+                        socket.receive(header.array(), ext);
+                        final int msg_type = header.getInt();
+
+                        // Read message data.
+                        remains -= ext;
+                        final ByteBuffer msg_data = ByteBuffer.allocate(remains);
+                        msg_data.order(mEndian);
+                        socket.receive(msg_data.array());
+
+                        // Dispatch message for handling.
+                        onEmulatorMessage(msg_type, msg_data);
+                        break;
+
+                    case ProtocolConstants.PACKET_TYPE_QUERY:
+                        // Read query ID and query type.
+                        final int extq = ProtocolConstants.QUERY_HEADER_SIZE - ProtocolConstants.PACKET_HEADER_SIZE;
+                        header.position(0);
+                        socket.receive(header.array(), extq);
+                        final int query_id = header.getInt();
+                        final int query_type = header.getInt();
+
+                        // Read query data.
+                        remains -= extq;
+                        final ByteBuffer query_data = ByteBuffer.allocate(remains);
+                        query_data.order(mEndian);
+                        socket.receive(query_data.array());
+
+                        // Dispatch query for handling.
+                        onEmulatorQuery(query_id, query_type, query_data);
+                        break;
+
+                    default:
+                        // Unknown packet type. Just discard the remainder
+                        // of the packet
+                        Loge("Unknown packet type " + packet_type + " in Channel "
+                                + mChannelName);
+                        final byte[] discard_data = new byte[remains];
+                        socket.receive(discard_data);
+                        break;
+                }
+                socket = mSocket;
+            }
+        } catch (IOException e) {
+            Loge("Exception " + e + " in I/O looper for Channel " + mChannelName);
+            onIoFailure();
+        }
+        if (DEBUG) Log.d(TAG, "Exiting I/O looper for Channel " + mChannelName);
+    }
+
+    /**
+     * Indicates any UI handler is currently registered with the channel. If no UI
+     * is displaying the channel's state, maybe the channel can skip UI related tasks.
+     *
+     * @return True if there's at least one UI handler registered.
+     */
+    public boolean hasUiHandler() {
+        return !mUiHandlers.isEmpty();
+    }
+
+    /**
+     * Registers a new UI handler.
+     *
+     * @param uiHandler A non-null UI handler to register. Ignored if the UI
+     *            handler is null or already registered.
+     */
+    public void addUiHandler(android.os.Handler uiHandler) {
+        assert uiHandler != null;
+        if (uiHandler != null) {
+            if (!mUiHandlers.contains(uiHandler)) {
+                mUiHandlers.add(uiHandler);
+            }
+        }
+    }
+
+    /**
+     * Unregisters an UI handler.
+     *
+     * @param uiHandler A non-null UI listener to unregister. Ignored if the
+     *            listener is null or already registered.
+     */
+    public void removeUiHandler(android.os.Handler uiHandler) {
+        assert uiHandler != null;
+        mUiHandlers.remove(uiHandler);
+    }
+
+    /**
+     * Protected method to be used by handlers to send an event to all UI
+     * handlers.
+     *
+     * @param event An integer event code with no specific parameters. To be
+     *            defined by the handler itself.
+     */
+    protected void notifyUiHandlers(int event) {
+        for (android.os.Handler uiHandler : mUiHandlers) {
+            uiHandler.sendEmptyMessage(event);
+        }
+    }
+
+    /**
+     * Protected method to be used by handlers to send an event to all UI
+     * handlers.
+     *
+     * @param msg An event with parameters. To be defined by the handler itself.
+     */
+    protected void notifyUiHandlers(Message msg) {
+        for (android.os.Handler uiHandler : mUiHandlers) {
+            uiHandler.sendMessage(msg);
+        }
+    }
+
+    /**
+     * A helper routine that expands ByteBuffer to contain given number of extra
+     * bytes.
+     *
+     * @param buff Buffer to expand.
+     * @param extra Number of bytes that are required to be available in the
+     *            buffer after current position()
+     * @return ByteBuffer, containing required number of available bytes.
+     */
+    public ByteBuffer ExpandIf(ByteBuffer buff, int extra) {
+        if (extra <= buff.remaining()) {
+            return buff;
+        }
+        ByteBuffer ret = ByteBuffer.allocate(buff.position() + extra);
+        ret.order(buff.order());
+        ret.put(buff.array(), 0, buff.position());
+        return ret;
+    }
+
+    /***************************************************************************
+     * Logging wrappers
+     **************************************************************************/
+
+    private void Loge(String log) {
+        mService.addError(log);
+        Log.e(TAG, log);
+    }
+
+    private void Logw(String log) {
+        Log.w(TAG, log);
+    }
+
+    private void Logv(String log) {
+        Log.v(TAG, log);
+    }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java
new file mode 100644
index 0000000..cb50869
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Connection.java
@@ -0,0 +1,412 @@
+/*
+ * 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.tools.sdkcontroller.lib;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.util.Log;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+
+import com.android.tools.sdkcontroller.lib.Channel;
+import com.android.tools.sdkcontroller.service.ControllerService;
+
+/**
+ * Encapsulates a connection between SdkController service and the emulator. On
+ * the device side, the connection is bound to the UNIX-domain socket named
+ * 'android.sdk.controller'. On the emulator side the connection is established
+ * via TCP port that is used to forward I/O traffic on the host machine to
+ * 'android.sdk.controller' socket on the device. Typically, the port forwarding
+ * can be enabled using adb command:
+ * <p/>
+ * 'adb forward tcp:<TCP port number> localabstract:android.sdk.controller'
+ * <p/>
+ * The way communication between the emulator and SDK controller service works
+ * is as follows:
+ * <p/>
+ * 1. Both sides, emulator and the service have components that implement a particular
+ * type of emulation. For instance, AndroidSensorsPort in the emulator, and
+ * SensorChannel in the application implement sensors emulation.
+ * Emulation channels are identified by unique names. For instance, sensor emulation
+ * is done via "sensors" channel, multi-touch emulation is done via "multi-touch"
+ * channel, etc.
+ * <p/>
+ * 2. Channels are connected to emulator via separate socket instance (though all
+ * of the connections share the same socket address).
+ * <p/>
+ * 3. Connection is initiated by the emulator side, while the service provides
+ * its side (a channel) that implement functionality and exchange protocol required
+ * by the requested type of emulation.
+ * <p/>
+ * Given that, the main responsibilities of this class are:
+ * <p/>
+ * 1. Bind to "android.sdk.controller" socket, listening to emulator connections.
+ * <p/>
+ * 2. Maintain a list of service-side channels registered by the application.
+ * <p/>
+ * 3. Bind emulator connection with service-side channel via port name, provided by
+ * the emulator.
+ * <p/>
+ * 4. Monitor connection state with the emulator, and automatically restore the
+ * connection once it is lost.
+ */
+public class Connection {
+    /** UNIX-domain name reserved for SDK controller. */
+    public static final String SDK_CONTROLLER_PORT = "android.sdk.controller";
+    /** Tag for logging messages. */
+    private static final String TAG = "SdkControllerConnection";
+    /** Controls debug logging */
+    private static final boolean DEBUG = false;
+
+    /** Server socket used to listen to emulator connections. */
+    private LocalServerSocket mServerSocket = null;
+    /** Service that has created this object. */
+    private ControllerService mService;
+    /**
+     * List of connected emulator sockets, pending for a channel to be registered.
+     * <p/>
+     * Emulator may connect to SDK controller before the app registers a channel
+     * for that connection. In this case (when app-side channel is not registered
+     * with this class) we will keep emulator connection in this list, pending
+     * for the app-side channel to register.
+     */
+    private List<Socket> mPendingSockets = new ArrayList<Socket>();
+    /**
+     * List of registered app-side channels.
+     * <p/>
+     * Channels that are kept in this list may be disconnected from (or pending
+     * connection with) the emulator, or they may be connected with the
+     * emulator.
+     */
+    private List<Channel> mChannels = new ArrayList<Channel>();
+
+    /**
+     * Constructs Connection instance.
+     */
+    public Connection(ControllerService service) {
+        mService = service;
+        if (DEBUG) Log.d(TAG, "SdkControllerConnection is constructed.");
+    }
+
+    /**
+     * Binds to the socket, and starts the listening thread.
+     */
+    public void connect() {
+        if (DEBUG) Log.d(TAG, "SdkControllerConnection is connecting...");
+        // Start connection listener.
+        new Thread(new Runnable() {
+                @Override
+            public void run() {
+                runIOLooper();
+            }
+        }, "SdkControllerConnectionIoLoop").start();
+    }
+
+    /**
+     * Stops the listener, and closes the socket.
+     *
+     * @return true if connection has been stopped in this call, or false if it
+     *         has been already stopped when this method has been called.
+     */
+    public boolean disconnect() {
+        // This is the only place in this class where we will null the
+        // socket object. Since this method can be called concurrently from
+        // different threads, lets do this under the lock.
+        LocalServerSocket socket;
+        synchronized (this) {
+            socket = mServerSocket;
+            mServerSocket = null;
+        }
+        if (socket != null) {
+            if (DEBUG) Log.d(TAG, "SdkControllerConnection is stopping I/O looper...");
+            // Stop accepting new connections.
+            wakeIOLooper(socket);
+            try {
+                socket.close();
+            } catch (Exception e) {
+            }
+
+            // Close all the pending sockets, and clear pending socket list.
+            if (DEBUG) Log.d(TAG, "SdkControllerConnection is closing pending sockets...");
+            for (Socket pending_socket : mPendingSockets) {
+                pending_socket.close();
+            }
+            mPendingSockets.clear();
+
+            // Disconnect all the emualtors.
+            if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnecting channels...");
+            for (Channel channel : mChannels) {
+                if (channel.disconnect()) {
+                    channel.onEmulatorDisconnected();
+                }
+            }
+            if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnected.");
+        }
+        return socket != null;
+    }
+
+    /**
+     * Registers SDK controller channel.
+     *
+     * @param channel SDK controller emulator to register.
+     * @return true if channel has been registered successfully, or false if channel
+     *         with the same name is already registered.
+     */
+    public boolean registerChannel(Channel channel) {
+        for (Channel check_channel : mChannels) {
+            if (check_channel.getChannelName().equals(channel.getChannelName())) {
+                Loge("Registering a duplicate Channel " + channel.getChannelName());
+                return false;
+            }
+        }
+        if (DEBUG) Log.d(TAG, "Registering Channel " + channel.getChannelName());
+        mChannels.add(channel);
+
+        // Lets see if there is a pending socket for this channel.
+        for (Socket pending_socket : mPendingSockets) {
+            if (pending_socket.getChannelName().equals(channel.getChannelName())) {
+                // Remove the socket from the pending list, and connect the registered channel with it.
+                if (DEBUG) Log.d(TAG, "Found pending Socket for registering Channel "
+                        + channel.getChannelName());
+                mPendingSockets.remove(pending_socket);
+                channel.connect(pending_socket);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Checks if at least one socket connection exists with channel.
+     *
+     * @return true if at least one socket connection exists with channel.
+     */
+    public boolean isEmulatorConnected() {
+        for (Channel channel : mChannels) {
+            if (channel.isConnected()) {
+                return true;
+            }
+        }
+        return !mPendingSockets.isEmpty();
+    }
+
+    /**
+     * Gets Channel instance for the given channel name.
+     *
+     * @param name Channel name to get Channel instance for.
+     * @return Channel instance for the given channel name, or NULL if no
+     *         channel has been registered for that name.
+     */
+    public Channel getChannel(String name) {
+        for (Channel channel : mChannels) {
+            if (channel.getChannelName().equals(name)) {
+                return channel;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets connected emulator socket that is pending for service-side channel
+     * registration.
+     *
+     * @param name Channel name to lookup Socket for.
+     * @return Connected emulator socket that is pending for service-side channel
+     *         registration, or null if no socket is pending for service-size
+     *         channel registration.
+     */
+    private Socket getPendingSocket(String name) {
+        for (Socket socket : mPendingSockets) {
+            if (socket.getChannelName().equals(name)) {
+                return socket;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Wakes I/O looper waiting on connection with the emulator.
+     *
+     * @param socket Server socket waiting on connection.
+     */
+    private void wakeIOLooper(LocalServerSocket socket) {
+        // We wake the looper by connecting to the socket.
+        LocalSocket waker = new LocalSocket();
+        try {
+            waker.connect(socket.getLocalSocketAddress());
+        } catch (IOException e) {
+            Loge("Exception " + e + " in SdkControllerConnection while waking up the I/O looper.");
+        }
+    }
+
+    /**
+     * Loops on the local socket, handling emulator connection attempts.
+     */
+    private void runIOLooper() {
+        if (DEBUG) Log.d(TAG, "In SdkControllerConnection I/O looper.");
+        do {
+            try {
+                // Create non-blocking server socket that would listen for connections,
+                // and bind it to the given port on the local host.
+                mServerSocket = new LocalServerSocket(SDK_CONTROLLER_PORT);
+                LocalServerSocket socket = mServerSocket;
+                while (socket != null) {
+                    final LocalSocket sk = socket.accept();
+                    if (mServerSocket != null) {
+                        onAccept(sk);
+                    } else {
+                        break;
+                    }
+                    socket = mServerSocket;
+                }
+            } catch (IOException e) {
+                Loge("Exception " + e + "SdkControllerConnection I/O looper.");
+            }
+            if (DEBUG) Log.d(TAG, "Exiting SdkControllerConnection I/O looper.");
+
+          // If we're exiting the internal loop for reasons other than an explicit
+          // disconnect request, we should reconnect again.
+        } while (disconnect());
+    }
+
+    /**
+     * Accepts new connection from the emulator.
+     *
+     * @param sock Connecting socket.
+     * @throws IOException
+     */
+    private void onAccept(LocalSocket sock) throws IOException {
+        final ByteBuffer handshake = ByteBuffer.allocate(ProtocolConstants.QUERY_HEADER_SIZE);
+
+        // By protocol, first byte received from newly connected emulator socket
+        // indicates host endianness.
+        Socket.receive(sock, handshake.array(), 1);
+        final ByteOrder endian = (handshake.getChar() == 0) ? ByteOrder.LITTLE_ENDIAN :
+                ByteOrder.BIG_ENDIAN;
+        handshake.order(endian);
+
+        // Right after that follows the handshake query header.
+        handshake.position(0);
+        Socket.receive(sock, handshake.array(), handshake.array().length);
+
+        // First int - signature
+        final int signature = handshake.getInt();
+        assert signature == ProtocolConstants.PACKET_SIGNATURE;
+        // Second int - total query size (including fixed query header)
+        final int remains = handshake.getInt() - ProtocolConstants.QUERY_HEADER_SIZE;
+        // After that - header type (which must be SDKCTL_PACKET_TYPE_QUERY)
+        final int msg_type = handshake.getInt();
+        assert msg_type == ProtocolConstants.PACKET_TYPE_QUERY;
+        // After that - query ID.
+        final int query_id = handshake.getInt();
+        // And finally, query type (which must be ProtocolConstants.QUERY_HANDSHAKE for
+        // handshake query)
+        final int query_type = handshake.getInt();
+        assert query_type == ProtocolConstants.QUERY_HANDSHAKE;
+        // Verify that received is a query.
+        if (msg_type != ProtocolConstants.PACKET_TYPE_QUERY) {
+            // Message type is not a query. Lets read and discard the remainder
+            // of the message.
+            if (remains > 0) {
+                Loge("Unexpected handshake message type: " + msg_type);
+                byte[] discard = new byte[remains];
+                Socket.receive(sock, discard, discard.length);
+            }
+            return;
+        }
+
+        // Receive query data.
+        final byte[] name_array = new byte[remains];
+        Socket.receive(sock, name_array, name_array.length);
+
+        // Prepare response header.
+        handshake.position(0);
+        handshake.putInt(ProtocolConstants.PACKET_SIGNATURE);
+        // Handshake reply is just one int.
+        handshake.putInt(ProtocolConstants.QUERY_RESP_HEADER_SIZE + 4);
+        handshake.putInt(ProtocolConstants.PACKET_TYPE_QUERY_RESPONSE);
+        handshake.putInt(query_id);
+
+        // Verify that received query is in deed a handshake query.
+        if (query_type != ProtocolConstants.QUERY_HANDSHAKE) {
+            // Query is not a handshake. Reply with failure.
+            Loge("Unexpected handshake query type: " + query_type);
+            handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_QUERY_UNKNOWN);
+            sock.getOutputStream().write(handshake.array());
+            return;
+        }
+
+        // Handshake query data consist of SDK controller channel name.
+        final String channel_name = new String(name_array);
+        if (DEBUG) Log.d(TAG, "Handshake received for channel " + channel_name);
+
+        // Respond to query depending on service-side channel availability
+        final Channel channel = getChannel(channel_name);
+        Socket sk = null;
+
+        if (channel != null) {
+            if (channel.isConnected()) {
+                // This is a duplicate connection.
+                Loge("Duplicate connection to a connected Channel " + channel_name);
+                handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_DUP);
+            } else {
+                // Connecting to a registered channel.
+                if (DEBUG) Log.d(TAG, "Emulator is connected to a registered Channel " + channel_name);
+                handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_CONNECTED);
+            }
+        } else {
+            // Make sure that there are no other channel connections for this
+            // channel name.
+            if (getPendingSocket(channel_name) != null) {
+                // This is a duplicate.
+                Loge("Duplicate connection to a pending Socket " + channel_name);
+                handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_DUP);
+            } else {
+                // Connecting to a channel that has not been registered yet.
+                if (DEBUG) Log.d(TAG, "Emulator is connected to a pending Socket " + channel_name);
+                handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_NOPORT);
+                sk = new Socket(sock, channel_name, endian);
+                mPendingSockets.add(sk);
+            }
+        }
+
+        // Send handshake reply.
+        sock.getOutputStream().write(handshake.array());
+
+        // If a disconnected channel for emulator connection has been found,
+        // connect it.
+        if (channel != null && !channel.isConnected()) {
+            if (DEBUG) Log.d(TAG, "Connecting Channel " + channel_name + " with emulator.");
+            sk = new Socket(sock, channel_name, endian);
+            channel.connect(sk);
+        }
+
+        mService.notifyStatusChanged();
+    }
+
+    /***************************************************************************
+     * Logging wrappers
+     **************************************************************************/
+
+    private void Loge(String log) {
+        mService.addError(log);
+        Log.e(TAG, log);
+    }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java
deleted file mode 100755
index f7682f8..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorConnection.java
+++ /dev/null
@@ -1,968 +0,0 @@
-/*

- * Copyright (C) 2011 The Android Open Source Project

- *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not

- * use this file except in compliance with the License. You may obtain a copy of

- * the License at

- *

- * http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing, software

- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

- * License for the specific language governing permissions and limitations under

- * the License.

- */

-

-package com.android.tools.sdkcontroller.lib;

-

-import java.io.IOException;

-import java.net.InetAddress;

-import java.net.InetSocketAddress;

-import java.net.Socket;

-import java.nio.ByteBuffer;

-import java.nio.channels.ClosedChannelException;

-import java.nio.channels.ClosedSelectorException;

-import java.nio.channels.SelectionKey;

-import java.nio.channels.Selector;

-import java.nio.channels.ServerSocketChannel;

-import java.nio.channels.SocketChannel;

-import java.nio.channels.spi.SelectorProvider;

-import java.util.Iterator;

-import java.util.Set;

-import java.util.Vector;

-

-import android.util.Log;

-

-/**

- * Encapsulates a connection with the emulator. The connection is established

- * over a TCP port forwarding enabled with 'adb forward' command.

- * <p/>

- * Communication with the emulator is performed via two socket channels

- * connected to the forwarded TCP port. One channel is a query channel that is

- * intended solely for receiving queries from the emulator. Another channel is

- * an event channel that is intended for sending notification messages (events)

- * to the emulator.

- * <p/>

- * EmulatorConnection is considered to be "connected" when both channels are connected.

- * EmulatorConnection is considered to be "disconnected" when connection with any of the

- * channels is lost.

- * <p/>

- * Instance of this class is operational only for a single connection with the

- * emulator. Once connection is established and then lost, a new instance of

- * this class must be created to establish new connection.

- * <p/>

- * Note that connection with the device over TCP port forwarding is extremely

- * fragile at the moment. For whatever reason the connection is even more

- * fragile if device uses asynchronous sockets (based on java.nio API). So, to

- * address this issue EmulatorConnection class implements two types of connections. One is

- * using synchronous sockets, and another is using asynchronous sockets. The

- * type of connection is selected when EmulatorConnection instance is created (see

- * comments to EmulatorConnection's constructor).

- * <p/>

- * According to the exchange protocol with the emulator, queries, responses to

- * the queries, and notification messages are all zero-terminated strings.

- */

-public class EmulatorConnection {

-    /** Defines connection types supported by the EmulatorConnection class. */

-    public enum EmulatorConnectionType {

-        /** Use asynchronous connection (based on java.nio API). */

-        ASYNC_CONNECTION,

-        /** Use synchronous connection (based on synchronous Socket objects). */

-        SYNC_CONNECTION,

-    }

-

-    /** TCP port reserved for the sensors emulation. */

-    public static final int SENSORS_PORT = 1968;

-    /** TCP port reserved for the multitouch emulation. */

-    public static final int MULTITOUCH_PORT = 1969;

-    /** Tag for logging messages. */

-    private static final String TAG = "EmulatorConnection";

-    /** EmulatorConnection events listener. */

-    private EmulatorListener mListener;

-    /** I/O selector (looper). */

-    private Selector mSelector;

-    /** Server socket channel. */

-    private ServerSocketChannel mServerSocket;

-    /** Query channel. */

-    private EmulatorChannel mQueryChannel;

-    /** Event channel. */

-    private EmulatorChannel mEventChannel;

-    /** Selector for the connection type. */

-    private EmulatorConnectionType mConnectionType;

-    /** Connection status */

-    private boolean mIsConnected = false;

-    /** Disconnection status */

-    private boolean mIsDisconnected = false;

-    /** Exit I/O loop flag. */

-    private boolean mExitIoLoop = false;

-    /** Disconnect flag. */

-    private boolean mDisconnect = false;

-

-    /***************************************************************************

-     * EmulatorChannel - Base class for sync / async channels.

-     **************************************************************************/

-

-    /**

-     * Encapsulates a base class for synchronous and asynchronous communication

-     * channels.

-     */

-    private abstract class EmulatorChannel {

-        /** Identifier for a query channel type. */

-        private static final String QUERY_CHANNEL = "query";

-        /** Identifier for an event channel type. */

-        private static final String EVENT_CHANNEL = "event";

-        /** BLOB query string. */

-        private static final String BLOBL_QUERY = "$BLOB";

-

-        /***********************************************************************

-         * Abstract API

-         **********************************************************************/

-

-        /**

-         * Sends a message via this channel.

-         *

-         * @param msg Zero-terminated message string to send.

-         */

-        public abstract void sendMessage(String msg) throws IOException;

-

-        /**

-         * Closes this channel.

-         */

-        abstract public void closeChannel() throws IOException;

-

-        /***********************************************************************

-         * Public API

-         **********************************************************************/

-

-        /**

-         * Constructs EmulatorChannel instance.

-         */

-        public EmulatorChannel() {

-        }

-

-        /**

-         * Handles a query received in this channel.

-         *

-         * @param socket A socket through which the query has been received.

-         * @param query_str Query received from this channel. All queries are

-         *            formatted as such: <query>:<query parameters> where -

-         *            <query> Is a query name that identifies the query, and -

-         *            <query parameters> represent parameters for the query.

-         *            Query name and query parameters are separated with a ':'

-         *            character.

-         */

-        public void onQueryReceived(Socket socket, String query_str) throws IOException {

-            String query, query_param, response;

-

-            // Lets see if query has parameters.

-            int sep = query_str.indexOf(':');

-            if (sep == -1) {

-                // Query has no parameters.

-                query = query_str;

-                query_param = "";

-            } else {

-                // Separate query name from its parameters.

-                query = query_str.substring(0, sep);

-                // Make sure that substring after the ':' does contain

-                // something, otherwise the query is paramless.

-                query_param = (sep < (query_str.length() - 1)) ? query_str.substring(sep + 1) : "";

-            }

-

-            // Handle the query, obtain response string, and reply it back to

-            // the emulator. Note that there is one special query: $BLOB, that

-            // requires reading of a byte array of data first. The size of the

-            // array is defined by the query parameter.

-            if (query.compareTo(BLOBL_QUERY) == 0) {

-                // This is the BLOB query. It must have a parameter which

-                // contains byte size of the blob.

-                final int array_size = Integer.parseInt(query_param);

-                if (array_size > 0) {

-                    // Read data from the query's socket.

-                    byte[] array = new byte[array_size];

-                    final int transferred = readSocketArray(socket, array);

-                    if (transferred == array_size) {

-                        // Handle blob query.

-                        response = onBlobQuery(array);

-                    } else {

-                        response = "ko:Transfer failure\0";

-                    }

-                } else {

-                    response = "ko:Invalid parameter\0";

-                }

-            } else {

-                response = onQuery(query, query_param);

-                if (response.length() == 0 || response.charAt(0) == '\0') {

-                    Logw("No response to the query " + query_str);

-                }

-            }

-

-            if (response.length() != 0) {

-                if (response.charAt(response.length() - 1) != '\0') {

-                    Logw("Response '" + response + "' to query '" + query

-                            + "' does not contain zero-terminator.");

-                }

-                sendMessage(response);

-            }

-        }

-    } // EmulatorChannel

-

-    /***************************************************************************

-     * EmulatorSyncChannel - Implements a synchronous channel.

-     **************************************************************************/

-

-    /**

-     * Encapsulates a synchronous communication channel with the emulator.

-     */

-    private class EmulatorSyncChannel extends EmulatorChannel {

-        /** Communication socket. */

-        private Socket mSocket;

-

-        /**

-         * Constructs EmulatorSyncChannel instance.

-         *

-         * @param socket Connected ('accept'ed) communication socket.

-         */

-        public EmulatorSyncChannel(Socket socket) {

-            mSocket = socket;

-            // Start the reader thread.

-            new Thread(new Runnable() {

-                @Override

-                public void run() {

-                    theReader();

-                }

-            }, "EmuSyncChannel").start();

-        }

-

-        /***********************************************************************

-         * Abstract API implementation

-         **********************************************************************/

-

-        /**

-         * Sends a message via this channel.

-         *

-         * @param msg Zero-terminated message string to send.

-         */

-        @Override

-        public void sendMessage(String msg) throws IOException {

-            if (msg.charAt(msg.length() - 1) != '\0') {

-                Logw("Missing zero-terminator in message '" + msg + "'");

-            }

-            mSocket.getOutputStream().write(msg.getBytes());

-        }

-

-        /**

-         * Closes this channel.

-         */

-        @Override

-        public void closeChannel() throws IOException {

-            mSocket.close();

-        }

-

-        /***********************************************************************

-         * EmulatorSyncChannel implementation

-         **********************************************************************/

-

-        /**

-         * The reader thread: loops reading and dispatching queries.

-         */

-        private void theReader() {

-            try {

-                for (;;) {

-                    String query = readSocketString(mSocket);

-                    onQueryReceived(mSocket, query);

-                }

-            } catch (IOException e) {

-                onLostConnection();

-            }

-        }

-    } // EmulatorSyncChannel

-

-    /***************************************************************************

-     * EmulatorAsyncChannel - Implements an asynchronous channel.

-     **************************************************************************/

-

-    /**

-     * Encapsulates an asynchronous communication channel with the emulator.

-     */

-    private class EmulatorAsyncChannel extends EmulatorChannel {

-        /** Communication socket channel. */

-        private SocketChannel mChannel;

-        /** I/O selection key for this channel. */

-        private SelectionKey mSelectionKey;

-        /** Accumulator for the query string received in this channel. */

-        private String mQuery = "";

-        /**

-         * Preallocated character reader that is used when data is read from

-         * this channel. See 'onRead' method for more details.

-         */

-        private ByteBuffer mIn = ByteBuffer.allocate(1);

-        /**

-         * Currently sent notification message(s). See 'sendMessage', and

-         * 'onWrite' methods for more details.

-         */

-        private ByteBuffer mOut;

-        /**

-         * Array of pending notification messages. See 'sendMessage', and

-         * 'onWrite' methods for more details.

-         */

-        private Vector<String> mNotifications = new Vector<String>();

-

-        /**

-         * Constructs EmulatorAsyncChannel instance.

-         *

-         * @param channel Accepted socket channel to use for communication.

-         * @throws IOException

-         */

-        private EmulatorAsyncChannel(SocketChannel channel) throws IOException {

-            // Mark character reader at the beginning, so we can reset it after

-            // next read character has been pulled out from the buffer.

-            mIn.mark();

-

-            // Configure communication channel as non-blocking, and register

-            // it with the I/O selector for reading.

-            mChannel = channel;

-            mChannel.configureBlocking(false);

-            mSelectionKey = mChannel.register(mSelector, SelectionKey.OP_READ, this);

-            // Start receiving read I/O.

-            mSelectionKey.selector().wakeup();

-        }

-

-        /***********************************************************************

-         * Abstract API implementation

-         **********************************************************************/

-

-        /**

-         * Sends a message via this channel.

-         *

-         * @param msg Zero-terminated message string to send.

-         */

-        @Override

-        public void sendMessage(String msg) throws IOException {

-            if (msg.charAt(msg.length() - 1) != '\0') {

-                Logw("Missing zero-terminator in message '" + msg + "'");

-            }

-            synchronized (this) {

-                if (mOut != null) {

-                    // Channel is busy with writing another message.

-                    // Queue this one. It will be picked up later when current

-                    // write operation is completed.

-                    mNotifications.add(msg);

-                    return;

-                }

-

-                // No other messages are in progress. Send this one outside of

-                // the lock.

-                mOut = ByteBuffer.wrap(msg.getBytes());

-            }

-            mChannel.write(mOut);

-

-            // Lets see if we were able to send the entire message.

-            if (mOut.hasRemaining()) {

-                // Write didn't complete. Schedule write I/O callback to

-                // pick up from where this write has left.

-                enableWrite();

-                return;

-            }

-

-            // Entire message has been sent. Lets see if other messages were

-            // queued while we were busy sending this one.

-            for (;;) {

-                synchronized (this) {

-                    // Dequeue message that was yielding to this write.

-                    if (!dequeueMessage()) {

-                        // Writing is over...

-                        disableWrite();

-                        mOut = null;

-                        return;

-                    }

-                }

-

-                // Send queued message.

-                mChannel.write(mOut);

-

-                // Lets see if we were able to send the entire message.

-                if (mOut.hasRemaining()) {

-                    // Write didn't complete. Schedule write I/O callback to

-                    // pick up from where this write has left.

-                    enableWrite();

-                    return;

-                }

-            }

-        }

-

-        /**

-         * Closes this channel.

-         */

-        @Override

-        public void closeChannel() throws IOException {

-            mSelectionKey.cancel();

-            synchronized (this) {

-                mNotifications.clear();

-            }

-            mChannel.close();

-        }

-

-        /***********************************************************************

-         * EmulatorAsyncChannel implementation

-         **********************************************************************/

-

-        /**

-         * Reads data from the channel. This method is invoked from the I/O loop

-         * when data is available for reading on this channel. When reading from

-         * a channel we read character-by-character, building the query string

-         * until zero-terminator is read. When zero-terminator is read, we

-         * handle the query, and start building the new query string.

-         *

-         * @throws IOException

-         */

-        private void onRead() throws IOException, ClosedChannelException {

-            int count = mChannel.read(mIn);

-            Logv("onRead: " + count);

-            while (count == 1) {

-                final char c = (char) mIn.array()[0];

-                mIn.reset();

-                if (c == '\0') {

-                    // Zero-terminator is read. Process the query, and reset

-                    // the query string.

-                    onQueryReceived(mChannel.socket(), mQuery);

-                    mQuery = "";

-                } else {

-                    // Continue building the query string.

-                    mQuery += c;

-                }

-                count = mChannel.read(mIn);

-            }

-

-            if (count == -1) {

-                // Channel got disconnected.

-                throw new ClosedChannelException();

-            } else {

-                // "Don't block" in effect. Will get back to reading as soon as

-                // read I/O is available.

-                assert (count == 0);

-            }

-        }

-

-        /**

-         * Writes data to the channel. This method is ivnoked from the I/O loop

-         * when data is available for writing on this channel.

-         *

-         * @throws IOException

-         */

-        private void onWrite() throws IOException {

-            if (mOut != null && mOut.hasRemaining()) {

-                // Continue writing to the channel.

-                mChannel.write(mOut);

-                if (mOut.hasRemaining()) {

-                    // Write is still incomplete. Come back to it when write I/O

-                    // becomes available.

-                    return;

-                }

-            }

-

-            // We're done with the current message. Lets see if we've

-            // accumulated some more while this write was in progress.

-            synchronized (this) {

-                // Dequeue next message into mOut.

-                if (!dequeueMessage()) {

-                    // Nothing left to write.

-                    disableWrite();

-                    mOut = null;

-                    return;

-                }

-                // We don't really want to run a big loop here, flushing the

-                // message queue. The reason is that we're inside the I/O loop,

-                // so we don't want to block others for long. So, we will

-                // continue with queue flushing next time we're picked up by

-                // write I/O event.

-            }

-        }

-

-        /**

-         * Dequeues messages that were yielding to the write in progress.

-         * Messages will be dequeued directly to the mOut, so it's ready to be

-         * sent when this method returns. NOTE: This method must be called from

-         * within synchronized(this).

-         *

-         * @return true if messages were dequeued, or false if message queue was

-         *         empty.

-         */

-        private boolean dequeueMessage() {

-            // It's tempting to dequeue all messages here, but in practice it's

-            // less performant than dequeuing just one.

-            if (!mNotifications.isEmpty()) {

-                mOut = ByteBuffer.wrap(mNotifications.remove(0).getBytes());

-                return true;

-            } else {

-                return false;

-            }

-        }

-

-        /**

-         * Enables write I/O callbacks.

-         */

-        private void enableWrite() {

-            mSelectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);

-            // Looks like we must wake up the selector. Otherwise it's not going

-            // to immediately pick up on the change that we just made.

-            mSelectionKey.selector().wakeup();

-        }

-

-        /**

-         * Disables write I/O callbacks.

-         */

-        private void disableWrite() {

-            mSelectionKey.interestOps(SelectionKey.OP_READ);

-        }

-    } // EmulatorChannel

-

-    /***************************************************************************

-     * EmulatorConnection public API

-     **************************************************************************/

-

-    /**

-     * Constructs EmulatorConnection instance.

-     * Caller must call {@link #connect(int, EmulatorConnectionType)} afterwards.

-     *

-     * @param listener EmulatorConnection event listener. Must not be null.

-     */

-    public EmulatorConnection(EmulatorListener listener) {

-        mListener = listener;

-    }

-

-    /**

-     * Connects the EmulatorConnection instance.

-     * <p/>

-     * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main

-     * thread. The caller is responsible to make sure this is NOT called from a main UI thread.

-     *

-     * @param port TCP port where emulator connects.

-     * @param ctype Defines connection type to use (sync / async). See comments

-     *            to EmulatorConnection class for more info.

-     * @return This object for chaining calls.

-     */

-    public EmulatorConnection connect(int port, EmulatorConnectionType ctype) {

-        constructEmulator(port, ctype);

-        return this;

-    }

-

-

-    /**

-     * Disconnects the emulator.

-     */

-    public void disconnect() {

-        mDisconnect = true;

-        mSelector.wakeup();

-    }

-

-    /**

-     * Constructs EmulatorConnection instance.

-     * <p/>

-     * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main

-     * thread. The caller is responsible to make sure this is NOT called from a main UI thread.

-     * <p/>

-     * On error or success, this calls

-     * {@link EmulatorListener#onEmulatorBindResult(boolean, Exception)} to indicate whether

-     * the socket was properly bound.

-     * The IO loop will start after the method reported a successful bind.

-     *

-     * @param port TCP port where emulator connects.

-     * @param ctype Defines connection type to use (sync / async). See comments

-     *            to EmulatorConnection class for more info.

-     */

-    private void constructEmulator(final int port, EmulatorConnectionType ctype) {

-

-        try {

-            mConnectionType = ctype;

-            // Create I/O looper.

-            mSelector = SelectorProvider.provider().openSelector();

-

-            // Create non-blocking server socket that would listen for connections,

-            // and bind it to the given port on the local host.

-            mServerSocket = ServerSocketChannel.open();

-            mServerSocket.configureBlocking(false);

-            InetAddress local = InetAddress.getLocalHost();

-            final InetSocketAddress address = new InetSocketAddress(local, port);

-            mServerSocket.socket().bind(address);

-

-            // Register 'accept' I/O on the server socket.

-            mServerSocket.register(mSelector, SelectionKey.OP_ACCEPT);

-        } catch (IOException e) {

-            mListener.onEmulatorBindResult(false, e);

-            return;

-        }

-

-        mListener.onEmulatorBindResult(true, null);

-        Logv("EmulatorConnection listener is created for port " + port);

-

-        // Start I/O looper and dispatcher.

-        new Thread(new Runnable() {

-            @Override

-            public void run() {

-                runIOLooper();

-            }

-        }, "EmuCnxIoLoop").start();

-    }

-

-    /**

-     * Sends a notification message to the emulator via 'event' channel.

-     * <p/>

-     * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main

-     * thread. The caller is responsible to make sure this is NOT called from a main UI thread.

-     *

-     * @param msg

-     */

-    public void sendNotification(String msg) {

-        if (mIsConnected) {

-            try {

-                mEventChannel.sendMessage(msg);

-            } catch (IOException e) {

-                onLostConnection();

-            }

-        } else {

-            Logw("Attempt to send '" + msg + "' to a disconnected EmulatorConnection");

-        }

-    }

-

-    /**

-     * Sets or removes a listener to the events generated by this emulator

-     * instance.

-     *

-     * @param listener Listener to set. Passing null with this parameter will

-     *            remove the current listener (if there was one).

-     */

-    public void setEmulatorListener(EmulatorListener listener) {

-        synchronized (this) {

-            mListener = listener;

-        }

-        // Make sure that new listener knows the connection status.

-        if (mListener != null) {

-            if (mIsConnected) {

-                mListener.onEmulatorConnected();

-            } else if (mIsDisconnected) {

-                mListener.onEmulatorDisconnected();

-            }

-        }

-    }

-

-    /***************************************************************************

-     * EmulatorConnection events

-     **************************************************************************/

-

-    /**

-     * Called when emulator is connected. NOTE: This method is called from the

-     * I/O loop, so all communication with the emulator will be "on hold" until

-     * this method returns.

-     */

-    private void onConnected() {

-        EmulatorListener listener;

-        synchronized (this) {

-            listener = mListener;

-        }

-        if (listener != null) {

-            listener.onEmulatorConnected();

-        }

-    }

-

-    /**

-     * Called when emulator is disconnected. NOTE: This method could be called

-     * from the I/O loop, in which case all communication with the emulator will

-     * be "on hold" until this method returns.

-     */

-    private void onDisconnected() {

-        EmulatorListener listener;

-        synchronized (this) {

-            listener = mListener;

-        }

-        if (listener != null) {

-            listener.onEmulatorDisconnected();

-        }

-    }

-

-    /**

-     * Called when a query is received from the emulator. NOTE: This method

-     * could be called from the I/O loop, in which case all communication with

-     * the emulator will be "on hold" until this method returns.

-     *

-     * @param query Name of the query received from the emulator.

-     * @param param Query parameters.

-     * @return Zero-terminated reply string. String must be formatted as such:

-     *         "ok|ko[:reply data]"

-     */

-    private String onQuery(String query, String param) {

-        EmulatorListener listener;

-        synchronized (this) {

-            listener = mListener;

-        }

-        if (listener != null) {

-            return listener.onEmulatorQuery(query, param);

-        } else {

-            return "ko:Service is detached.\0";

-        }

-    }

-

-    /**

-     * Called when a BLOB query is received from the emulator. NOTE: This method

-     * could be called from the I/O loop, in which case all communication with

-     * the emulator will be "on hold" until this method returns.

-     *

-     * @param array Array containing blob data.

-     * @return Zero-terminated reply string. String must be formatted as such:

-     *         "ok|ko[:reply data]"

-     */

-    private String onBlobQuery(byte[] array) {

-        EmulatorListener listener;

-        synchronized (this) {

-            listener = mListener;

-        }

-        if (listener != null) {

-            return listener.onEmulatorBlobQuery(array);

-        } else {

-            return "ko:Service is detached.\0";

-        }

-    }

-

-    /***************************************************************************

-     * EmulatorConnection implementation

-     **************************************************************************/

-

-    /**

-     * Loops on the selector, handling and dispatching I/O events.

-     */

-    private void runIOLooper() {

-        try {

-            Logv("Waiting on EmulatorConnection to connect...");

-            // Check mExitIoLoop before calling 'select', and after in order to

-            // detect condition when mSelector has been waken up to exit the

-            // I/O loop.

-            while (!mExitIoLoop && !mDisconnect &&

-                    mSelector.select() >= 0 &&

-                    !mExitIoLoop && !mDisconnect) {

-                Set<SelectionKey> readyKeys = mSelector.selectedKeys();

-                Iterator<SelectionKey> i = readyKeys.iterator();

-                while (i.hasNext()) {

-                    SelectionKey sk = i.next();

-                    i.remove();

-                    if (sk.isAcceptable()) {

-                        final int ready = sk.readyOps();

-                        if ((ready & SelectionKey.OP_ACCEPT) != 0) {

-                            // Accept new connection.

-                            onAccept(((ServerSocketChannel) sk.channel()).accept());

-                        }

-                    } else {

-                        // Read / write events are expected only on a 'query',

-                        // or 'event' asynchronous channels.

-                        EmulatorAsyncChannel esc = (EmulatorAsyncChannel) sk.attachment();

-                        if (esc != null) {

-                            final int ready = sk.readyOps();

-                            if ((ready & SelectionKey.OP_READ) != 0) {

-                                // Read data.

-                                esc.onRead();

-                            }

-                            if ((ready & SelectionKey.OP_WRITE) != 0) {

-                                // Write data.

-                                esc.onWrite();

-                            }

-                        } else {

-                            Loge("No emulator channel found in selection key.");

-                        }

-                    }

-                }

-            }

-        } catch (ClosedSelectorException e) {

-        } catch (IOException e) {

-        }

-

-        // Destroy connection on any I/O failure.

-        if (!mExitIoLoop) {

-            onLostConnection();

-        }

-    }

-

-    /**

-     * Accepts new connection from the emulator.

-     *

-     * @param channel Connecting socket channel.

-     * @throws IOException

-     */

-    private void onAccept(SocketChannel channel) throws IOException {

-        // Make sure we're not connected yet.

-        if (mEventChannel != null && mQueryChannel != null) {

-            // We don't accept any more connections after both channels were

-            // connected.

-            Loge("EmulatorConnection is connecting to the already connected instance.");

-            channel.close();

-            return;

-        }

-

-        // According to the protocol, each channel identifies itself as a query

-        // or event channel, sending a "cmd", or "event" message right after

-        // the connection.

-        Socket socket = channel.socket();

-        String socket_type = readSocketString(socket);

-        if (socket_type.contentEquals(EmulatorChannel.QUERY_CHANNEL)) {

-            if (mQueryChannel == null) {

-                // TODO: Find better way to do that!

-                socket.getOutputStream().write("ok\0".getBytes());

-                if (mConnectionType == EmulatorConnectionType.ASYNC_CONNECTION) {

-                    mQueryChannel = new EmulatorAsyncChannel(channel);

-                    Logv("Asynchronous query channel is registered.");

-                } else {

-                    mQueryChannel = new EmulatorSyncChannel(channel.socket());

-                    Logv("Synchronous query channel is registered.");

-                }

-            } else {

-                // TODO: Find better way to do that!

-                Loge("Duplicate query channel.");

-                socket.getOutputStream().write("ko:Duplicate\0".getBytes());

-                channel.close();

-                return;

-            }

-        } else if (socket_type.contentEquals(EmulatorChannel.EVENT_CHANNEL)) {

-            if (mEventChannel == null) {

-                // TODO: Find better way to do that!

-                socket.getOutputStream().write("ok\0".getBytes());

-                if (mConnectionType == EmulatorConnectionType.ASYNC_CONNECTION) {

-                    mEventChannel = new EmulatorAsyncChannel(channel);

-                    Logv("Asynchronous event channel is registered.");

-                } else {

-                    mEventChannel = new EmulatorSyncChannel(channel.socket());

-                    Logv("Synchronous event channel is registered.");

-                }

-            } else {

-                Loge("Duplicate event channel.");

-                socket.getOutputStream().write("ko:Duplicate\0".getBytes());

-                channel.close();

-                return;

-            }

-        } else {

-            Loge("Unknown channel is connecting: " + socket_type);

-            socket.getOutputStream().write("ko:Unknown channel type\0".getBytes());

-            channel.close();

-            return;

-        }

-

-        // Lets see if connection is complete...

-        if (mEventChannel != null && mQueryChannel != null) {

-            // When both, query and event channels are connected, the emulator

-            // is considered to be connected.

-            Logv("... EmulatorConnection is connected.");

-            mIsConnected = true;

-            onConnected();

-        }

-    }

-

-    /**

-     * Called when connection to any of the channels has been lost.

-     */

-    private void onLostConnection() {

-        // Since we're multithreaded, there can be multiple "bangs" from those

-        // threads. We should only handle the first one.

-        boolean first_time = false;

-        synchronized (this) {

-            first_time = mIsConnected;

-            mIsConnected = false;

-            mIsDisconnected = true;

-        }

-        if (first_time) {

-            Logw("Connection with the emulator is lost!");

-            // Close all channels, exit the I/O loop, and close the selector.

-            try {

-                if (mEventChannel != null) {

-                    mEventChannel.closeChannel();

-                }

-                if (mQueryChannel != null) {

-                    mQueryChannel.closeChannel();

-                }

-                if (mServerSocket != null) {

-                    mServerSocket.close();

-                }

-                if (mSelector != null) {

-                    mExitIoLoop = true;

-                    mSelector.wakeup();

-                    mSelector.close();

-                }

-            } catch (IOException e) {

-                Loge("onLostConnection exception: " + e.getMessage());

-            }

-

-            // Notify the app about lost connection.

-            onDisconnected();

-        }

-    }

-

-    /**

-     * Reads zero-terminated string from a synchronous socket.

-     *

-     * @param socket Socket to read string from. Must be a synchronous socket.

-     * @return String read from the socket.

-     * @throws IOException

-     */

-    private static String readSocketString(Socket socket) throws IOException {

-        String str = "";

-

-        // Current characted received from the input stream.

-        int current_byte = 0;

-

-        // With port forwarding there is no reliable way how to detect

-        // socket disconnection, other than checking on the input stream

-        // to die ("end of stream" condition). That condition is reported

-        // when input stream's read() method returns -1.

-        while (socket.isConnected() && current_byte != -1) {

-            // Character by character read the input stream, and accumulate

-            // read characters in the command string. The end of the command

-            // is indicated with zero character.

-            current_byte = socket.getInputStream().read();

-            if (current_byte != -1) {

-                if (current_byte == 0) {

-                    // String is completed.

-                    return str;

-                } else {

-                    // Append read character to the string.

-                    str += (char) current_byte;

-                }

-            }

-        }

-

-        // Got disconnected!

-        throw new ClosedChannelException();

-    }

-

-    /**

-     * Reads a block of data from a socket.

-     *

-     * @param socket Socket to read data from. Must be a synchronous socket.

-     * @param array Array where to read data.

-     * @return Number of bytes read from the socket, or -1 on an error.

-     * @throws IOException

-     */

-    private static int readSocketArray(Socket socket, byte[] array) throws IOException {

-        int in = 0;

-        while (in < array.length) {

-            final int ret = socket.getInputStream().read(array, in, array.length - in);

-            if (ret == -1) {

-                // Got disconnected!

-                throw new ClosedChannelException();

-            }

-            in += ret;

-        }

-        return in;

-    }

-

-    /***************************************************************************

-     * Logging wrappers

-     **************************************************************************/

-

-    private void Loge(String log) {

-        Log.e(TAG, log);

-    }

-

-    private void Logw(String log) {

-        Log.w(TAG, log);

-    }

-

-    private void Logv(String log) {

-        Log.v(TAG, log);

-    }

-}

diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorListener.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorListener.java
deleted file mode 100644
index 4d2a19f..0000000
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/EmulatorListener.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.tools.sdkcontroller.lib;
-
-
-/**
- * Encapsulates a listener to emulator events. An object implementing this
- * interface must be registered with the EmulatorConnection instance via
- * setEmulatorListener method of the EmulatorConnection class.
- */
-public interface EmulatorListener {
-
-    /**
-     * Called as a side effect of constructing a new {@link EmulatorConnection} to
-     * indicate whether the when emulator is bound with its communication socket.
-     *
-     * @param success True if the socket bind was successful.
-     *   False when the socket bind failed.
-     * @param e Any exception thrown whilst trying to bind to the communication socket.
-     *   Null if there's no exception (typically when {@code success==true}).
-     */
-    public void onEmulatorBindResult(boolean success, Exception e);
-
-    /**
-     * Called when emulator is connected. NOTE: This method is called from the
-     * I/O loop, so all communication with the emulator will be "on hold" until
-     * this method returns.
-     */
-    public void onEmulatorConnected();
-
-    /**
-     * Called when emulator is disconnected. NOTE: This method could be called
-     * from the I/O loop, in which case all communication with the emulator will
-     * be "on hold" until this method returns.
-     */
-    public void onEmulatorDisconnected();
-
-    /**
-     * Called when a query is received from the emulator. NOTE: This method is
-     * called from the I/O loop, so all communication with the emulator will be
-     * "on hold" until this method returns.
-     *
-     * @param query Name of the query received from the emulator.
-     * @param param Query parameters.
-     * @return Zero-terminated reply string. If not an empty string is returned,
-     *         it must be formatted as such: "ok|ko[:reply data]"
-     */
-    public String onEmulatorQuery(String query, String param);
-
-    /**
-     * Called when a BLOB query is received from the emulator. NOTE: This method
-     * is called from the I/O loop, so all communication with the emulator will
-     * be "on hold" until this method returns.
-     *
-     * @param array contains BLOB data for the query.
-     * @return Zero-terminated reply string. If not an empty string is returned,
-     *         it must be formatted as such: "ok|ko[:reply data]"
-     */
-    public String onEmulatorBlobQuery(byte[] array);
-}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java
new file mode 100644
index 0000000..b4c6593
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/ProtocolConstants.java
@@ -0,0 +1,142 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.tools.sdkcontroller.lib;
+
+/**
+ * Contains declarations of constants that are tied to emulator implementation.
+ * These constants can be changed only simultaneously in both places.
+ */
+public final class ProtocolConstants {
+    /*
+     * Constants related to data transfer.
+     */
+
+    /** Signature of a packet sent via SDK controller socket ('SDKC') */
+    public static final int PACKET_SIGNATURE = 0x53444B43;
+
+    /*
+     * Header sizes for packets sent / received by SDK controller emulator.
+     */
+
+    /**
+     * 12 bytes (3 ints) for the packet header:
+     * <p/>
+     * - Signature.
+     * <p/>
+     * - Total packet size.
+     * <p/>
+     * - Packet type.
+     */
+    public static final int PACKET_HEADER_SIZE = 12;
+    /**
+     * 16 bytes (4 ints) for the message header:
+     * <p/>
+     * - Common packet header.
+     * <p/>
+     * - Message type.
+     */
+    public static final int MESSAGE_HEADER_SIZE = 16;
+    /**
+     * 20 bytes (5 ints) for the query header:
+     * <p/>
+     * - Common packet header.
+     * <p/>
+     * - Query ID.
+     * <p/>
+     * - Query type.
+     */
+    public static final int QUERY_HEADER_SIZE = 20;
+    /**
+     * 16 bytes (4 ints) for the query response:
+     * <p/>
+     * - Common packet header.
+     * <p/>
+     * - Query ID.
+     */
+    public static final int QUERY_RESP_HEADER_SIZE = 16;
+
+    /*
+     * Types of packets transferred via SDK Controller channel.
+     */
+
+    /** Packet is a message. */
+    public static final int PACKET_TYPE_MESSAGE = 1;
+    /** Packet is a query. */
+    public static final int PACKET_TYPE_QUERY = 2;
+    /** Packet is a response to a query. */
+    public static final int PACKET_TYPE_QUERY_RESPONSE = 3;
+
+    /*
+     * Constants related to handshake protocol between the emulator and a channel.
+     */
+
+    /**
+     * Query type for a special "handshake" query.
+     * <p/>
+     * When emulator connects to SDK controller, the first thing that goes
+     * through the socket is a special "handshake" query that delivers channel name
+     * to the service.
+     */
+    public static final int QUERY_HANDSHAKE = -1;
+    /**
+     * Handshake query response on condition that service-side channel is available
+     * (registered).
+     */
+    public static final int HANDSHAKE_RESP_CONNECTED = 0;
+    /**
+     * Handshake query response on condition that service-side channel is not
+     * available (not registered).
+     */
+    public static final int HANDSHAKE_RESP_NOPORT = 1;
+    /**
+     * Handshake query response on condition that there is already an existing
+     * emulator connection for this channel. Emulator should stop connection
+     * attempts in this case.
+     */
+    public static final int HANDSHAKE_RESP_DUP = -1;
+    /** Response to an unknown handshake query type. */
+    public static final int HANDSHAKE_RESP_QUERY_UNKNOWN = -2;
+
+    /*
+     * Constants related to multi-touch emulation.
+     */
+
+    /** Received frame is JPEG image. */
+    public static final int MT_FRAME_JPEG = 1;
+    /** Received frame is RGB565 bitmap. */
+    public static final int MT_FRAME_RGB565 = 2;
+    /** Received frame is RGB888 bitmap. */
+    public static final int MT_FRAME_RGB888 = 3;
+
+    /** Pointer(s) moved. */
+    public static final int MT_MOVE = 1;
+    /** First pointer down message. */
+    public static final int MT_FISRT_DOWN = 2;
+    /** Last pointer up message. */
+    public static final int MT_LAST_UP = 3;
+    /** Pointer down message. */
+    public static final int MT_POINTER_DOWN = 4;
+    /** Pointer up message. */
+    public static final int MT_POINTER_UP = 5;
+    /** Sends framebuffer update. */
+    public static final int MT_FB_UPDATE = 6;
+    /** Size of an event entry in the touch event message to the emulator. */
+    public static final int MT_EVENT_ENTRY_SIZE = 16;
+
+    /*
+     * Constants related to sensor emulation.
+     */
+
+    /** Query type for a query that should return the list of available sensors. */
+    public static final int SENSORS_QUERY_LIST = 1;
+    /** Message that starts sensor emulation. */
+    public static final int SENSORS_START = 1;
+    /** Message that stops sensor emulation. */
+    public static final int SENSORS_STOP = 2;
+    /** Message that enables emulation of a particular sensor. */
+    public static final int SENSORS_ENABLE = 3;
+    /** Message that disables emulation of a particular sensor. */
+    public static final int SENSORS_DISABLE = 4;
+    /** Message that delivers sensor events to emulator. */
+    public static final int SENSORS_SENSOR_EVENT = 5;
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java
new file mode 100644
index 0000000..08e6b28
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/lib/Socket.java
@@ -0,0 +1,213 @@
+/*
+ * 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.tools.sdkcontroller.lib;
+
+import android.net.LocalSocket;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+import java.nio.channels.ClosedChannelException;
+
+/**
+ * Encapsulates a connection with the emulator over a UNIX-domain socket.
+ */
+public class Socket {
+    /** UNIX-domain socket connected with the emulator. */
+    private LocalSocket mSocket = null;
+    /** Channel name for the connection established via this socket. */
+    private String mChannelName;
+    /** Endianness of data transferred in this connection. */
+    private ByteOrder mEndian;
+
+    /** Tag for message logging. */
+    private static final String TAG = "SdkControllerSocket";
+    /** Controls debug log. */
+    private static boolean DEBUG = false;
+
+    /**
+     * Constructs Socket instance.
+     *
+     * @param socket Socket connection with the emulator.
+     * @param name Channel port name for this connection.
+     * @param endian Endianness of data transferred in this connection.
+     */
+    public Socket(LocalSocket socket, String name, ByteOrder endian) {
+        mSocket = socket;
+        mChannelName = name;
+        mEndian = endian;
+        if (DEBUG) Log.d(TAG, "Socket is constructed for " + mChannelName);
+    }
+
+    /**
+     * Gets connection status of this socket.
+     *
+     * @return true if socket is connected, or false if socket is not connected.
+     */
+    public boolean isConnected() {
+        return mSocket != null;
+    }
+
+    /**
+     * Gets channel name for this socket.
+     *
+     * @return Channel name for this socket.
+     */
+    public String getChannelName() {
+        return mChannelName;
+    }
+
+    /**
+     * Gets endianness of data transferred via this socket.
+     *
+     * @return Endianness of data transferred via this socket.
+     */
+    public ByteOrder getEndian() {
+        return mEndian;
+    }
+
+    /**
+     * Sends data to the socket.
+     *
+     * @param data Data to send. Data size is defined by the length of the
+     *            array.
+     * @throws IOException
+     */
+    public void send(byte[] data) throws IOException {
+        // Use local copy of the socket, ensuring it's not going to NULL while
+        // we're working with it. If it gets closed, while we're in the middle
+        // of data transfer - it's OK, since it will produce an exception, and
+        // the caller will gracefully handle it.
+        //
+        // Same technique is used everywhere in this class where mSocket member
+        // is touched.
+        LocalSocket socket = mSocket;
+        if (socket == null) {
+            Logw("'send' request on closed Socket " + mChannelName);
+            throw new ClosedChannelException();
+        }
+        socket.getOutputStream().write(data);
+    }
+
+    /**
+     * Sends data to the socket.
+     *
+     * @param data Data to send.
+     * @param offset The start position in data from where to get bytes.
+     * @param len The number of bytes from data to write to this socket.
+     * @throws IOException
+     */
+    public void send(byte[] data, int offset, int len) throws IOException {
+        LocalSocket socket = mSocket;
+        if (socket == null) {
+            Logw("'send' request on closed Socket " + mChannelName);
+            throw new ClosedChannelException();
+        }
+        socket.getOutputStream().write(data, offset, len);
+    }
+
+    /**
+     * Receives data from the socket.
+     *
+     * @param socket Socket from where to receive data.
+     * @param data Array where to save received data.
+     * @param len Number of bytes to receive.
+     * @throws IOException
+     */
+    public static void receive(LocalSocket socket, byte[] data, int len) throws IOException {
+        final InputStream is = socket.getInputStream();
+        int received = 0;
+        while (received != len) {
+            final int chunk = is.read(data, received, len - received);
+            if (chunk < 0) {
+                throw new IOException(
+                        "I/O failure while receiving SDK controller data from socket.");
+            }
+            received += chunk;
+        }
+    }
+
+    /**
+     * Receives data from the socket.
+     *
+     * @param data Array where to save received data.
+     * @param len Number of bytes to receive.
+     * @throws IOException
+     */
+    public void receive(byte[] data, int len) throws IOException {
+        LocalSocket socket = mSocket;
+        if (socket == null) {
+            Logw("'receive' request on closed Socket " + mChannelName);
+            throw new ClosedChannelException();
+        }
+        receive(socket, data, len);
+    }
+
+    /**
+     * Receives data from the socket.
+     *
+     * @param data Array where to save received data. Data size is defined by
+     *            the size of the array.
+     * @throws IOException
+     */
+    public void receive(byte[] data) throws IOException {
+        receive(data, data.length);
+    }
+
+    /**
+     * Closes the socket.
+     *
+     * @return true if socket has been closed in this call, or false if it had
+     *         been already closed when this method has been called.
+     */
+    public boolean close() {
+        // This is the only place in this class where we will null the socket
+        // object. Since this method can be called concurrently from different
+        // threads, lets do this under the lock.
+        LocalSocket socket;
+        synchronized (this) {
+            socket = mSocket;
+            mSocket = null;
+        }
+        if (socket != null) {
+            try {
+                // Force all I/O to stop before closing the socket.
+                socket.shutdownInput();
+                socket.shutdownOutput();
+                socket.close();
+                if (DEBUG) Log.d(TAG, "Socket is closed for " + mChannelName);
+                return true;
+            } catch (IOException e) {
+                Loge("Exception " + e + " while closing Socket for " + mChannelName);
+            }
+        }
+        return false;
+    }
+
+    /***************************************************************************
+     * Logging wrappers
+     **************************************************************************/
+
+    private void Loge(String log) {
+        Log.e(TAG, log);
+    }
+
+    private void Logw(String log) {
+        Log.w(TAG, log);
+    }
+}
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java b/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
index cd35833..705bd07 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/service/ControllerService.java
@@ -17,9 +17,7 @@
 package com.android.tools.sdkcontroller.service;

 

 import java.util.ArrayList;

-import java.util.HashSet;

 import java.util.List;

-import java.util.Set;

 

 import android.app.Activity;

 import android.app.Notification;

@@ -33,33 +31,32 @@
 

 import com.android.tools.sdkcontroller.R;

 import com.android.tools.sdkcontroller.activities.MainActivity;

-import com.android.tools.sdkcontroller.handlers.BaseHandler;

-import com.android.tools.sdkcontroller.handlers.BaseHandler.HandlerType;

-import com.android.tools.sdkcontroller.handlers.MultiTouchHandler;

-import com.android.tools.sdkcontroller.handlers.SensorsHandler;

-import com.android.tools.sdkcontroller.lib.EmulatorConnection;

-import com.android.tools.sdkcontroller.lib.EmulatorConnection.EmulatorConnectionType;

-import com.android.tools.sdkcontroller.lib.EmulatorListener;

+import com.android.tools.sdkcontroller.handlers.MultiTouchChannel;

+import com.android.tools.sdkcontroller.handlers.SensorChannel;

+import com.android.tools.sdkcontroller.lib.Connection;

+import com.android.tools.sdkcontroller.lib.Channel;

 

 /**

  * The background service of the SdkController.

  * There can be only one instance of this.

  * <p/>

- * The service manages a number of action "handlers" which can be seen as individual tasks

- * that the user might want to accomplish, for example "sending sensor data to the emulator"

- * or "sending multi-touch data and displaying an emulator screen".

+ * The service manages a number of SDK controller channels which can be seen as

+ * individual tasks that the user might want to accomplish, for example "sending

+ * sensor data to the emulator" or "sending multi-touch data and displaying an

+ * emulator screen".

  * <p/>

- * Each handler currently has its own emulator connection associated to it (cf class

- * {@code EmuCnxHandler} below. However our goal is to later move to a single connection channel

- * with all data multiplexed on top of it.

+ * Each channel connects to the emulator via UNIX-domain socket that is bound to

+ * "android.sdk.controller" port. Connection class provides a socket server that

+ * listens to emulator connections on this port, and binds new connection with a

+ * channel, based on channel name.

  * <p/>

- * All the handlers are created when the service starts, and whether the emulator connection

- * is successful or not, and whether there's any UI to control it. It's up to the handlers

- * to deal with these specific details. <br/>

- * For example the {@link SensorsHandler} initializes its sensor list as soon as created

- * and then tries to send data as soon as there's an emulator connection.

- * On the other hand the {@link MultiTouchHandler} lays dormant till there's an UI interacting

- * with it.

+ * All the channels are created when the service starts, and whether the emulator

+ * connection is successful or not, and whether there's any UI to control it.

+ * It's up to the channels to deal with these specific details. <br/>

+ * For example the {@link SensorChannel} initializes its sensor list as soon as

+ * created and then tries to send data as soon as there's an emulator

+ * connection. On the other hand the {@link MultiTouchChannel} lays dormant till

+ * there's an UI interacting with it.

  */

 public class ControllerService extends Service {

 

@@ -68,12 +65,17 @@
      * http://developer.android.com/reference/android/app/Service.html#LocalServiceSample

      */

 

+    /** Tag for logging messages. */

     public static String TAG = ControllerService.class.getSimpleName();

+    /** Controls debug log. */

     private static boolean DEBUG = true;

-

     /** Identifier for the notification. */

     private static int NOTIF_ID = 'S' << 24 + 'd' << 16 + 'k' << 8 + 'C' << 0;

 

+    /** Connection to the emulator. */

+    public Connection mConnection;

+

+

     private final IBinder mBinder = new ControllerBinder();

 

     private List<ControllerListener> mListeners = new ArrayList<ControllerListener>();

@@ -86,8 +88,6 @@
     /** Internal error reported by the service. */

     private String mServiceError = "";

 

-    private final Set<EmuCnxHandler> mHandlers = new HashSet<ControllerService.EmuCnxHandler>();

-

     /**

      * Interface that the service uses to notify binded activities.

      * <p/>

@@ -119,7 +119,7 @@
         public void addControllerListener(ControllerListener listener) {

             assert listener != null;

             if (listener != null) {

-                synchronized(mListeners) {

+                synchronized (mListeners) {

                     if (!mListeners.contains(listener)) {

                         mListeners.add(listener);

                     }

@@ -134,7 +134,7 @@
          */

         public void removeControllerListener(ControllerListener listener) {

             assert listener != null;

-            synchronized(mListeners) {

+            synchronized (mListeners) {

                 mListeners.remove(listener);

             }

         }

@@ -149,34 +149,24 @@
         }

 

         /**

-         * Indicates when <em>all</all> the communication channels for all handlers

-         * are properly connected.

+         * Indicates when <em>any</all> of the SDK controller channels is connected

+         * with the emulator.

          *

-         * @return True if all the handler's communication channels are connected.

+         * @return True if any of the SDK controller channels is connected with the

+         *         emulator.

          */

         public boolean isEmuConnected() {

-            for (EmuCnxHandler handler : mHandlers) {

-                if (!handler.isConnected()) {

-                    return false;

-                }

-            }

-            return true;

+            return mConnection.isEmulatorConnected();

         }

 

         /**

-         * Returns the handler for the given type.

+         * Returns the channel instance for the given type.

          *

-         * @param type One of the {@link HandlerType}s. Must not be null.

+         * @param name One of the channel names. Must not be null.

          * @return Null if the type is not found, otherwise the handler's unique instance.

          */

-        public BaseHandler getHandler(HandlerType type) {

-            for (EmuCnxHandler handler : mHandlers) {

-                BaseHandler h = handler.getHandler();

-                if (h.getType() == type) {

-                    return h;

-                }

-            }

-            return null;

+        public Channel getChannel(String name) {

+            return mConnection.getChannel(name);

         }

     }

 

@@ -220,101 +210,10 @@
         super.onDestroy();

     }

 

-    // ------

-

-    /**

-     * Wrapper that associates one {@link EmulatorConnection} with

-     * one {@link BaseHandler}. Ideally we would not need this if all

-     * the action handlers were using the same port, so this wrapper

-     * is just temporary.

-     */

-    private class EmuCnxHandler implements EmulatorListener {

-

-        private EmulatorConnection mCnx;

-        private boolean mConnected;

-        private final BaseHandler mHandler;

-

-        public EmuCnxHandler(BaseHandler handler) {

-            mHandler = handler;

-        }

-

-        @Override

-        public void onEmulatorConnected() {

-            mConnected = true;

-            notifyStatusChanged();

-        }

-

-        @Override

-        public void onEmulatorDisconnected() {

-            mConnected = false;

-            notifyStatusChanged();

-        }

-

-        @Override

-        public String onEmulatorQuery(String query, String param) {

-            if (DEBUG) Log.d(TAG, mHandler.getType().toString() +  " Query " + query);

-            return mHandler.onEmulatorQuery(query, param);

-        }

-

-        @Override

-        public String onEmulatorBlobQuery(byte[] array) {

-            if (DEBUG) Log.d(TAG, mHandler.getType().toString() +  " BlobQuery " + array.length);

-            return mHandler.onEmulatorBlobQuery(array);

-        }

-

-        EmuCnxHandler connect() {

-            assert mCnx == null;

-

-            mCnx = new EmulatorConnection(this);

-

-            // Apps targeting Honeycomb SDK can't do network IO on their main UI

-            // thread. So just start the connection from a thread.

-            Thread t = new Thread(new Runnable() {

-                @Override

-                public void run() {

-                    // This will call onEmulatorBindResult with the result.

-                    mCnx.connect(mHandler.getPort(), EmulatorConnectionType.SYNC_CONNECTION);

-                }

-            }, "EmuCnxH.connect-" + mHandler.getType().toString());

-            t.start();

-

-            return this;

-        }

-

-        @Override

-        public void onEmulatorBindResult(boolean success, Exception e) {

-            if (success) {

-                mHandler.onStart(mCnx, ControllerService.this /*context*/);

-            } else {

-                Log.e(TAG, "EmuCnx failed for " + mHandler.getType(), e);

-                String msg = mHandler.getType().toString() + " failed: " +

-                    (e == null ? "n/a" : e.toString());

-                addError(msg);

-            }

-        }

-

-        void disconnect() {

-            if (mCnx != null) {

-                mHandler.onStop();

-                mCnx.disconnect();

-                mCnx = null;

-            }

-        }

-

-        boolean isConnected() {

-            return mConnected;

-        }

-

-        public BaseHandler getHandler() {

-            return mHandler;

-        }

-    }

-

     private void disconnectAll() {

-        for(EmuCnxHandler handler : mHandlers) {

-            handler.disconnect();

+        if (mConnection != null) {

+            mConnection.disconnect();

         }

-        mHandlers.clear();

     }

 

     /**

@@ -324,9 +223,15 @@
         try {

             disconnectAll();

 

-            assert mHandlers.isEmpty();

-            mHandlers.add(new EmuCnxHandler(new MultiTouchHandler()).connect());

-            mHandlers.add(new EmuCnxHandler(new SensorsHandler()).connect());

+            // Bind to SDK controller port, and start accepting emulator

+            // connections.

+            mConnection = new Connection(ControllerService.this);

+            mConnection.connect();

+

+            // Create and register sensors channel.

+            mConnection.registerChannel(new SensorChannel(ControllerService.this));

+            // Create and register multi-touch channel.

+            mConnection.registerChannel(new MultiTouchChannel(ControllerService.this));

         } catch (Exception e) {

             addError("Connection failed: " + e.toString());

         }

@@ -340,15 +245,15 @@
     }

 

     private void notifyErrorChanged() {

-        synchronized(mListeners) {

+        synchronized (mListeners) {

             for (ControllerListener listener : mListeners) {

                 listener.onErrorChanged();

             }

         }

     }

 

-    private void notifyStatusChanged() {

-        synchronized(mListeners) {

+    public void notifyStatusChanged() {

+        synchronized (mListeners) {

             for (ControllerListener listener : mListeners) {

                 listener.onStatusChanged();

             }

@@ -368,7 +273,7 @@
      * An internal utility method to add a line to the error string and notify listeners.

      * @param error A non-null non-empty error line. \n will be added automatically.

      */

-    private void addError(String error) {

+    public void addError(String error) {

         Log.e(TAG, error);

         if (mServiceError.length() > 0) {

             mServiceError += "\n";

@@ -397,10 +302,10 @@
         Intent intent = new Intent(this, MainActivity.class);

         intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

         PendingIntent pi = PendingIntent.getActivity(

-                this,     //context

-                0,        //requestCode

-                intent,   //intent

-                0         // pending intent flags

+                this,   //context

+                0,      //requestCode

+                intent, //intent

+                0       //pending intent flags

                 );

         n.setLatestEventInfo(this, text, text, pi);

 

diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java b/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
index d612769..30bccdc 100755
--- a/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/views/MultiTouchView.java
@@ -17,6 +17,7 @@
 package com.android.tools.sdkcontroller.views;

 

 import java.io.InputStream;

+import java.nio.ByteBuffer;

 

 import android.content.Context;

 import android.graphics.Bitmap;

@@ -186,19 +187,19 @@
     /**

      * Constructs touch event message to be send to emulator.

      *

-     * @param sb String builder where to construct the message.

+     * @param bb ByteBuffer where to construct the message.

      * @param event Event for which to construct the message.

      * @param ptr_index Index of the motion pointer for which to construct the

      *            message.

      */

-    public void constructEventMessage(StringBuilder sb, MotionEvent event, int ptr_index) {

-        sb.append(" pid=").append(event.getPointerId(ptr_index));

+    public void constructEventMessage(ByteBuffer bb, MotionEvent event, int ptr_index) {

+        bb.putInt(event.getPointerId(ptr_index));

         if (mRotateDisplay == false) {

-            sb.append(" x=").append((int) (event.getX(ptr_index) / mDx));

-            sb.append(" y=").append((int) (event.getY(ptr_index) / mDy));

+            bb.putInt((int) (event.getX(ptr_index) / mDx));

+            bb.putInt((int) (event.getY(ptr_index) / mDy));

         } else {

-            sb.append(" x=").append((int) (event.getY(ptr_index) / mDy));

-            sb.append(" y=").append((int) (getWidth() - event.getX(ptr_index) / mDx));

+            bb.putInt((int) (event.getY(ptr_index) / mDy));

+            bb.putInt((int) (getWidth() - event.getX(ptr_index) / mDx));

         }

         // At the system level the input reader takes integers in the range

         // 0 - 100 for the pressure.

@@ -207,7 +208,7 @@
         if (pressure > 100) {

             pressure = 100;

         }

-        sb.append(" pressure=").append(pressure);

+        bb.putInt(pressure);

     }

 

     /***************************************************************************

diff --git a/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java b/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java
index fa81392..0124000 100644
--- a/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java
+++ b/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java
@@ -59,6 +59,8 @@
 
     /** Shapes that can be used for icon backgrounds */
     public static enum Shape {
+        /** No background */
+        NONE("none"),
         /** Circular background */
         CIRCLE("circle"),
         /** Square background */
diff --git a/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java b/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java
index 4e0534d..b3e327b 100644
--- a/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java
+++ b/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java
@@ -42,12 +42,17 @@
             density = launcherOptions.density.getResourceValue();
         }
         String shape = launcherOptions.shape.id;
-        BufferedImage mBackImage = context.loadImageResource("/images/launcher_stencil/"
+        BufferedImage mBackImage = null;
+        BufferedImage mForeImage = null;
+        BufferedImage mMaskImage = null;
+        if (launcherOptions.shape != Shape.NONE) {
+            mBackImage = context.loadImageResource("/images/launcher_stencil/"
                 + shape + "/" + density + "/back.png");
-        BufferedImage mForeImage = context.loadImageResource("/images/launcher_stencil/"
+            mForeImage = context.loadImageResource("/images/launcher_stencil/"
                 + shape + "/" + density + "/" + launcherOptions.style.id + ".png");
-        BufferedImage mMaskImage = context.loadImageResource("/images/launcher_stencil/"
+            mMaskImage = context.loadImageResource("/images/launcher_stencil/"
                 + shape + "/" + density + "/mask.png");
+        }
 
         float scaleFactor = GraphicGenerator.getMdpiScaleFactor(launcherOptions.density);
         if (launcherOptions.isWebGraphic) {
@@ -59,14 +64,18 @@
 
         BufferedImage outImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height);
         Graphics2D g = (Graphics2D) outImage.getGraphics();
-        g.drawImage(mBackImage, 0, 0, null);
+        if (mBackImage != null) {
+            g.drawImage(mBackImage, 0, 0, null);
+        }
 
         BufferedImage tempImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height);
         Graphics2D g2 = (Graphics2D) tempImage.getGraphics();
-        g2.drawImage(mMaskImage, 0, 0, null);
-        g2.setComposite(AlphaComposite.SrcAtop);
-        g2.setPaint(new Color(launcherOptions.backgroundColor));
-        g2.fillRect(0, 0, imageRect.width, imageRect.height);
+        if (mMaskImage != null) {
+            g2.drawImage(mMaskImage, 0, 0, null);
+            g2.setComposite(AlphaComposite.SrcAtop);
+            g2.setPaint(new Color(launcherOptions.backgroundColor));
+            g2.fillRect(0, 0, imageRect.width, imageRect.height);
+        }
 
         if (launcherOptions.crop) {
             Util.drawCenterCrop(g2, launcherOptions.sourceImage, targetRect);
@@ -75,7 +84,9 @@
         }
 
         g.drawImage(tempImage, 0, 0, null);
-        g.drawImage(mForeImage, 0, 0, null);
+        if (mForeImage != null) {
+            g.drawImage(mForeImage, 0, 0, null);
+        }
 
         g.dispose();
         g2.dispose();
diff --git a/bash_completion/adb.bash b/bash_completion/adb.bash
index 12b36ef..46ed208 100644
--- a/bash_completion/adb.bash
+++ b/bash_completion/adb.bash
@@ -66,7 +66,7 @@
             COMPREPLY=( $(compgen -W "$OPTIONS $COMMAND" -- "$cur") )
             ;;
         OPT_SERIAL_ARG)
-            local devices=$(command adb devices '2>' /dev/null | grep -v "List of devices" | awk '{ print $1 }')
+            local devices=$(command adb devices 2> /dev/null | grep -v "List of devices" | awk '{ print $1 }')
             COMPREPLY=( $(compgen -W "${devices}" -- ${cur}) )
             ;;
         COMMAND)
diff --git a/build/product_sdk.mk b/build/product_sdk.mk
index 7802211..f96bcf5 100644
--- a/build/product_sdk.mk
+++ b/build/product_sdk.mk
@@ -25,50 +25,35 @@
 # packaged in the SDK.
 #
 
-# Host tools that are parts of the SDK.
+# Host tools and java libraries that are parts of the SDK.
 PRODUCT_PACKAGES += \
 	android \
-	emulator \
-	ddms \
-	draw9patch \
-	hierarchyviewer \
-	lint \
-	mksdcard \
-	monkeyrunner \
-	monitor \
-	traceview
-
-# Native host Java libraries that are parts of the SDK.
-PRODUCT_PACKAGES += \
 	androidprefs \
 	annotations \
 	anttasks \
 	archquery \
-	commons-compress-1.0 \
+	assetstudio \
 	common-tests \
-	ddms \
 	ddmlib \
 	ddmlib-tests \
+	ddms \
 	ddmuilib \
 	draw9patch \
+	emulator \
 	hierarchyviewer \
+	ide_common \
 	lint \
-	traceview \
-	jcommon-1.0.12 \
-	jfreechart-1.0.9 \
-	jfreechart-1.0.9-swt \
+	manifmerger \
+	mksdcard \
+	monitor \
 	monkeyrunner \
-	org.eclipse.core.commands_3.4.0.I20080509-2000 \
-	org.eclipse.equinox.common_3.4.0.v20080421-2006 \
-	org.eclipse.jface_3.4.2.M20090107-0800 \
-	osgi \
 	ninepatch-tests \
-	sdkmanager \
+	rule_api \
 	sdklib \
 	sdklib-tests \
+	sdkmanager \
 	sdkstats \
 	sdkuilib \
 	sdkuilib-tests \
-	swing-worker-1.1 \
-	swtmenubar
-
+	swtmenubar \
+	traceview
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatBufferChangeListener.java
similarity index 60%
rename from ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java
rename to ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatBufferChangeListener.java
index 2caf50d..1a547c7 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatBufferChangeListener.java
@@ -19,11 +19,13 @@
 import java.util.List;
 
 /**
- * Listeners interested in log cat messages should implement this interface.
+ * Listeners interested in changes in the logcat buffer should implement this interface.
  */
-public interface ILogCatMessageEventListener {
-    /** Called on reception of logcat messages.
-     * @param receivedMessages list of messages received
+public interface ILogCatBufferChangeListener {
+    /**
+     * Called when the logcat buffer changes.
+     * @param addedMessages list of messages that were added to the logcat buffer
+     * @param deletedMessages list of messages that were removed from the logcat buffer
      */
-    void messageReceived(List<LogCatMessage> receivedMessages);
+    void bufferChanged(List<LogCatMessage> addedMessages, List<LogCatMessage> deletedMessages);
 }
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilter.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilter.java
index 509449d..7bdd98a 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilter.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilter.java
@@ -15,7 +15,6 @@
  */
 package com.android.ddmuilib.logcat;
 
-import com.android.ddmlib.Log;
 import com.android.ddmlib.Log.LogLevel;
 
 import java.util.ArrayList;
@@ -96,8 +95,6 @@
                 mAppNamePattern = Pattern.compile(mAppName, getPatternCompileFlags(mAppName));
                 mCheckAppName = true;
             } catch (PatternSyntaxException e) {
-                Log.e("LogCatFilter", "Ignoring invalid app name regex.");
-                Log.e("LogCatFilter", e.getMessage());
                 mCheckAppName = false;
             }
         }
@@ -107,8 +104,6 @@
                 mTagPattern = Pattern.compile(mTag, getPatternCompileFlags(mTag));
                 mCheckTag = true;
             } catch (PatternSyntaxException e) {
-                Log.e("LogCatFilter", "Ignoring invalid tag regex.");
-                Log.e("LogCatFilter", e.getMessage());
                 mCheckTag = false;
             }
         }
@@ -118,8 +113,6 @@
                 mTextPattern = Pattern.compile(mText, getPatternCompileFlags(mText));
                 mCheckText = true;
             } catch (PatternSyntaxException e) {
-                Log.e("LogCatFilter", "Ignoring invalid text regex.");
-                Log.e("LogCatFilter", e.getMessage());
                 mCheckText = false;
             }
         }
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java
index 164f484..68c08d4 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java
@@ -39,10 +39,6 @@
      */
     @Override
     public Object[] getElements(Object model) {
-        if (model instanceof List<?>) {
-            return ((List<?>) model).toArray();
-        }
-        return null;
+        return ((List<?>) model).toArray();
     }
-
 }
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java
index f68ee05..39b3fa9 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java
@@ -165,7 +165,7 @@
      * on the dialog is valid or not. If it is not valid, the message
      * field stores the reason why it isn't.
      */
-    private final class DialogStatus {
+    private static final class DialogStatus {
         final boolean valid;
         final String message;
 
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java
deleted file mode 100644
index bd7b520..0000000
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ddmuilib.logcat;
-
-import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-/**
- * A JFace content provider for the LogCat log messages, used in the {@link LogCatPanel}.
- */
-public final class LogCatMessageContentProvider implements IStructuredContentProvider {
-    @Override
-    public void dispose() {
-    }
-
-    @Override
-    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-    }
-
-    @Override
-    public Object[] getElements(Object model) {
-        if (model instanceof LogCatMessageList) {
-            Object[] e = ((LogCatMessageList) model).toArray();
-            return e;
-        }
-
-        return null;
-    }
-}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java
deleted file mode 100644
index 1d83a9c..0000000
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ddmuilib.logcat;
-
-import com.android.ddmlib.Log.LogLevel;
-
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.jface.viewers.ViewerCell;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.Point;
-
-/**
- * A JFace Column label provider for the LogCat log messages. It expects elements of type
- * {@link LogCatMessage}.
- */
-public final class LogCatMessageLabelProvider extends ColumnLabelProvider {
-    private static final int INDEX_LOGLEVEL = 0;
-    private static final int INDEX_LOGTIME = 1;
-    private static final int INDEX_PID = 2;
-    private static final int INDEX_APPNAME = 3;
-    private static final int INDEX_TAG = 4;
-    private static final int INDEX_TEXT = 5;
-
-    /* Default Colors for different log levels. */
-    private static final Color INFO_MSG_COLOR =    new Color(null, 0, 127, 0);
-    private static final Color DEBUG_MSG_COLOR =   new Color(null, 0, 0, 127);
-    private static final Color ERROR_MSG_COLOR =   new Color(null, 255, 0, 0);
-    private static final Color WARN_MSG_COLOR =    new Color(null, 255, 127, 0);
-    private static final Color VERBOSE_MSG_COLOR = new Color(null, 0, 0, 0);
-
-    /** Amount of pixels to shift the tooltip by. */
-    private static final Point LOGCAT_TOOLTIP_SHIFT = new Point(10, 10);
-
-    private Font mLogFont;
-    private int mWrapWidth = 100;
-
-    /**
-     * Construct a column label provider for the logcat table.
-     * @param font default font to use
-     */
-    public LogCatMessageLabelProvider(Font font) {
-        mLogFont = font;
-    }
-
-    private String getCellText(LogCatMessage m, int columnIndex) {
-        switch (columnIndex) {
-            case INDEX_LOGLEVEL:
-                return Character.toString(m.getLogLevel().getPriorityLetter());
-            case INDEX_LOGTIME:
-                return m.getTime();
-            case INDEX_PID:
-                return m.getPid();
-            case INDEX_APPNAME:
-                return m.getAppName();
-            case INDEX_TAG:
-                return m.getTag();
-            case INDEX_TEXT:
-                return m.getMessage();
-            default:
-                return "";
-        }
-    }
-
-    @Override
-    public void update(ViewerCell cell) {
-        Object element = cell.getElement();
-        if (!(element instanceof LogCatMessage)) {
-            return;
-        }
-        LogCatMessage m = (LogCatMessage) element;
-
-        String text = getCellText(m, cell.getColumnIndex());
-        cell.setText(text);
-        cell.setFont(mLogFont);
-        cell.setForeground(getForegroundColor(m));
-    }
-
-    private Color getForegroundColor(LogCatMessage m) {
-        LogLevel l = m.getLogLevel();
-
-        if (l.equals(LogLevel.VERBOSE)) {
-            return VERBOSE_MSG_COLOR;
-        } else if (l.equals(LogLevel.INFO)) {
-            return INFO_MSG_COLOR;
-        } else if (l.equals(LogLevel.DEBUG)) {
-            return DEBUG_MSG_COLOR;
-        } else if (l.equals(LogLevel.ERROR)) {
-            return ERROR_MSG_COLOR;
-        } else if (l.equals(LogLevel.WARN)) {
-            return WARN_MSG_COLOR;
-        }
-
-        return null;
-    }
-
-    public void setFont(Font preferredFont) {
-        if (mLogFont != null) {
-            mLogFont.dispose();
-        }
-
-        mLogFont = preferredFont;
-    }
-
-    public void setMinimumLengthForToolTips(int widthInChars) {
-        mWrapWidth  = widthInChars;
-    }
-
-    /**
-     * Obtain the tool tip to show for a particular logcat message.
-     * We display a tool tip only for messages longer than the width set by
-     * {@link #setMinimumLengthForToolTips(int)}.
-     */
-    @Override
-    public String getToolTipText(Object element) {
-        String text = element.toString();
-        if (text.length() > mWrapWidth) {
-            return text;
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public Point getToolTipShift(Object object) {
-        // The only reason we override this method is that the default shift amounts
-        // don't seem to work on OS X Lion.
-        return LOGCAT_TOOLTIP_SHIFT;
-    }
-}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java
index 0d0e3c2..080dbc1 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java
@@ -16,6 +16,8 @@
 
 package com.android.ddmuilib.logcat;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 
@@ -33,7 +35,6 @@
 
     private int mFifoSize;
     private BlockingQueue<LogCatMessage> mQ;
-    private LogCatMessage[] mQArray;
 
     /**
      * Construct an empty message list.
@@ -43,7 +44,6 @@
         mFifoSize = maxMessages;
 
         mQ = new ArrayBlockingQueue<LogCatMessage>(mFifoSize);
-        mQArray = new LogCatMessage[mFifoSize];
     }
 
     /**
@@ -64,8 +64,6 @@
                 mQ.offer(curMessages[i]);
             }
         }
-
-        mQArray = new LogCatMessage[mFifoSize];
     }
 
     /**
@@ -73,17 +71,30 @@
      * message will be popped off of it.
      * @param m log to be inserted
      */
-    public synchronized void appendMessage(final LogCatMessage m) {
-        if (mQ.remainingCapacity() == 0) {
-            /* make space by removing the first entry */
-            mQ.poll();
+    public synchronized void appendMessages(final List<LogCatMessage> messages) {
+        ensureSpace(messages.size());
+        for (LogCatMessage m: messages) {
+            mQ.offer(m);
         }
-        mQ.offer(m);
     }
 
     /**
-     * Returns the number of additional elements that this queue can 
-     * ideally (in the absence of memory or resource constraints) 
+     * Ensure that there is sufficient space for given number of messages.
+     * @return list of messages that were deleted to create additional space.
+     */
+    public synchronized List<LogCatMessage> ensureSpace(int messageCount) {
+        List<LogCatMessage> l = new ArrayList<LogCatMessage>(messageCount);
+
+        while (mQ.remainingCapacity() < messageCount) {
+            l.add(mQ.poll());
+        }
+
+        return l;
+    }
+
+    /**
+     * Returns the number of additional elements that this queue can
+     * ideally (in the absence of memory or resource constraints)
      * accept without blocking.
      * @return the remaining capacity
      */
@@ -91,25 +102,13 @@
         return mQ.remainingCapacity();
     }
 
-    /**
-     * Clear all messages in the list.
-     */
+    /** Clear all messages in the list. */
     public synchronized void clear() {
         mQ.clear();
     }
 
-    /**
-     * Obtain all the messages currently present in the list.
-     * @return array containing all the log messages
-     */
-    public Object[] toArray() {
-        if (mQ.size() == mFifoSize) {
-            /*
-             * Once the queue is full, it stays full until the user explicitly clears
-             * all the logs. Optimize for this case by not reallocating the array.
-             */
-            return mQ.toArray(mQArray);
-        }
-        return mQ.toArray();
+    /** Obtain a copy of the message list. */
+    public synchronized List<LogCatMessage> getAllMessages() {
+        return new ArrayList<LogCatMessage>(mQ);
     }
 }
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
index 7aa0328..c2942d0 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
@@ -25,17 +25,14 @@
 import com.android.ddmuilib.SelectionDependentPanel;
 import com.android.ddmuilib.TableHelper;
 
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.MenuManager;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.preference.PreferenceConverter;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
 import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.ViewerCell;
-import org.eclipse.jface.window.ToolTip;
 import org.eclipse.jface.window.Window;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.SashForm;
@@ -50,9 +47,12 @@
 import org.eclipse.swt.events.ModifyListener;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.FontData;
 import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Combo;
@@ -63,7 +63,7 @@
 import org.eclipse.swt.widgets.FileDialog;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Menu;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableColumn;
 import org.eclipse.swt.widgets.TableItem;
@@ -78,12 +78,14 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
 
 /**
  * LogCatPanel displays a table listing the logcat messages.
  */
 public final class LogCatPanel extends SelectionDependentPanel
-                        implements ILogCatMessageEventListener {
+                        implements ILogCatBufferChangeListener {
     /** Preference key to use for storing list of logcat filters. */
     public static final String LOGCAT_FILTERS_LIST = "logcat.view.filters.list";
 
@@ -121,20 +123,26 @@
     private static final String IMAGE_SAVE_LOG_TO_FILE = "save.png"; //$NON-NLS-1$
     private static final String IMAGE_CLEAR_LOG = "clear.png"; //$NON-NLS-1$
     private static final String IMAGE_DISPLAY_FILTERS = "displayfilters.png"; //$NON-NLS-1$
-    private static final String IMAGE_PAUSE_LOGCAT = "pause_logcat.png"; //$NON-NLS-1$
+    private static final String IMAGE_SCROLL_LOCK = "pause_logcat.png"; //$NON-NLS-1$
 
     private static final int[] WEIGHTS_SHOW_FILTERS = new int[] {15, 85};
     private static final int[] WEIGHTS_LOGCAT_ONLY = new int[] {0, 100};
 
+    /** Index of the default filter in the saved filters column. */
+    private static final int DEFAULT_FILTER_INDEX = 0;
+
+    /* Text colors for the filter box */
+    private static final Color VALID_FILTER_REGEX_COLOR =
+            Display.getDefault().getSystemColor(SWT.COLOR_BLACK);
+    private static final Color INVALID_FILTER_REGEX_COLOR =
+            Display.getDefault().getSystemColor(SWT.COLOR_RED);
+
     private LogCatReceiver mReceiver;
     private IPreferenceStore mPrefStore;
 
     private List<LogCatFilter> mLogCatFilters;
     private int mCurrentSelectedFilterIndex;
 
-    private int mRemovedEntriesCount = 0;
-    private int mPreviousRemainingCapacity = 0;
-
     private ToolItem mNewFilterToolItem;
     private ToolItem mDeleteFilterToolItem;
     private ToolItem mEditFilterToolItem;
@@ -143,28 +151,40 @@
     private Combo mLiveFilterLevelCombo;
     private Text mLiveFilterText;
 
-    private TableViewer mViewer;
+    private List<LogCatFilter> mCurrentFilters = Collections.emptyList();
+
+    private Table mTable;
 
     private boolean mShouldScrollToLatestLog = true;
-    private ToolItem mPauseLogcatCheckBox;
-    private boolean mLastItemPainted = false;
+    private ToolItem mScrollLockCheckBox;
 
     private String mLogFileExportFolder;
-    private LogCatMessageLabelProvider mLogCatMessageLabelProvider;
+
+    private Font mFont;
+    private int mWrapWidthInChars;
 
     private SashForm mSash;
 
+    // messages added since last refresh, synchronized on mLogBuffer
+    private List<LogCatMessage> mLogBuffer;
+
+    // # of messages deleted since last refresh, synchronized on mLogBuffer
+    private int mDeletedLogCount;
+
     /**
      * Construct a logcat panel.
      * @param prefStore preference store where UI preferences will be saved
      */
     public LogCatPanel(IPreferenceStore prefStore) {
         mPrefStore = prefStore;
+        mLogBuffer = new ArrayList<LogCatMessage>(LogCatMessageList.MAX_MESSAGES_DEFAULT);
 
         initializeFilters();
 
         setupDefaultPreferences();
         initializePreferenceUpdateListeners();
+
+        mFont = getFontFromPrefStore();
     }
 
     private void initializeFilters() {
@@ -196,15 +216,24 @@
             @Override
             public void propertyChange(PropertyChangeEvent event) {
                 String changedProperty = event.getProperty();
-
                 if (changedProperty.equals(LogCatPanel.LOGCAT_VIEW_FONT_PREFKEY)) {
-                    mLogCatMessageLabelProvider.setFont(getFontFromPrefStore());
-                    refreshLogCatTable();
-                } else if (changedProperty.equals(
-                        LogCatMessageList.MAX_MESSAGES_PREFKEY)) {
+                    if (mFont != null) {
+                        mFont.dispose();
+                    }
+                    mFont = getFontFromPrefStore();
+                    recomputeWrapWidth();
+                    Display.getDefault().syncExec(new Runnable() {
+                        @Override
+                        public void run() {
+                            for (TableItem it: mTable.getItems()) {
+                                it.setFont(mFont);
+                            }
+                        }
+                    });
+                } else if (changedProperty.equals(LogCatMessageList.MAX_MESSAGES_PREFKEY)) {
                     mReceiver.resizeFifo(mPrefStore.getInt(
                             LogCatMessageList.MAX_MESSAGES_PREFKEY));
-                    refreshLogCatTable();
+                    reloadLogBuffer();
                 }
             }
         });
@@ -246,7 +275,7 @@
 
         mReceiver = LogCatReceiverFactory.INSTANCE.newReceiver(device, mPrefStore);
         mReceiver.addMessageReceivedEventListener(this);
-        mViewer.setInput(mReceiver.getMessages());
+        reloadLogBuffer();
 
         // Always scroll to last line whenever the selected device changes.
         // Run this in a separate async thread to give the table some time to update after the
@@ -353,9 +382,11 @@
         });
     }
 
-    private void addNewFilter() {
+    private void addNewFilter(String defaultTag, String defaultText, String defaultPid,
+            String defaultAppName, LogLevel defaultLevel) {
         LogCatFilterSettingsDialog d = new LogCatFilterSettingsDialog(
                 Display.getCurrent().getActiveShell());
+        d.setDefaults("", defaultTag, defaultText, defaultPid, defaultAppName, defaultLevel);
         if (d.open() != Window.OK) {
             return;
         }
@@ -378,6 +409,11 @@
         saveFilterPreferences();
     }
 
+    private void addNewFilter() {
+        addNewFilter("", "", "",
+                "", LogLevel.VERBOSE);
+    }
+
     private void deleteSelectedFilter() {
         int selectedIndex = mFiltersTableViewer.getTable().getSelectionIndex();
         if (selectedIndex <= 0) {
@@ -430,7 +466,7 @@
      * @param appName application name to filter by
      */
     public void selectTransientAppFilter(String appName) {
-        assert mViewer.getTable().getDisplay().getThread() == Thread.currentThread();
+        assert mTable.getDisplay().getThread() == Thread.currentThread();
 
         LogCatFilter f = findTransientAppFilter(appName);
         if (f == null) {
@@ -463,8 +499,11 @@
 
     private void selectFilterAt(final int index) {
         mFiltersTableViewer.refresh();
-        mFiltersTableViewer.getTable().setSelection(index);
-        filterSelectionChanged();
+
+        if (index != mFiltersTableViewer.getTable().getSelectionIndex()) {
+            mFiltersTableViewer.getTable().setSelection(index);
+            filterSelectionChanged();
+        }
     }
 
     private void createFiltersTable(Composite parent) {
@@ -501,11 +540,7 @@
         createLogcatViewTable(c);
     }
 
-    /**
-     * Create the search bar at the top of the logcat messages table.
-     * FIXME: Currently, this feature is incomplete: The UI elements are created, but they
-     * are all set to disabled state.
-     */
+    /** Create the search bar at the top of the logcat messages table. */
     private void createLiveFilters(Composite parent) {
         Composite c = new Composite(parent, SWT.NONE);
         c.setLayout(new GridLayout(3, false));
@@ -518,6 +553,7 @@
         mLiveFilterText.addModifyListener(new ModifyListener() {
             @Override
             public void modifyText(ModifyEvent arg0) {
+                updateFilterTextColor();
                 updateAppliedFilters();
             }
         });
@@ -557,8 +593,6 @@
                     mReceiver.clearMessages();
                     refreshLogCatTable();
 
-                    mRemovedEntriesCount = 0;
-
                     // the filters view is not cleared unless the filters are re-applied.
                     updateAppliedFilters();
                 }
@@ -580,21 +614,34 @@
             }
         });
 
-        mPauseLogcatCheckBox = new ToolItem(toolBar, SWT.CHECK);
-        mPauseLogcatCheckBox.setImage(
-                ImageLoader.getDdmUiLibLoader().loadImage(IMAGE_PAUSE_LOGCAT,
+        mScrollLockCheckBox = new ToolItem(toolBar, SWT.CHECK);
+        mScrollLockCheckBox.setImage(
+                ImageLoader.getDdmUiLibLoader().loadImage(IMAGE_SCROLL_LOCK,
                         toolBar.getDisplay()));
-        mPauseLogcatCheckBox.setSelection(false);
-        mPauseLogcatCheckBox.setToolTipText("Pause receiving new logcat messages.");
-        mPauseLogcatCheckBox.addSelectionListener(new SelectionAdapter() {
+        mScrollLockCheckBox.setSelection(false);
+        mScrollLockCheckBox.setToolTipText("Scroll Lock");
+        mScrollLockCheckBox.addSelectionListener(new SelectionAdapter() {
             @Override
             public void widgetSelected(SelectionEvent event) {
-                boolean pauseLogcat = mPauseLogcatCheckBox.getSelection();
-                setScrollToLatestLog(!pauseLogcat, false);
+                boolean scrollLock = mScrollLockCheckBox.getSelection();
+                setScrollToLatestLog(!scrollLock);
             }
         });
     }
 
+    /** Sets the foreground color of filter text based on whether the regex is valid. */
+    private void updateFilterTextColor() {
+        String text = mLiveFilterText.getText();
+        Color c;
+        try {
+            Pattern.compile(text.trim());
+            c = VALID_FILTER_REGEX_COLOR;
+        } catch (PatternSyntaxException e) {
+            c = INVALID_FILTER_REGEX_COLOR;
+        }
+        mLiveFilterText.setForeground(c);
+    }
+
     private void updateFiltersColumn(boolean showFilters) {
         if (showFilters) {
             mSash.setWeights(WEIGHTS_SHOW_FILTERS);
@@ -620,13 +667,13 @@
         Thread t = new Thread(new Runnable() {
             @Override
             public void run() {
+                BufferedWriter w = null;
                 try {
-                    BufferedWriter w = new BufferedWriter(new FileWriter(fName));
+                    w = new BufferedWriter(new FileWriter(fName));
                     for (LogCatMessage m : selectedMessages) {
                         w.append(m.toString());
                         w.newLine();
                     }
-                    w.close();
                 } catch (final IOException e) {
                     Display.getDefault().asyncExec(new Runnable() {
                         @Override
@@ -637,6 +684,14 @@
                                             + e.getMessage());
                         }
                     });
+                } finally {
+                    if (w != null) {
+                        try {
+                            w.close();
+                        } catch (IOException e) {
+                            // ignore
+                        }
+                    }
                 }
             }
         });
@@ -675,44 +730,25 @@
     }
 
     private List<LogCatMessage> getSelectedLogCatMessages() {
-        Table table = mViewer.getTable();
-        int[] indices = table.getSelectionIndices();
+        int[] indices = mTable.getSelectionIndices();
         Arrays.sort(indices); /* Table.getSelectionIndices() does not specify an order */
 
-        // Get items from the table's input as opposed to getting each table item's data.
-        // Retrieving table item's data can return NULL in case of a virtual table if the item
-        // has not been displayed yet.
-        Object input = mViewer.getInput();
-        if (!(input instanceof LogCatMessageList)) {
-            return Collections.emptyList();
-        }
-
-        List<LogCatMessage> filteredItems = applyCurrentFilters((LogCatMessageList) input);
         List<LogCatMessage> selectedMessages = new ArrayList<LogCatMessage>(indices.length);
         for (int i : indices) {
-            // consider removed logcat message entries
-            i -= mRemovedEntriesCount;
-            if (i >= 0 && i < filteredItems.size()) {
-                LogCatMessage m = filteredItems.get(i);
-                selectedMessages.add(m);
+            Object data = mTable.getItem(i).getData();
+            if (data instanceof LogCatMessage) {
+                selectedMessages.add((LogCatMessage) data);
             }
         }
 
         return selectedMessages;
     }
 
-    private List<LogCatMessage> applyCurrentFilters(LogCatMessageList msgList) {
-        Object[] items = msgList.toArray();
-        List<LogCatMessage> filteredItems = new ArrayList<LogCatMessage>(items.length);
-        List<LogCatViewerFilter> filters = getFiltersToApply();
+    private List<LogCatMessage> applyCurrentFilters(List<LogCatMessage> msgList) {
+        List<LogCatMessage> filteredItems = new ArrayList<LogCatMessage>(msgList.size());
 
-        for (Object item : items) {
-            if (!(item instanceof LogCatMessage)) {
-                continue;
-            }
-
-            LogCatMessage msg = (LogCatMessage) item;
-            if (!isMessageFiltered(msg, filters)) {
+        for (LogCatMessage msg: msgList) {
+            if (isMessageAccepted(msg, mCurrentFilters)) {
                 filteredItems.add(msg);
             }
         }
@@ -720,33 +756,30 @@
         return filteredItems;
     }
 
-    private boolean isMessageFiltered(LogCatMessage msg, List<LogCatViewerFilter> filters) {
-        for (LogCatViewerFilter f : filters) {
-            if (!f.select(null, null, msg)) {
-                // message does not make it through this filter
-                return true;
+    private boolean isMessageAccepted(LogCatMessage msg, List<LogCatFilter> filters) {
+        for (LogCatFilter f : filters) {
+            if (!f.matches(msg)) {
+                // not accepted by this filter
+                return false;
             }
         }
 
-        return false;
+        // accepted by all filters
+        return true;
     }
 
     private void createLogcatViewTable(Composite parent) {
-        // The SWT.VIRTUAL bit causes the table to be rendered faster. However it makes all rows
-        // to be of the same height, thereby clipping any rows with multiple lines of text.
-        // In such a case, users can view the full text by hovering over the item and looking at
-        // the tooltip.
-        final Table table = new Table(parent, SWT.FULL_SELECTION | SWT.MULTI | SWT.VIRTUAL);
-        mViewer = new TableViewer(table);
+        mTable = new Table(parent, SWT.FULL_SELECTION | SWT.MULTI);
 
-        table.setLayoutData(new GridData(GridData.FILL_BOTH));
-        table.getHorizontalBar().setVisible(true);
+        mTable.setLayoutData(new GridData(GridData.FILL_BOTH));
+        mTable.getHorizontalBar().setVisible(true);
 
         /** Columns to show in the table. */
         String[] properties = {
                 "Level",
                 "Time",
                 "PID",
+                "TID",
                 "Application",
                 "Tag",
                 "Text",
@@ -758,32 +791,31 @@
                 "    ",
                 "    00-00 00:00:00.0000 ",
                 "    0000",
+                "    0000",
                 "    com.android.launcher",
                 "    SampleTagText",
                 "    Log Message field should be pretty long by default. As long as possible for correct display on Mac.",
         };
 
-        mLogCatMessageLabelProvider = new LogCatMessageLabelProvider(getFontFromPrefStore());
         for (int i = 0; i < properties.length; i++) {
-            TableColumn tc = TableHelper.createTableColumn(mViewer.getTable(),
+            TableHelper.createTableColumn(mTable,
                     properties[i],                      /* Column title */
                     SWT.LEFT,                           /* Column Style */
                     sampleText[i],                      /* String to compute default col width */
                     getColPreferenceKey(properties[i]), /* Preference Store key for this column */
                     mPrefStore);
-            TableViewerColumn tvc = new TableViewerColumn(mViewer, tc);
-            tvc.setLabelProvider(mLogCatMessageLabelProvider);
         }
 
-        mViewer.getTable().setLinesVisible(true); /* zebra stripe the table */
-        mViewer.getTable().setHeaderVisible(true);
-        mViewer.setContentProvider(new LogCatMessageContentProvider());
-        WrappingToolTipSupport.enableFor(mViewer, ToolTip.NO_RECREATE);
+        // don't zebra stripe the table: When the buffer is full, and scroll lock is on, having
+        // zebra striping means that the background could keep changing depending on the number
+        // of new messages added to the bottom of the log.
+        mTable.setLinesVisible(false);
+        mTable.setHeaderVisible(true);
 
         // Set the row height to be sufficient enough to display the current font.
         // This is not strictly necessary, except that on WinXP, the rows showed up clipped. So
         // we explicitly set it to be sure.
-        mViewer.getTable().addListener(SWT.MeasureItem, new Listener() {
+        mTable.addListener(SWT.MeasureItem, new Listener() {
             @Override
             public void handleEvent(Event event) {
                 event.height = event.gc.getFontMetrics().getHeight();
@@ -791,146 +823,87 @@
         });
 
         // Update the label provider whenever the text column's width changes
-        TableColumn textColumn = mViewer.getTable().getColumn(properties.length - 1);
+        TableColumn textColumn = mTable.getColumn(properties.length - 1);
         textColumn.addControlListener(new ControlAdapter() {
             @Override
             public void controlResized(ControlEvent event) {
-                TableColumn tc = (TableColumn) event.getSource();
-                int width = tc.getWidth();
-                GC gc = new GC(tc.getParent());
-                int avgCharWidth = gc.getFontMetrics().getAverageCharWidth();
-                gc.dispose();
-
-                if (mLogCatMessageLabelProvider != null) {
-                    mLogCatMessageLabelProvider.setMinimumLengthForToolTips(width/avgCharWidth);
-                }
+                recomputeWrapWidth();
             }
         });
 
-        setupAutoScrollLockBehavior();
+        addRightClickMenu(mTable);
         initDoubleClickListener();
+        recomputeWrapWidth();
     }
 
-    /**
-     * Setup to automatically enable or disable scroll lock. From a user's perspective,
-     * the logcat window will: <ul>
-     * <li> Automatically scroll and reveal new entries if the scrollbar is at the bottom. </li>
-     * <li> Not scroll even when new messages are received if the scrollbar is not at the bottom.
-     * </li>
-     * </ul>
-     * This requires that we are able to detect where the scrollbar is and what direction
-     * it is moving. Unfortunately, that proves to be very platform dependent. Here's the behavior
-     * of the scroll events on different platforms: <ul>
-     * <li> On Windows, scroll bar events specify which direction the scrollbar is moving, but
-     * it is not possible to determine if the scrollbar is right at the end. </li>
-     * <li> On Mac/Cocoa, scroll bar events do not specify the direction of movement (it is always
-     * set to SWT.DRAG), and it is not possible to identify where the scrollbar is since
-     * small movements of the scrollbar are not reflected in sb.getSelection(). </li>
-     * <li> On Linux/gtk, we don't get the direction, but we can accurately locate the
-     * scrollbar location using getSelection(), getThumb() and getMaximum().
-     * </ul>
-     */
-    private void setupAutoScrollLockBehavior() {
-        if (DdmConstants.CURRENT_PLATFORM == DdmConstants.PLATFORM_WINDOWS) {
-            // On Windows, it is not possible to detect whether the scrollbar is at the
-            // bottom using the values of ScrollBar.getThumb, getSelection and getMaximum.
-            // Instead we resort to the following workaround: attach to the paint listener
-            // and see if the last item has been painted since the previous scroll event.
-            // If the last item has been painted, then we assume that we are at the bottom.
-            mViewer.getTable().addListener(SWT.PaintItem, new Listener() {
-                @Override
-                public void handleEvent(Event event) {
-                    TableItem item = (TableItem) event.item;
-                    TableItem[] items = mViewer.getTable().getItems();
-                    if (items.length > 0 && items[items.length - 1] == item) {
-                        mLastItemPainted = true;
-                    }
+    /** Setup menu to be displayed when right clicking a log message. */
+    private void addRightClickMenu(final Table table) {
+        // This action will pop up a create filter dialog pre-populated with current selection
+        final Action filterAction = new Action("Filter similar messages..") {
+            @Override
+            public void run() {
+                List<LogCatMessage> selectedMessages = getSelectedLogCatMessages();
+                if (selectedMessages.size() == 0) {
+                    addNewFilter();
+                } else {
+                    LogCatMessage m = selectedMessages.get(0);
+                    addNewFilter(m.getTag(), m.getMessage(), m.getPid(), m.getAppName(),
+                            m.getLogLevel());
                 }
-            });
-            mViewer.getTable().getVerticalBar().addSelectionListener(new SelectionAdapter() {
-                @Override
-                public void widgetSelected(SelectionEvent event) {
-                    boolean scrollToLast;
-                    if (event.detail == SWT.ARROW_UP || event.detail == SWT.PAGE_UP
-                            || event.detail == SWT.HOME) {
-                        // if we know that we are moving up, then do not scroll down
-                        scrollToLast = false;
-                    } else {
-                        // otherwise, enable scrollToLast only if the last item was displayed
-                        scrollToLast = mLastItemPainted;
-                    }
+            }
+        };
 
-                    setScrollToLatestLog(scrollToLast, true);
-                    mLastItemPainted = false;
-                }
-            });
-        } else if (DdmConstants.CURRENT_PLATFORM == DdmConstants.PLATFORM_LINUX) {
-            // On Linux/gtk, we do not get any details regarding the scroll event (up/down/etc).
-            // So we completely rely on whether the scrollbar is at the bottom or not.
-            mViewer.getTable().getVerticalBar().addSelectionListener(new SelectionAdapter() {
-                @Override
-                public void widgetSelected(SelectionEvent event) {
-                    ScrollBar sb = (ScrollBar) event.getSource();
-                    boolean scrollToLast = sb.getSelection() + sb.getThumb() == sb.getMaximum();
-                    setScrollToLatestLog(scrollToLast, true);
-                }
-            });
-        } else {
-            // On Mac, we do not get any details regarding the (trackball) scroll event,
-            // nor can we rely on getSelection() changing for small movements. As a result, we
-            // do not setup any auto scroll lock behavior. Mac users have to manually pause and
-            // unpause if they are looking at a particular item in a high volume stream of events.
+        final MenuManager mgr = new MenuManager();
+        mgr.add(filterAction);
+        final Menu menu = mgr.createContextMenu(table);
+
+        table.addListener(SWT.MenuDetect, new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                Point pt = table.getDisplay().map(null, table, new Point(event.x, event.y));
+                Rectangle clientArea = table.getClientArea();
+
+                // The click location is in the header if it is between
+                // clientArea.y and clientArea.y + header height
+                boolean header = pt.y > clientArea.y
+                                    && pt.y < (clientArea.y + table.getHeaderHeight());
+
+                // Show the menu only if it is not inside the header
+                table.setMenu(header ? null : menu);
+            }
+        });
+    }
+
+    public void recomputeWrapWidth() {
+        if (mTable == null || mTable.isDisposed()) {
+            return;
         }
+
+        // get width of the last column (log message)
+        TableColumn tc = mTable.getColumn(mTable.getColumnCount() - 1);
+        int colWidth = tc.getWidth();
+
+        // get font width
+        GC gc = new GC(tc.getParent());
+        gc.setFont(mFont);
+        int avgCharWidth = gc.getFontMetrics().getAverageCharWidth();
+        gc.dispose();
+
+        int MIN_CHARS_PER_LINE = 50;    // show atleast these many chars per line
+        mWrapWidthInChars = Math.max(colWidth/avgCharWidth, MIN_CHARS_PER_LINE);
+
+        int OFFSET_AT_END_OF_LINE = 10; // leave some space at the end of the line
+        mWrapWidthInChars -= OFFSET_AT_END_OF_LINE;
     }
 
-    private void setScrollToLatestLog(boolean scroll, boolean updateCheckbox) {
+    private void setScrollToLatestLog(boolean scroll) {
         mShouldScrollToLatestLog = scroll;
 
-        if (updateCheckbox) {
-            mPauseLogcatCheckBox.setSelection(!scroll);
-        }
-
         if (scroll) {
-            mViewer.refresh();
             scrollToLatestLog();
         }
     }
 
-    private static class WrappingToolTipSupport extends ColumnViewerToolTipSupport {
-        protected WrappingToolTipSupport(ColumnViewer viewer, int style,
-                boolean manualActivation) {
-            super(viewer, style, manualActivation);
-        }
-
-        @Override
-        protected Composite createViewerToolTipContentArea(Event event, ViewerCell cell,
-                Composite parent) {
-            Composite comp = new Composite(parent, SWT.NONE);
-            GridLayout l = new GridLayout(1, false);
-            l.horizontalSpacing = 0;
-            l.marginWidth = 0;
-            l.marginHeight = 0;
-            l.verticalSpacing = 0;
-            comp.setLayout(l);
-
-            Text text = new Text(comp, SWT.BORDER | SWT.V_SCROLL | SWT.WRAP);
-            text.setEditable(false);
-            text.setText(cell.getElement().toString());
-            text.setLayoutData(new GridData(500, 150));
-
-            return comp;
-        }
-
-        @Override
-        public boolean isHideOnMouseDown() {
-            return false;
-        }
-
-        public static final void enableFor(ColumnViewer viewer, int style) {
-            new WrappingToolTipSupport(viewer, style, false);
-        }
-    }
-
     private String getColPreferenceKey(String field) {
         return LOGCAT_VIEW_COLSIZE_PREFKEY_PREFIX + field;
     }
@@ -952,7 +925,7 @@
      * Perform all necessary updates whenever a filter is selected (by user or programmatically).
      */
     private void filterSelectionChanged() {
-        int idx = getSelectedSavedFilterIndex();
+        int idx = mFiltersTableViewer.getTable().getSelectionIndex();
         if (idx == -1) {
             /* One of the filters should always be selected.
              * On Linux, there is no way to deselect an item.
@@ -971,84 +944,80 @@
     }
 
     private void resetUnreadCountForSelectedFilter() {
-        int index = getSelectedSavedFilterIndex();
-        mLogCatFilters.get(index).resetUnreadCount();
-
+        mLogCatFilters.get(mCurrentSelectedFilterIndex).resetUnreadCount();
         refreshFiltersTable();
     }
 
-    private int getSelectedSavedFilterIndex() {
-        return mFiltersTableViewer.getTable().getSelectionIndex();
-    }
-
     private void updateFiltersToolBar() {
         /* The default filter at index 0 can neither be edited, nor removed. */
-        boolean en = getSelectedSavedFilterIndex() != 0;
+        boolean en = mCurrentSelectedFilterIndex != DEFAULT_FILTER_INDEX;
         mEditFilterToolItem.setEnabled(en);
         mDeleteFilterToolItem.setEnabled(en);
     }
 
     private void updateAppliedFilters() {
-        List<LogCatViewerFilter> filters = getFiltersToApply();
-        mViewer.setFilters(filters.toArray(new LogCatViewerFilter[filters.size()]));
-
-        /* whenever filters are changed, the number of displayed logs changes
-         * drastically. Display the latest log in such a situation. */
-        scrollToLatestLog();
+        mCurrentFilters = getFiltersToApply();
+        reloadLogBuffer();
     }
 
-    private List<LogCatViewerFilter> getFiltersToApply() {
+    private List<LogCatFilter> getFiltersToApply() {
         /* list of filters to apply = saved filter + live filters */
-        List<LogCatViewerFilter> filters = new ArrayList<LogCatViewerFilter>();
-        filters.add(getSelectedSavedFilter());
+        List<LogCatFilter> filters = new ArrayList<LogCatFilter>();
+
+        if (mCurrentSelectedFilterIndex != DEFAULT_FILTER_INDEX) {
+            filters.add(getSelectedSavedFilter());
+        }
+
         filters.addAll(getCurrentLiveFilters());
         return filters;
     }
 
-    private List<LogCatViewerFilter> getCurrentLiveFilters() {
-        List<LogCatViewerFilter> liveFilters = new ArrayList<LogCatViewerFilter>();
-
-        List<LogCatFilter> liveFilterSettings = LogCatFilter.fromString(
+    private List<LogCatFilter> getCurrentLiveFilters() {
+        return LogCatFilter.fromString(
                 mLiveFilterText.getText(),                                  /* current query */
                 LogLevel.getByString(mLiveFilterLevelCombo.getText()));     /* current log level */
-        for (LogCatFilter s : liveFilterSettings) {
-            liveFilters.add(new LogCatViewerFilter(s));
-        }
-
-        return liveFilters;
     }
 
-    private LogCatViewerFilter getSelectedSavedFilter() {
-        int index = getSelectedSavedFilterIndex();
-        return new LogCatViewerFilter(mLogCatFilters.get(index));
+    private LogCatFilter getSelectedSavedFilter() {
+        return mLogCatFilters.get(mCurrentSelectedFilterIndex);
     }
 
-
     @Override
     public void setFocus() {
     }
 
-    /**
-     * Update view whenever a message is received.
-     * @param receivedMessages list of messages from logcat
-     * Implements {@link ILogCatMessageEventListener#messageReceived()}.
-     */
     @Override
-    public void messageReceived(List<LogCatMessage> receivedMessages) {
-        refreshLogCatTable();
+    public void bufferChanged(List<LogCatMessage> addedMessages,
+            List<LogCatMessage> deletedMessages) {
 
-        if (mShouldScrollToLatestLog) {
-            updateUnreadCount(receivedMessages);
-            refreshFiltersTable();
-        } else {
-            LogCatMessageList messageList = mReceiver.getMessages();
-            int remainingCapacity = messageList.remainingCapacity();
-            if (remainingCapacity == 0) {
-                mRemovedEntriesCount +=
-                        receivedMessages.size() - mPreviousRemainingCapacity;
-            }
-            mPreviousRemainingCapacity = remainingCapacity;
+        synchronized (mLogBuffer) {
+            addedMessages = applyCurrentFilters(addedMessages);
+            deletedMessages = applyCurrentFilters(deletedMessages);
+
+            mLogBuffer.addAll(addedMessages);
+            mDeletedLogCount += deletedMessages.size();
         }
+
+        refreshLogCatTable();
+        updateUnreadCount(addedMessages);
+        refreshFiltersTable();
+    }
+
+    private void reloadLogBuffer() {
+        mTable.removeAll();
+
+        synchronized (mLogBuffer) {
+            mLogBuffer.clear();
+            mDeletedLogCount = 0;
+        }
+
+        if (mReceiver == null || mReceiver.getMessages() == null) {
+            return;
+        }
+
+        List<LogCatMessage> addedMessages = mReceiver.getMessages().getAllMessages();
+        List<LogCatMessage> deletedMessages = Collections.emptyList();
+        bufferChanged(addedMessages, deletedMessages);
     }
 
     /**
@@ -1089,34 +1058,210 @@
      */
     private void refreshLogCatTable() {
         synchronized (this) {
-            if (mCurrentRefresher == null && mShouldScrollToLatestLog) {
+            if (mCurrentRefresher == null) {
                 mCurrentRefresher = new LogCatTableRefresherTask();
                 Display.getDefault().asyncExec(mCurrentRefresher);
             }
         }
     }
 
+    /**
+     * The {@link LogCatTableRefresherTask} takes care of refreshing the table with the
+     * new log messages that have been received. Since the log behaves like a circular buffer,
+     * the first step is to remove items from the top of the table (if necessary). This step
+     * is complicated by the fact that a single log message may span multiple rows if the message
+     * was wrapped. Once the deleted items are removed, the new messages are added to the bottom
+     * of the table. If scroll lock is enabled, the item that was original visible is made visible
+     * again, if not, the last item is made visible.
+     */
     private class LogCatTableRefresherTask implements Runnable {
         @Override
         public void run() {
-            if (mViewer.getTable().isDisposed()) {
+            if (mTable.isDisposed()) {
                 return;
             }
             synchronized (LogCatPanel.this) {
                 mCurrentRefresher = null;
             }
 
-            if (mShouldScrollToLatestLog) {
-                mViewer.refresh();
-                scrollToLatestLog();
+            // Current topIndex so that it can be restored if scroll locked.
+            int topIndex = mTable.getTopIndex();
+
+            mTable.setRedraw(false);
+
+            // Obtain the list of new messages, and the number of deleted messages.
+            List<LogCatMessage> newMessages;
+            int deletedMessageCount;
+            synchronized (mLogBuffer) {
+                newMessages = new ArrayList<LogCatMessage>(mLogBuffer);
+                mLogBuffer.clear();
+
+                deletedMessageCount = mDeletedLogCount;
+                mDeletedLogCount = 0;
             }
+
+            int originalItemCount = mTable.getItemCount();
+
+            // Remove entries from the start of the table if they were removed in the log buffer
+            // This is complicated by the fact that a single message may span multiple TableItems
+            // if it was word-wrapped.
+            deletedMessageCount -= removeFromTable(mTable, deletedMessageCount);
+
+            // Compute number of table items that were deleted from the table.
+            int deletedItemCount = originalItemCount - mTable.getItemCount();
+
+            // If there are more messages to delete (after deleting messages from the table),
+            // then delete them from the start of the newly added messages list
+            if (deletedMessageCount > 0) {
+                assert deletedMessageCount < newMessages.size();
+                for (int i = 0; i < deletedMessageCount; i++) {
+                    newMessages.remove(0);
+                }
+            }
+
+            // Add the remaining messages to the table.
+            for (LogCatMessage m: newMessages) {
+                List<String> wrappedMessageList = wrapMessage(m.getMessage(), mWrapWidthInChars);
+                Color c = getForegroundColor(m);
+                for (int i = 0; i < wrappedMessageList.size(); i++) {
+                    TableItem item = new TableItem(mTable, SWT.NONE);
+
+                    if (i == 0) {
+                        // Only set the message data in the first item. This allows code that
+                        // examines the table item data (such as copy selection) to distinguish
+                        // between real messages versus lines that are really just wrapped
+                        // content from the previous message.
+                        item.setData(m);
+
+                        item.setText(new String[] {
+                                Character.toString(m.getLogLevel().getPriorityLetter()),
+                                m.getTime(),
+                                m.getPid(),
+                                m.getTid(),
+                                m.getAppName(),
+                                m.getTag(),
+                                wrappedMessageList.get(i)
+                        });
+                    } else {
+                        item.setText(new String[] {
+                                "", "", "", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+                                "", "", "", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+                                wrappedMessageList.get(i)
+                        });
+                    }
+                    item.setForeground(c);
+                    item.setFont(mFont);
+                }
+            }
+
+            if (mShouldScrollToLatestLog) {
+                scrollToLatestLog();
+            } else {
+                // If scroll locked, show the same item that was original visible in the table.
+                int index = Math.max(topIndex - deletedItemCount, 0);
+                mTable.setTopIndex(index);
+            }
+
+            mTable.setRedraw(true);
+        }
+
+        /**
+         * Removes given number of messages from the table, starting at the top of the table.
+         * Note that the number of messages deleted is not equal to the number of rows
+         * deleted since a single message could span multiple rows. This method first calculates
+         * the number of rows that correspond to the number of messages to delete, and then
+         * removes all those rows.
+         * @param table table from which messages should be removed
+         * @param msgCount number of messages to be removed
+         * @return number of messages that were actually removed
+         */
+        private int removeFromTable(Table table, int msgCount) {
+            int deletedMessageCount = 0; // # of messages that have been deleted
+            int lastItemToDelete = 0;    // index of the last item that should be deleted
+
+            while (deletedMessageCount < msgCount && lastItemToDelete < table.getItemCount()) {
+                // only rows that begin a message have their item data set
+                TableItem item = table.getItem(lastItemToDelete);
+                if (item.getData() != null) {
+                    deletedMessageCount++;
+                }
+
+                lastItemToDelete++;
+            }
+
+            // If there are any table items left over at the end that are wrapped over from the
+            // previous message, mark them for deletion as well.
+            if (lastItemToDelete < table.getItemCount()
+                    && table.getItem(lastItemToDelete).getData() == null) {
+                lastItemToDelete++;
+            }
+
+            table.remove(0, lastItemToDelete - 1);
+
+            return deletedMessageCount;
         }
     }
 
     /** Scroll to the last line. */
     private void scrollToLatestLog() {
-        mRemovedEntriesCount = 0;
-        mViewer.getTable().setTopIndex(mViewer.getTable().getItemCount() - 1);
+        mTable.setTopIndex(mTable.getItemCount() - 1);
+    }
+
+    /**
+     * Splits the message into multiple lines if the message length exceeds given width.
+     * If the message was split, then a wrap character \u23ce is appended to the end of all
+     * lines but the last one.
+     */
+    private List<String> wrapMessage(String msg, int wrapWidth) {
+        if (msg.length() < wrapWidth) {
+            return Collections.singletonList(msg);
+        }
+
+        List<String> wrappedMessages = new ArrayList<String>();
+
+        int offset = 0;
+        int len = msg.length();
+
+        while (len > 0) {
+            int copylen = Math.min(wrapWidth, len);
+            String s = msg.substring(offset, offset + copylen);
+
+            offset += copylen;
+            len -= copylen;
+
+            if (len > 0) { // if there are more lines following, then append a wrap marker
+                s += " \u23ce"; //$NON-NLS-1$
+            }
+
+            wrappedMessages.add(s);
+        }
+
+        return wrappedMessages;
+    }
+
+    /* Default Colors for different log levels. */
+    private static final Color INFO_MSG_COLOR = new Color(null, 0, 127, 0);
+    private static final Color DEBUG_MSG_COLOR = new Color(null, 0, 0, 127);
+    private static final Color ERROR_MSG_COLOR = new Color(null, 255, 0, 0);
+    private static final Color WARN_MSG_COLOR = new Color(null, 255, 127, 0);
+    private static final Color VERBOSE_MSG_COLOR = new Color(null, 0, 0, 0);
+
+    private static Color getForegroundColor(LogCatMessage m) {
+        LogLevel l = m.getLogLevel();
+
+        if (l.equals(LogLevel.VERBOSE)) {
+            return VERBOSE_MSG_COLOR;
+        } else if (l.equals(LogLevel.INFO)) {
+            return INFO_MSG_COLOR;
+        } else if (l.equals(LogLevel.DEBUG)) {
+            return DEBUG_MSG_COLOR;
+        } else if (l.equals(LogLevel.ERROR)) {
+            return ERROR_MSG_COLOR;
+        } else if (l.equals(LogLevel.WARN)) {
+            return WARN_MSG_COLOR;
+        }
+
+        return null;
     }
 
     private List<ILogCatMessageSelectionListener> mMessageSelectionListeners;
@@ -1124,7 +1269,7 @@
     private void initDoubleClickListener() {
         mMessageSelectionListeners = new ArrayList<ILogCatMessageSelectionListener>(1);
 
-        mViewer.getTable().addSelectionListener(new SelectionAdapter() {
+        mTable.addSelectionListener(new SelectionAdapter() {
             @Override
             public void widgetDefaultSelected(SelectionEvent arg0) {
                 List<LogCatMessage> selectedMessages = getSelectedLogCatMessages();
@@ -1153,7 +1298,6 @@
     public void setTableFocusListener(ITableFocusListener listener) {
         mTableFocusListener = listener;
 
-        final Table table = mViewer.getTable();
         final IFocusedTableActivator activator = new IFocusedTableActivator() {
             @Override
             public void copy(Clipboard clipboard) {
@@ -1162,11 +1306,11 @@
 
             @Override
             public void selectAll() {
-                table.selectAll();
+                mTable.selectAll();
             }
         };
 
-        table.addFocusListener(new FocusListener() {
+        mTable.addFocusListener(new FocusListener() {
             @Override
             public void focusGained(FocusEvent e) {
                 mTableFocusListener.focusGained(activator);
@@ -1198,6 +1342,6 @@
 
     /** Select all items in the logcat table. */
     public void selectAll() {
-        mViewer.getTable().selectAll();
+        mTable.selectAll();
     }
 }
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java
index c9606f6..2674e92 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java
@@ -20,9 +20,11 @@
 import com.android.ddmlib.IShellOutputReceiver;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.MultiLineReceiver;
+import com.android.ddmlib.Log.LogLevel;
 
 import org.eclipse.jface.preference.IPreferenceStore;
 
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -34,11 +36,14 @@
 public final class LogCatReceiver {
     private static final String LOGCAT_COMMAND = "logcat -v long";
     private static final int DEVICE_POLL_INTERVAL_MSEC = 1000;
+    private static LogCatMessage DEVICE_DISCONNECTED_MESSAGE =
+            new LogCatMessage(LogLevel.ERROR, "", "", "",
+                    "", "", "Device disconnected");
 
     private LogCatMessageList mLogMessages;
     private IDevice mCurrentDevice;
     private LogCatOutputReceiver mCurrentLogCatOutputReceiver;
-    private Set<ILogCatMessageEventListener> mLogCatMessageListeners;
+    private Set<ILogCatBufferChangeListener> mLogCatMessageListeners;
     private LogCatMessageParser mLogCatMessageParser;
     private LogCatPidToNameMapper mPidToNameMapper;
     private IPreferenceStore mPrefStore;
@@ -55,7 +60,7 @@
         mCurrentDevice = device;
         mPrefStore = prefStore;
 
-        mLogCatMessageListeners = new HashSet<ILogCatMessageEventListener>();
+        mLogCatMessageListeners = new HashSet<ILogCatBufferChangeListener>();
         mLogCatMessageParser = new LogCatMessageParser();
         mPidToNameMapper = new LogCatPidToNameMapper(mCurrentDevice);
 
@@ -72,9 +77,11 @@
             /* stop the current logcat command */
             mCurrentLogCatOutputReceiver.mIsCancelled = true;
             mCurrentLogCatOutputReceiver = null;
+
+            // add a message to the log indicating that the device has been disconnected.
+            processLogMessages(Collections.singletonList(DEVICE_DISCONNECTED_MESSAGE));
         }
 
-        mLogMessages = null;
         mCurrentDevice = null;
     }
 
@@ -147,14 +154,19 @@
     }
 
     private void processLogLines(String[] lines) {
-        List<LogCatMessage> messages = mLogCatMessageParser.processLogLines(lines,
+        List<LogCatMessage> newMessages = mLogCatMessageParser.processLogLines(lines,
                 mPidToNameMapper);
+        processLogMessages(newMessages);
+    }
 
-        if (messages.size() > 0) {
-            for (LogCatMessage m : messages) {
-                mLogMessages.appendMessage(m);
+    private void processLogMessages(List<LogCatMessage> newMessages) {
+        if (newMessages.size() > 0) {
+            List<LogCatMessage> deletedMessages;
+            synchronized (mLogMessages) {
+                deletedMessages = mLogMessages.ensureSpace(newMessages.size());
+                mLogMessages.appendMessages(newMessages);
             }
-            sendMessageReceivedEvent(messages);
+            sendLogChangedEvent(newMessages, deletedMessages);
         }
     }
 
@@ -177,17 +189,18 @@
      * Add to list of message event listeners.
      * @param l listener to notified when messages are received from the device
      */
-    public void addMessageReceivedEventListener(ILogCatMessageEventListener l) {
+    public void addMessageReceivedEventListener(ILogCatBufferChangeListener l) {
         mLogCatMessageListeners.add(l);
     }
 
-    public void removeMessageReceivedEventListener(ILogCatMessageEventListener l) {
+    public void removeMessageReceivedEventListener(ILogCatBufferChangeListener l) {
         mLogCatMessageListeners.remove(l);
     }
 
-    private void sendMessageReceivedEvent(List<LogCatMessage> messages) {
-        for (ILogCatMessageEventListener l : mLogCatMessageListeners) {
-            l.messageReceived(messages);
+    private void sendLogChangedEvent(List<LogCatMessage> addedMessages,
+            List<LogCatMessage> deletedMessages) {
+        for (ILogCatBufferChangeListener l : mLogCatMessageListeners) {
+            l.bufferChanged(addedMessages, deletedMessages);
         }
     }
 
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatViewerFilter.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatViewerFilter.java
deleted file mode 100644
index f7b8dce..0000000
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatViewerFilter.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ddmuilib.logcat;
-
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerFilter;
-
-/**
- * A JFace {@link ViewerFilter} for the {@link LogCatPanel} displaying logcat messages.
- * This is a simple wrapper around {@link LogCatFilter}.
- */
-public final class LogCatViewerFilter extends ViewerFilter {
-    private LogCatFilter mFilter;
-
-    /**
-     * Construct a {@link ViewerFilter} filtering logcat messages based on
-     * user provided filter settings for PID, Tag and log level.
-     * @param filter filter to use
-     */
-    public LogCatViewerFilter(LogCatFilter filter) {
-        mFilter = filter;
-    }
-
-    @Override
-    public boolean select(Viewer viewer, Object parent, Object element) {
-        if (!(element instanceof LogCatMessage)) {
-            return false;
-        }
-
-        LogCatMessage m = (LogCatMessage) element;
-        return mFilter.matches(m);
-    }
-}
diff --git a/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
index 7c719b4..9b086f4 100644
--- a/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
+++ b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
@@ -88,7 +88,7 @@
     private JLabel xLabel;
     private JLabel yLabel;
 
-    private TexturePaint texture;    
+    private TexturePaint texture;
 
     private List<Rectangle> patches;
     private List<Rectangle> horizontalPatches;
@@ -98,7 +98,7 @@
     private boolean horizontalStartWithPatch;
 
     private Pair<Integer> horizontalPadding;
-    private Pair<Integer> verticalPadding;    
+    private Pair<Integer> verticalPadding;
 
     ImageEditorPanel(MainFrame mainFrame, BufferedImage image, String name) {
         this.image = image;
@@ -409,7 +409,7 @@
             both = new StretchView();
 
             setScale(DEFAULT_SCALE);
-            
+
             add(vertical, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
                     GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
             add(horizontal, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index f0432c6..fe8cc4b 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -58,6 +58,7 @@
 congrats
 coords
 credentials
+cyclical
 dalvik
 ddms
 deactivated
@@ -153,6 +154,7 @@
 locales
 logo
 lombok
+lopsided
 lowercase
 luminance
 mac
@@ -166,6 +168,7 @@
 micro
 min
 mipmap
+misaligned
 monospace
 monte
 ms
@@ -271,6 +274,7 @@
 subclassed
 subclassing
 submenu
+submenus
 subregion
 superclasses
 supertype
@@ -317,6 +321,7 @@
 verbosity
 viewport
 vs
+wallpaper
 webtools
 whilst
 workflow
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/activity.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/activity.png
new file mode 100644
index 0000000..3612361
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/activity.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/display.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/display.png
new file mode 100644
index 0000000..8831f89
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/display.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_landscape.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_landscape.png
new file mode 100644
index 0000000..4940cea
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_landscape.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_portrait.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_portrait.png
new file mode 100644
index 0000000..c28a980
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/flip_portrait.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/globe.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/globe.png
new file mode 100644
index 0000000..45e5e05
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/globe.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/landscape.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/landscape.png
new file mode 100644
index 0000000..4ed5c2e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/landscape.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/newConfig.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/newConfig.png
new file mode 100644
index 0000000..d6317df
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/newConfig.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/portrait.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/portrait.png
new file mode 100644
index 0000000..3786153
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/portrait.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/square.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/square.png
new file mode 100644
index 0000000..7cc8a4c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/square.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/themes.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/themes.png
new file mode 100644
index 0000000..c96394c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/themes.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java
index 3129f4d..768f93f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java
@@ -172,6 +172,7 @@
      * The top level android package as a prefix, "android.".
      */
     public static final String ANDROID_PKG_PREFIX = ANDROID_PKG + '.';
+    public static final String ANDROID_SUPPORT_PKG_PREFIX = ANDROID_PKG_PREFIX + "support."; //$NON-NLS-1$
 
     /** The android.view. package prefix */
     public static final String ANDROID_VIEW_PKG = ANDROID_PKG_PREFIX + "view."; //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
index ec3d8a4..7b3e8a3 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
@@ -75,6 +75,15 @@
         mDeprecatedDoc = info.mDeprecatedDoc;
     }
 
+    /**
+     * Sets the XML Name of the attribute
+     *
+     * @param name the new name to assign
+     */
+    public void setName(String name) {
+        mName = name;
+    }
+
     /** Returns the XML Name of the attribute */
     @Override
     public String getName() {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java
index 52b0e15..a0a5ad8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java
@@ -21,6 +21,7 @@
 import com.android.ide.common.api.IAttributeInfo.Format;
 import com.android.ide.common.log.ILogger;
 import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo;
+import com.android.ide.eclipse.adt.AdtUtils;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
@@ -197,7 +198,7 @@
             String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$
                     viewLayoutClass.getShortClassName(),
                     info.getShortClassName());
-            xmlName = xmlName.replaceFirst("Params$", ""); //$NON-NLS-1$ //$NON-NLS-2$
+            xmlName = AdtUtils.stripSuffix(xmlName, "Params"); //$NON-NLS-1$
 
             DeclareStyleableInfo style = mStyleMap.get(xmlName);
             if (style != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index 7e2a44d..114e45a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -16,8 +16,13 @@
 
 package com.android.ide.eclipse.adt;
 
+import static com.android.tools.lint.detector.api.LintConstants.TOOLS_PREFIX;
+import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI;
+
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
 
@@ -36,6 +41,8 @@
 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.IDocument;
 import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.TextUtilities;
+import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.IEditorInput;
 import org.eclipse.ui.IEditorPart;
 import org.eclipse.ui.IFileEditorInput;
@@ -44,6 +51,10 @@
 import org.eclipse.ui.IWorkbenchWindow;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
 
 import java.io.File;
 import java.net.URISyntaxException;
@@ -219,6 +230,22 @@
     }
 
     /**
+     * Strips the given suffix from the given string, provided that the string ends with
+     * the suffix.
+     *
+     * @param string the full string to strip from
+     * @param suffix the suffix to strip out
+     * @return the string without the suffix at the end
+     */
+    public static String stripSuffix(@NonNull String string, @NonNull String suffix) {
+        if (string.endsWith(suffix)) {
+            return string.substring(0, string.length() - suffix.length());
+        }
+
+        return string;
+    }
+
+    /**
      * Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z].
      * Returns the string unmodified if the first character is not [a-z].
      *
@@ -236,6 +263,29 @@
         return sb.toString();
     }
 
+    /** For use by {@link #getLineSeparator()} */
+    private static String sLineSeparator;
+
+    /**
+     * Returns the default line separator to use.
+     * <p>
+     * NOTE: If you have an associated {@link IDocument}, it is better to call
+     * {@link TextUtilities#getDefaultLineDelimiter(IDocument)} since that will
+     * allow (for example) editing a \r\n-delimited document on a \n-delimited
+     * platform and keep a consistent usage of delimiters in the file.
+     *
+     * @return the delimiter string to use
+     */
+    @NonNull
+    public static String getLineSeparator() {
+        if (sLineSeparator == null) {
+            // This is guaranteed to exist:
+            sLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$
+        }
+
+        return sLineSeparator;
+    }
+
     /**
      * Returns the current editor (the currently visible and active editor), or null if
      * not found
@@ -550,4 +600,90 @@
 
         return "";
     }
+
+    /**
+     * Sets the given tools: attribute in the given XML editor document, adding
+     * the tools name space declaration if necessary, formatting the affected
+     * document region, and optionally comma-appending to an existing value and
+     * optionally opening and revealing the attribute.
+     *
+     * @param editor the associated editor
+     * @param element the associated element
+     * @param description the description of the attribute (shown in the undo
+     *            event)
+     * @param name the name of the attribute
+     * @param value the attribute value
+     * @param reveal if true, open the editor and select the given attribute
+     *            node
+     * @param appendValue if true, add this value as a comma separated value to
+     *            the existing attribute value, if any
+     */
+    @SuppressWarnings("restriction") // DOM model
+    public static void setToolsAttribute(
+            @NonNull final AndroidXmlEditor editor,
+            @NonNull final Element element,
+            @NonNull final String description,
+            @NonNull final String name,
+            @NonNull final String value,
+            final boolean reveal,
+            final boolean appendValue) {
+        editor.wrapUndoEditXmlModel(description, new Runnable() {
+            @Override
+            public void run() {
+                String prefix = UiElementNode.lookupNamespacePrefix(element,
+                        TOOLS_URI, null);
+                if (prefix == null) {
+                    // Add in new prefix...
+                    prefix = UiElementNode.lookupNamespacePrefix(element,
+                            TOOLS_URI, TOOLS_PREFIX);
+                    // ...and ensure that the header is formatted such that
+                    // the XML namespace declaration is placed in the right
+                    // position and wrapping is applied etc.
+                    editor.scheduleNodeReformat(editor.getUiRootNode(),
+                            true /*attributesOnly*/);
+                }
+
+                String v = value;
+                if (appendValue) {
+                    String prev = element.getAttributeNS(TOOLS_URI, name);
+                    if (prev.length() > 0) {
+                        v = prev + ',' + value;
+                    }
+                }
+
+                // Use the non-namespace form of set attribute since we can't
+                // reference the namespace until the model has been reloaded
+                element.setAttribute(prefix + ':' + name, v);
+
+                UiElementNode rootUiNode = editor.getUiRootNode();
+                if (rootUiNode != null) {
+                    final UiElementNode uiNode = rootUiNode.findXmlNode(element);
+                    if (uiNode != null) {
+                        editor.scheduleNodeReformat(uiNode, true /*attributesOnly*/);
+
+                        if (reveal) {
+                            // Update editor selection after format
+                            Display display = AdtPlugin.getDisplay();
+                            if (display != null) {
+                                display.asyncExec(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        Node xmlNode = uiNode.getXmlNode();
+                                        Attr attribute = ((Element) xmlNode).getAttributeNodeNS(
+                                                TOOLS_URI, name);
+                                        if (attribute instanceof IndexedRegion) {
+                                            IndexedRegion region = (IndexedRegion) attribute;
+                                            editor.getStructuredTextEditor().selectAndReveal(
+                                                    region.getStartOffset(), region.getLength());
+                                        }
+                                    }
+                                });
+                            }
+                        }
+                    }
+                }
+            }
+        });
+    }
+
 }
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 b468c5e..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_REVISION + "=(\\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/actions/AddCompatibilityJarAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java
index 8d38c7c..7da3cc0 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddCompatibilityJarAction.java
@@ -16,6 +16,7 @@
 
 package com.android.ide.eclipse.adt.internal.actions;
 
+import com.android.ide.eclipse.adt.AdtConstants;
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.AdtUtils;
 import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog;
@@ -35,7 +36,9 @@
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
 import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
 import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
@@ -245,12 +248,16 @@
         final IProject newProject;
         try {
             IProgressMonitor monitor = new NullProgressMonitor();
-            IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+            IWorkspace workspace = ResourcesPlugin.getWorkspace();
+            IWorkspaceRoot root = workspace.getRoot();
 
             String name = AdtUtils.getUniqueProjectName(
                     "gridlayout_v7", "_"); //$NON-NLS-1$ //$NON-NLS-2$
             newProject = root.getProject(name);
-            newProject.create(monitor);
+            IProjectDescription description = workspace.newProjectDescription(name);
+            String[] natures = new String[] { AdtConstants.NATURE_DEFAULT, JavaCore.NATURE_ID };
+            description.setNatureIds(natures);
+            newProject.create(description, monitor);
 
             // Copy in the files recursively
             IFileSystem fileSystem = EFS.getLocalFileSystem();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java
index f8a080a..7345a04 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java
@@ -17,6 +17,7 @@
 package com.android.ide.eclipse.adt.internal.actions;
 
 import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
 import com.android.sdklib.SdkConstants;
@@ -164,15 +165,7 @@
 
                 final BufferedWriter writer = new BufferedWriter(new FileWriter(dstFile));
 
-                String sep = System.getProperty("line.separator");                  //$NON-NLS-1$
-                if (sep == null || sep.length() < 1) {
-                    if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
-                        sep = "\r\n";                                               //$NON-NLS-1$
-                    } else {
-                        sep = "\n";                                                 //$NON-NLS-1$
-                    }
-                }
-                final String lineSep = sep;
+                final String lineSep = AdtUtils.getLineSeparator();
 
                 int err = GrabProcessOutput.grabProcessOutput(
                         process,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java
index 29cb910..58dd332 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java
@@ -19,7 +19,7 @@
 /**
  * The type of asset to create: launcher icon, menu icon, etc.
  */
-enum AssetType {
+public enum AssetType {
     /** Launcher icon to be shown in the application list */
     LAUNCHER("Launcher Icons", "ic_launcher"),                     //$NON-NLS-2$
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ChooseAssetTypePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ChooseAssetTypePage.java
index 15b1f05..a26a22e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ChooseAssetTypePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ChooseAssetTypePage.java
@@ -21,7 +21,6 @@
 import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
 import com.android.resources.ResourceFolderType;
 
-import org.eclipse.core.resources.IProject;
 import org.eclipse.jface.dialogs.IMessageProvider;
 import org.eclipse.jface.wizard.WizardPage;
 import org.eclipse.swt.SWT;
@@ -41,25 +40,19 @@
 
 /** Page for choosing the type of asset to create, as well as the target project */
 public class ChooseAssetTypePage extends WizardPage implements SelectionListener, ModifyListener {
+    private final CreateAssetSetWizardState mValues;
     private ProjectCombo mProjectButton;
     private Button mClipboardButton;
-    private IProject mProject;
     private Text mNameText;
-    /**
-     * The type of asset being created. This field is static such that when you
-     * bring up the wizard repeatedly (for example to create multiple
-     * notification icons) you don't have to keep selecting the same type over
-     * and over.
-     */
-    private static AssetType sType = AssetType.LAUNCHER;
     private boolean mNameModified;
     private Label mResourceName;
 
     /**
      * Create the wizard.
      */
-    public ChooseAssetTypePage() {
+    public ChooseAssetTypePage(CreateAssetSetWizardState values) {
         super("chooseAssetTypePage");
+        mValues = values;
         setTitle("Choose Icon Set Type");
         setDescription("Select the type of icon set to create:");
     }
@@ -80,7 +73,7 @@
             Button button = new Button(container, SWT.RADIO);
             button.setData(type);
             button.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
-            button.setSelection(type == sType);
+            button.setSelection(type == mValues.type);
             button.setText(type.getDisplayName());
             button.addSelectionListener(this);
         }
@@ -96,7 +89,7 @@
 
         ProjectChooserHelper helper =
                 new ProjectChooserHelper(getShell(), null /* filter */);
-        mProjectButton = new ProjectCombo(helper, container, mProject);
+        mProjectButton = new ProjectCombo(helper, container, mValues.project);
         mProjectButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
         mProjectButton.addSelectionListener(this);
 
@@ -135,8 +128,10 @@
         if (!mNameModified) {
             // Default name suggestion, possibly as a suffix, e.g. "ic_menu_<name>"
             String replace = "name";
-            String suggestedName = String.format(getAssetType().getDefaultNameFormat(), replace);
+            String suggestedName = String.format(mValues.type.getDefaultNameFormat(), replace);
             mNameText.setText(suggestedName);
+            mValues.outputName = suggestedName;
+
             updateResourceLabel();
             mNameModified = false;
             int start = suggestedName.indexOf(replace);
@@ -154,24 +149,16 @@
         mResourceName.setText("@drawable/" + getOutputName()); //$NON-NLS-1$
     }
 
-    void setProject(IProject project) {
-        mProject = project;
-    }
-
-    IProject getProject() {
-        return mProject;
-    }
-
     @Override
     public boolean canFlipToNextPage() {
-        return mProject != null;
+        return mValues.project != null;
     }
 
     @Override
     public void widgetSelected(SelectionEvent e) {
         Object source = e.getSource();
         if (source == mProjectButton) {
-            mProject = mProjectButton.getSelectedProject();
+            mValues.project = mProjectButton.getSelectedProject();
             validatePage();
         } else if (source == mClipboardButton) {
             Clipboard clipboard = new Clipboard(getShell().getDisplay());
@@ -184,7 +171,8 @@
             // User selected a different asset type to be created
             Object data = ((Button) source).getData();
             if (data instanceof AssetType) {
-                sType = (AssetType) data;
+                mValues.type = (AssetType) data;
+                CreateAssetSetWizardState.sLastType = mValues.type;
                 updateAssetType();
             }
         }
@@ -199,24 +187,21 @@
         Object source = e.getSource();
         if (source == mNameText) {
             mNameModified = true;
+            mValues.outputName = mNameText.getText().trim();
             updateResourceLabel();
         }
 
         validatePage();
     }
 
-    String getOutputName() {
+    private String getOutputName() {
         return mNameText.getText().trim();
     }
 
-    AssetType getAssetType() {
-        return sType;
-    }
-
     private void validatePage() {
         String error = null;
 
-        if (getProject() == null) {
+        if (mValues.project == null) {
             error = "Please select an Android project.";
         } else {
             String outputName = getOutputName();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
index 105892a..1f45570 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
@@ -92,6 +92,7 @@
  */
 public class ConfigureAssetSetPage extends WizardPage implements SelectionListener,
         GraphicGeneratorContext, ModifyListener {
+    private final CreateAssetSetWizardState mValues;
 
     private static final int PREVIEW_AREA_WIDTH = 120;
 
@@ -102,6 +103,8 @@
      */
     private static boolean SUPPORT_LAUNCHER_ICON_TYPES = false;
 
+    private boolean mShown;
+
     private Composite mConfigurationArea;
     private Button mImageRadio;
     private Button mClipartRadio;
@@ -112,6 +115,7 @@
     private Label mPercentLabel;
     private Button mCropRadio;
     private Button mCenterRadio;
+    private Button mNoShapeRadio;
     private Button mSquareRadio;
     private Button mCircleButton;
     private Button mBgButton;
@@ -132,7 +136,6 @@
     private RGB mBgColor;
     private RGB mFgColor;
     private Text mText;
-    private String mSelectedClipart;
 
     /** Most recently set image path: preserved across wizard sessions */
     private static String sImagePath;
@@ -151,11 +154,17 @@
     private Label mEffectsLabel;
     private Composite mEffectsComposite;
 
+    private boolean mIgnore;
+
     /**
      * Create the wizard.
+     *
+     * @param values the wizard state
      */
-    public ConfigureAssetSetPage() {
+    public ConfigureAssetSetPage(CreateAssetSetWizardState values) {
         super("configureAssetPage");
+        mValues = values;
+
         setTitle("Configure Icon Set");
         setDescription("Configure the attributes of the icon set");
     }
@@ -261,7 +270,6 @@
         textLabel.setText("Text:");
 
         mText = new Text(mTextForm, SWT.BORDER);
-        mText.setText("Aa");
         mText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
         mText.addModifyListener(this);
 
@@ -292,12 +300,11 @@
         // This doesn't work right -- not sure why. For now just use a plain slider
         // and subtract 10 from it to get the real range.
         //mPaddingSlider.setValues(0, -10, 50, 0, 1, 10);
-        mPaddingSlider.setSelection(10 + 15);
+        //mPaddingSlider.setSelection(10 + 15);
         mPaddingSlider.addSelectionListener(this);
 
         mPercentLabel = new Label(mConfigurationArea, SWT.NONE);
         mPercentLabel.setText("  15%"); // Enough available space for -10%
-
         mScalingLabel = new Label(mConfigurationArea, SWT.NONE);
         mScalingLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
         mScalingLabel.setText("Foreground Scaling:");
@@ -324,10 +331,14 @@
 
         mShapeComposite = new Composite(mConfigurationArea, SWT.NONE);
         mShapeComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 2, 1));
-        GridLayout gl_mShapeComposite = new GridLayout(5, false);
+        GridLayout gl_mShapeComposite = new GridLayout(6, false);
         gl_mShapeComposite.horizontalSpacing = 0;
         mShapeComposite.setLayout(gl_mShapeComposite);
 
+        mNoShapeRadio = new Button(mShapeComposite, SWT.FLAT | SWT.TOGGLE);
+        mNoShapeRadio.setText("None");
+        mNoShapeRadio.addSelectionListener(this);
+
         mSquareRadio = new Button(mShapeComposite, SWT.FLAT | SWT.TOGGLE);
         mSquareRadio.setSelection(true);
         mSquareRadio.setText("Square");
@@ -427,17 +438,11 @@
 
         // Initial color
         Display display = parent.getDisplay();
-        //updateColor(display, new RGB(0xa4, 0xc6, 0x39), true /*background*/);
-        updateColor(display, new RGB(0xff, 0x00, 0x00), true /*background*/);
-        updateColor(display, new RGB(0x00, 0x00, 0x00), false /*background*/);
+        updateColor(display, mValues.background, true /*background*/);
+        updateColor(display, mValues.foreground, false /*background*/);
 
-        // Start out showing the image form
-        //mImageRadio.setSelection(true);
-        //chooseForegroundTab(mImageRadio, mImageForm);
-        // No, start out showing the text, since the user doesn't have to enter anything
-        // initially and we still get images
-        mTextRadio.setSelection(true);
-        chooseForegroundTab(mTextRadio, mTextForm);
+        setSourceType(mValues.sourceType);
+
         new Label(mConfigurationArea, SWT.NONE);
         new Label(mConfigurationArea, SWT.NONE);
         new Label(mConfigurationArea, SWT.NONE);
@@ -482,6 +487,8 @@
         // that method is called when the wizard is created, and we want to wait until the
         // user has chosen a project before attempting to look up the right default image to use
         if (visible) {
+            mShown = true;
+
             // Clear out old previews - important if the user goes back to page one, changes
             // asset type and steps into page 2 - at that point we arrive here and we might
             // display the old previews for a brief period until the preview delay timer expires.
@@ -492,15 +499,14 @@
 
             // Update asset type configuration: will show/hide parameter controls depending
             // on which asset type is chosen
-            CreateAssetSetWizard wizard = (CreateAssetSetWizard) getWizard();
-            AssetType type = wizard.getAssetType();
+            AssetType type = mValues.type;
             assert type != null;
             configureAssetType(type);
 
             // Initial image - use the most recently used image, or the default launcher
             // icon created in our default projects, if there
             if (sImagePath == null) {
-                IProject project = wizard.getProject();
+                IProject project = mValues.project;
                 if (project != null) {
                     IResource icon = project.findMember("res/drawable-hdpi/icon.png"); //$NON-NLS-1$
                     if (icon != null) {
@@ -511,8 +517,42 @@
                 }
             }
             if (sImagePath != null) {
+                mValues.imagePath = new File(sImagePath);
                 mImagePathText.setText(sImagePath);
             }
+
+            try {
+                mIgnore = true;
+
+                mTrimCheckBox.setSelection(mValues.trim);
+
+                // This doesn't work right -- not sure why. For now just use a plain slider
+                // and subtract 10 from it to get the real range.
+                //mPaddingSlider.setValues(0, -10, 50, 0, 1, 10);
+                //mPaddingSlider.setSelection(10 + 15);
+                mPaddingSlider.setSelection(mValues.padding + 10);
+                mPercentLabel.setText(Integer.toString(mValues.padding) + '%');
+
+                if (mValues.imagePath != null) {
+                    mImagePathText.setText(mValues.imagePath.getPath());
+                }
+
+                if (mValues.text != null) {
+                    mText.setText(mValues.text);
+                }
+
+                setSourceType(mValues.sourceType);
+                setShape(mValues.shape);
+
+                // Initial color
+                Display display = mPreviewArea.getDisplay();
+                //updateColor(display, new RGB(0xa4, 0xc6, 0x39), true /*background*/);
+                updateColor(display, mValues.background, true /*background*/);
+                updateColor(display, mValues.foreground, false /*background*/);
+            } finally {
+                mIgnore = false;
+            }
+
             validatePage();
 
             requestUpdatePreview(true /*quickly*/);
@@ -523,13 +563,25 @@
         }
     }
 
+    private void setSourceType(CreateAssetSetWizardState.SourceType sourceType) {
+        if (sourceType == CreateAssetSetWizardState.SourceType.IMAGE) {
+            chooseForegroundTab(mImageRadio, mImageForm);
+        } else if (sourceType == CreateAssetSetWizardState.SourceType.CLIPART) {
+            chooseForegroundTab(mClipartRadio, mClipartForm);
+        } else if (sourceType == CreateAssetSetWizardState.SourceType.TEXT) {
+            updateFontLabel(mFontButton.getFont());
+            chooseForegroundTab(mTextRadio, mTextForm);
+            mText.setFocus();
+        }
+    }
+
     private boolean validatePage() {
         String error = null;
         //String warning = null;
 
         if (mImageRadio.getSelection()) {
-            String path = mImagePathText.getText().trim();
-            if (path.length() == 0) {
+            String path = mValues.imagePath != null ? mValues.imagePath.getPath() : null;
+            if (path == null || path.length() == 0) {
                 error = "Select an image";
             } else if (!(new File(path).exists())) {
                 error = String.format("%1$s does not exist", path);
@@ -538,13 +590,12 @@
                 sImagePath = path;
             }
         } else if (mTextRadio.getSelection()) {
-            String text = mText.getText().trim();
-            if (text.length() == 0) {
+            if (mValues.text.length() == 0) {
                 error = "Enter text";
             }
         } else {
             assert mClipartRadio.getSelection();
-            if (mSelectedClipart == null) {
+            if (mValues.clipartName == null) {
                 error = "Select clip art";
             }
         }
@@ -565,16 +616,22 @@
     @Override
     public boolean isPageComplete() {
         // Force user to reach second page before hitting Finish
-        return isCurrentPage();
+        return mShown;
     }
 
     // ---- Implements ModifyListener ----
 
     @Override
     public void modifyText(ModifyEvent e) {
+        if (mIgnore) {
+            return;
+        }
+
         if (e.getSource() == mImagePathText) {
+            mValues.imagePath = new File(mImagePathText.getText().trim());
             requestUpdatePreview(false);
         } else if (e.getSource() == mText) {
+            mValues.text = mText.getText().trim();
             requestUpdatePreview(false);
         }
 
@@ -590,15 +647,22 @@
 
     @Override
     public void widgetSelected(SelectionEvent e) {
+        if (mIgnore) {
+            return;
+        }
+
         Object source = e.getSource();
         boolean updateQuickly = true;
 
         // Tabs
         if (source == mImageRadio) {
+            mValues.sourceType = CreateAssetSetWizardState.SourceType.IMAGE;
             chooseForegroundTab((Button) source, mImageForm);
         } else if (source == mClipartRadio) {
+            mValues.sourceType = CreateAssetSetWizardState.SourceType.CLIPART;
             chooseForegroundTab((Button) source, mClipartForm);
         } else if (source == mTextRadio) {
+            mValues.sourceType = CreateAssetSetWizardState.SourceType.TEXT;
             updateFontLabel(mFontButton.getFont());
             chooseForegroundTab((Button) source, mTextForm);
             mText.setFocus();
@@ -609,6 +673,7 @@
             FileDialog dialog = new FileDialog(mPickImageButton.getShell(), SWT.OPEN);
             String file = dialog.open();
             if (file != null) {
+                mValues.imagePath = new File(file);
                 mImagePathText.setText(file);
             }
         }
@@ -622,11 +687,14 @@
             mCropRadio.setSelection(false);
         }
         if (source == mSquareRadio) {
-            mSquareRadio.setSelection(true);
-            mCircleButton.setSelection(false);
+            mValues.shape = GraphicGenerator.Shape.SQUARE;
+            setShape(mValues.shape);
         } else if (source == mCircleButton) {
-            mCircleButton.setSelection(true);
-            mSquareRadio.setSelection(false);
+            mValues.shape = GraphicGenerator.Shape.CIRCLE;
+            setShape(mValues.shape);
+        } else if (source == mNoShapeRadio) {
+            mValues.shape = GraphicGenerator.Shape.NONE;
+            setShape(mValues.shape);
         }
 
         if (SUPPORT_LAUNCHER_ICON_TYPES) {
@@ -645,6 +713,10 @@
             }
         }
 
+        if (source == mTrimCheckBox) {
+            mValues.trim = mTrimCheckBox.getSelection();
+        }
+
         if (source == mHoloDarkRadio) {
             mHoloDarkRadio.setSelection(true);
             mHoloLightRadio.setSelection(false);
@@ -683,7 +755,7 @@
                             // Clicked on some of the sample art
                             if (event.widget instanceof ImageControl) {
                                 ImageControl image = (ImageControl) event.widget;
-                                mSelectedClipart = (String) image.getData();
+                                mValues.clipartName = (String) image.getData();
                                 close();
 
                                 for (Control c : mClipartPreviewPanel.getChildren()) {
@@ -691,8 +763,8 @@
                                 }
                                 if (mClipartPreviewPanel.getChildren().length == 0) {
                                     try {
-                                        BufferedImage icon =
-                                                GraphicGenerator.getClipartIcon(mSelectedClipart);
+                                        BufferedImage icon = GraphicGenerator.getClipartIcon(
+                                                mValues.clipartName);
                                         if (icon != null) {
                                             Display display = mClipartForm.getDisplay();
                                             Image swtImage = SwtUtils.convertToSwt(display, icon,
@@ -746,6 +818,7 @@
             if (rgb != null) {
                 // Dispose the old color, create the
                 // new one, and set into the label
+                mValues.background = rgb;
                 updateColor(mBgButton.getDisplay(), rgb, true /*background*/);
             }
         } else if (source == mFgButton) {
@@ -756,6 +829,7 @@
             if (rgb != null) {
                 // Dispose the old color, create the
                 // new one, and set into the label
+                mValues.foreground = rgb;
                 updateColor(mFgButton.getDisplay(), rgb, false /*background*/);
             }
         }
@@ -782,6 +856,7 @@
         }
 
         if (source == mPaddingSlider) {
+            mValues.padding = getPadding();
             mPercentLabel.setText(Integer.toString(getPadding()) + '%');
 
             // When dragging the slider, only do periodic updates
@@ -791,6 +866,24 @@
         requestUpdatePreview(updateQuickly);
     }
 
+    private void setShape(GraphicGenerator.Shape shape) {
+        if (shape == GraphicGenerator.Shape.SQUARE) {
+            mSquareRadio.setSelection(true);
+            mCircleButton.setSelection(false);
+            mNoShapeRadio.setSelection(false);
+        } else if (shape == GraphicGenerator.Shape.CIRCLE) {
+            mCircleButton.setSelection(true);
+            mSquareRadio.setSelection(false);
+            mNoShapeRadio.setSelection(false);
+        } else if (shape == GraphicGenerator.Shape.NONE) {
+            mNoShapeRadio.setSelection(true);
+            mCircleButton.setSelection(false);
+            mSquareRadio.setSelection(false);
+        } else {
+            assert false : shape;
+        }
+    }
+
     private void updateFontLabel(Font f) {
         FontData[] fd = f.getFontData();
         FontData primary = fd[0];
@@ -909,86 +1002,90 @@
         mPreviewArea.layout(true);
     }
 
-    Map<String, Map<String, BufferedImage>> generateImages(boolean previewOnly) {
+    public Map<String, Map<String, BufferedImage>> generateImages(boolean previewOnly) {
         // Map of ids to images: Preserve insertion order (the densities)
         Map<String, Map<String, BufferedImage>> categoryMap =
                 new LinkedHashMap<String, Map<String, BufferedImage>>();
 
-        CreateAssetSetWizard wizard = (CreateAssetSetWizard) getWizard();
-        AssetType type = wizard.getAssetType();
-        boolean crop = mTrimCheckBox.getSelection();
+        AssetType type = mValues.type;
+        boolean trim = mValues.trim;
 
         BufferedImage sourceImage = null;
-        if (mImageRadio.getSelection()) {
-            // Load the image
-            // TODO: Only do this when the source image type is image
-            String path = mImagePathText.getText().trim();
-            if (path.length() == 0) {
-                setErrorMessage("Enter a filename");
-                return Collections.emptyMap();
-            }
-            File file = new File(path);
-            if (!file.exists()) {
-                setErrorMessage(String.format("%1$s does not exist", file.getPath()));
-                return Collections.emptyMap();
-            }
-
-            setErrorMessage(null);
-            sourceImage = getImage(path, false);
-            if (sourceImage != null) {
-                if (crop) {
-                    sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
+        switch (mValues.sourceType) {
+            case IMAGE: {
+                // Load the image
+                // TODO: Only do this when the source image type is image
+                String path = mValues.imagePath != null ? mValues.imagePath.getPath() : "";
+                if (path.length() == 0) {
+                    setErrorMessage("Enter a filename");
+                    return Collections.emptyMap();
                 }
-                int padding = getPadding();
-                if (padding != 0) {
-                    sourceImage = Util.paddedImage(sourceImage, padding);
-                }
-            }
-        } else if (mTextRadio.getSelection()) {
-            String text = mText.getText();
-            TextRenderUtil.Options options = new TextRenderUtil.Options();
-            options.font = getSelectedFont();
-            int color;
-            if (type.needsColors()) {
-                color = 0xFF000000 | (mFgColor.red << 16) | (mFgColor.green << 8) | mFgColor.blue;
-            } else {
-                color = 0xFFFFFFFF;
-            }
-            options.foregroundColor = color;
-            sourceImage = TextRenderUtil.renderTextImage(text, getPadding(), options);
-
-            if (crop) {
-                sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
-            }
-
-            int padding = getPadding();
-            if (padding != 0) {
-                sourceImage = Util.paddedImage(sourceImage, padding);
-            }
-        } else {
-            assert mClipartRadio.getSelection();
-            assert mSelectedClipart != null;
-            try {
-                sourceImage = GraphicGenerator.getClipartImage(mSelectedClipart);
-
-                if (crop) {
-                    sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
+                File file = new File(path);
+                if (!file.exists()) {
+                    setErrorMessage(String.format("%1$s does not exist", file.getPath()));
+                    return Collections.emptyMap();
                 }
 
+                setErrorMessage(null);
+                sourceImage = getImage(path, false);
+                if (sourceImage != null) {
+                    if (trim) {
+                        sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
+                    }
+                    if (mValues.padding != 0) {
+                        sourceImage = Util.paddedImage(sourceImage, mValues.padding);
+                    }
+                }
+                break;
+            }
+            case CLIPART: {
+                try {
+                    sourceImage = GraphicGenerator.getClipartImage(mValues.clipartName);
+
+                    if (trim) {
+                        sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
+                    }
+
+                    if (type.needsColors()) {
+                        int color = 0xFF000000 | (mFgColor.red << 16) | (mFgColor.green << 8)
+                                | mFgColor.blue;
+                        Paint paint = new java.awt.Color(color);
+                        sourceImage = Util.filledImage(sourceImage, paint);
+                    }
+
+                    int padding = mValues.padding;
+                    if (padding != 0) {
+                        sourceImage = Util.paddedImage(sourceImage, padding);
+                    }
+                } catch (IOException e) {
+                    AdtPlugin.log(e, null);
+                    return categoryMap;
+                }
+                break;
+            }
+            case TEXT: {
+                String text = mValues.text;
+                TextRenderUtil.Options options = new TextRenderUtil.Options();
+                options.font = getSelectedFont();
+                int color;
                 if (type.needsColors()) {
-                    int color = 0xFF000000 | (mFgColor.red << 16) | (mFgColor.green << 8)
-                            | mFgColor.blue;
-                    Paint paint = new java.awt.Color(color);
-                    sourceImage = Util.filledImage(sourceImage, paint);
+                    color = 0xFF000000
+                            | (mFgColor.red << 16) | (mFgColor.green << 8) | mFgColor.blue;
+                } else {
+                    color = 0xFFFFFFFF;
+                }
+                options.foregroundColor = color;
+                sourceImage = TextRenderUtil.renderTextImage(text, mValues.padding, options);
+
+                if (trim) {
+                    sourceImage = ImageUtils.cropBlank(sourceImage, null, TYPE_INT_ARGB);
                 }
 
-                int padding = getPadding();
+                int padding = mValues.padding;
                 if (padding != 0) {
                     sourceImage = Util.paddedImage(sourceImage, padding);
                 }
-            } catch (IOException e) {
-                AdtPlugin.log(e, null);
-                return categoryMap;
+                break;
             }
         }
 
@@ -999,8 +1096,14 @@
                 generator = new LauncherIconGenerator();
                 LauncherIconGenerator.LauncherOptions launcherOptions =
                         new LauncherIconGenerator.LauncherOptions();
-                launcherOptions.shape = mCircleButton.getSelection()
-                        ? GraphicGenerator.Shape.CIRCLE : GraphicGenerator.Shape.SQUARE;
+                if (mCircleButton.getSelection()) {
+                    launcherOptions.shape = GraphicGenerator.Shape.CIRCLE;
+                } else if (mSquareRadio.getSelection()) {
+                    launcherOptions.shape = GraphicGenerator.Shape.SQUARE;
+                } else {
+                    assert mNoShapeRadio.getSelection();
+                    launcherOptions.shape = GraphicGenerator.Shape.NONE;
+                }
                 launcherOptions.crop = mCropRadio.getSelection();
 
                 if (SUPPORT_LAUNCHER_ICON_TYPES) {
@@ -1038,8 +1141,7 @@
                 generator = new NotificationIconGenerator();
                 NotificationIconGenerator.NotificationOptions notificationOptions =
                         new NotificationIconGenerator.NotificationOptions();
-                notificationOptions.shape = mCircleButton.getSelection()
-                        ? GraphicGenerator.Shape.CIRCLE : GraphicGenerator.Shape.SQUARE;
+                notificationOptions.shape = mValues.shape;
                 options = notificationOptions;
                 break;
             }
@@ -1054,11 +1156,15 @@
 
         options.sourceImage = sourceImage;
 
-        IProject project = wizard.getProject();
-        Pair<Integer, Integer> v = ManifestInfo.computeSdkVersions(project);
-        options.minSdk = v.getFirst();
+        IProject project = mValues.project;
+        if (mValues.minSdk != -1) {
+            options.minSdk = mValues.minSdk;
+        } else {
+            Pair<Integer, Integer> v = ManifestInfo.computeSdkVersions(project);
+            options.minSdk = v.getFirst();
+        }
 
-        String baseName = wizard.getBaseName();
+        String baseName = mValues.outputName;
         generator.generate(null, categoryMap, this, options, baseName);
 
         return categoryMap;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizard.java
index f42960c..0b5fc8e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizard.java
@@ -71,6 +71,7 @@
     private ConfigureAssetSetPage mConfigureAssetPage;
     private IProject mInitialProject;
     private List<IResource> mCreatedFiles;
+    private CreateAssetSetWizardState mValues = new CreateAssetSetWizardState();
 
     /** Creates a new asset set wizard */
     public CreateAssetSetWizard() {
@@ -79,24 +80,21 @@
 
     @Override
     public void addPages() {
-        mChooseAssetPage = new ChooseAssetTypePage();
-        mChooseAssetPage.setProject(mInitialProject);
-        mConfigureAssetPage = new ConfigureAssetSetPage();
+        mValues.project = mInitialProject;
+
+        mChooseAssetPage = new ChooseAssetTypePage(mValues);
+        mConfigureAssetPage = new ConfigureAssetSetPage(mValues);
 
         addPage(mChooseAssetPage);
         addPage(mConfigureAssetPage);
     }
 
-    String getBaseName() {
-        return mChooseAssetPage.getOutputName();
-    }
-
     @Override
     public boolean performFinish() {
         Map<String, Map<String, BufferedImage>> categories =
                 mConfigureAssetPage.generateImages(false);
 
-        IProject project = getProject();
+        IProject project = mValues.project;
 
         // Write out the images into the project
         boolean yesToAll = false;
@@ -236,28 +234,10 @@
         }
     }
 
-    /**
-     * Returns the project to be used by the wizard (which may differ from the
-     * project initialized by {@link #init(IWorkbench, IStructuredSelection)} or
-     * set by {@link #setProject(IProject)} if the user changes the project
-     * in the first page of the wizard.
-     */
-    IProject getProject() {
-        if (mChooseAssetPage != null) {
-            return mChooseAssetPage.getProject();
-        } else {
-            return mInitialProject;
-        }
-    }
-
     /** Sets the initial project to be used by the wizard */
     void setProject(IProject project) {
         mInitialProject = project;
-    }
-
-    /** Returns the {@link AssetType} to create */
-    AssetType getAssetType() {
-        return mChooseAssetPage.getAssetType();
+        mValues.project = project;
     }
 
     @Override
@@ -265,9 +245,7 @@
         setHelpAvailable(false);
 
         mInitialProject = guessProject(selection);
-        if (mChooseAssetPage != null) {
-            mChooseAssetPage.setProject(mInitialProject);
-        }
+        mValues.project = mInitialProject;
     }
 
     private IProject guessProject(IStructuredSelection selection) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizardState.java
new file mode 100644
index 0000000..624a99a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizardState.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.assetstudio;
+
+import com.android.assetstudiolib.GraphicGenerator.Shape;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.swt.graphics.RGB;
+
+import java.io.File;
+
+/**
+ * Value object for the AssetStudio wizard. These values are both set by the
+ * wizard as well as read by the wizard initially, so passing in a configured
+ * {@link CreateAssetSetWizardState} to the icon generator is possible.
+ */
+public class CreateAssetSetWizardState {
+    /**
+     * The type of asset being created. This field is static such that when you
+     * bring up the wizard repeatedly (for example to create multiple
+     * notification icons) you don't have to keep selecting the same type over
+     * and over.
+     */
+    public static AssetType sLastType = AssetType.LAUNCHER;
+
+    /** The type of asset to be created */
+    public AssetType type = sLastType;
+
+    /** The base name to use for the created icons */
+    public String outputName;
+
+    /** The minimum SDK being targeted */
+    public int minSdk = -1;
+
+    /** The project to create the icons into */
+    public IProject project;
+
+    /** Whether empty space around the source image should be trimmed */
+    public boolean trim;
+
+    /** The type of source the icon is being created from */
+    public SourceType sourceType = SourceType.TEXT;
+
+    /** If {@link #sourceType} is a {@link SourceType#CLIPART}, the name of the clipart image */
+    public String clipartName;
+
+    /** If {@link #sourceType} is a {@link SourceType#IMAGE}, the path to the input image */
+    public File imagePath;
+
+    /** If {@link #sourceType} is a {@link SourceType#TEXT}, the text to render */
+    public String text = "aA";
+
+    /** The amount of padding to add around the source image */
+    public int padding = 15;
+
+    /** The background shape */
+    public Shape shape = Shape.SQUARE;
+
+    /** The background color to use for the shape (unless the shape is {@link Shape#NONE} */
+    public RGB background = new RGB(0xff, 0x00, 0x00);
+
+    /** The background color to use for the text or clipart (unless shape is {@link Shape#NONE} */
+    public RGB foreground = new RGB(0x00, 0x00, 0x00);
+
+    /** Types of sources that the asset studio can use to generate icons from */
+    public enum SourceType {
+        /** Generate the icon using the image pointed to by {@link #imagePath} */
+        IMAGE,
+
+        /** Generate the icon using the clipart named by {@link #clipartName} */
+        CLIPART,
+
+        /** Generate the icon using the text in {@link #text} */
+        TEXT
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
index c213005..b471c3f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
@@ -23,6 +23,8 @@
 import com.android.ide.eclipse.adt.internal.build.Messages;
 import com.android.ide.eclipse.adt.internal.build.RenderScriptProcessor;
 import com.android.ide.eclipse.adt.internal.build.SourceProcessor;
+import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
+import com.android.ide.eclipse.adt.internal.lint.LintDeltaProcessor;
 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
@@ -293,6 +295,11 @@
                     dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, mProcessors);
                     delta.accept(dv);
 
+                    // Check for errors on save/build, if enabled
+                    if (AdtPrefs.getPrefs().isLintOnSave()) {
+                        LintDeltaProcessor.create().process(delta);
+                    }
+
                     // Check to see if Manifest.xml, Manifest.java, or R.java have changed:
                     mMustCompileResources |= dv.getCompileResources();
 
@@ -641,6 +648,9 @@
         removeMarkersFromContainer(project, AdtConstants.MARKER_AIDL);
         removeMarkersFromContainer(project, AdtConstants.MARKER_RENDERSCRIPT);
         removeMarkersFromContainer(project, AdtConstants.MARKER_ANDROID);
+
+        // Also clean up lint
+        EclipseLintClient.clearMarkers(project);
     }
 
     @Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
index b7c95b5..7a2658b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
@@ -305,7 +305,7 @@
                 Node attr = attrs.item(n);
                 if (XmlnsAttributeDescriptor.XMLNS.equals(attr.getPrefix())) {
                     String uri = attr.getNodeValue();
-                    if (SdkConstants.NS_RESOURCES.equals(uri)) {
+                    if (nsUri.equals(uri)) {
                         return attr.getLocalName();
                     }
                     visited.add(uri);
@@ -316,7 +316,7 @@
         // Use a sensible default prefix if we can't find one.
         // We need to make sure the prefix is not one that was declared in the scope
         // visited above.
-        prefix = SdkConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$
+        prefix = SdkConstants.NS_RESOURCES.equals(nsUri) ? "android" : "app"; //$NON-NLS-1$ //$NON-NLS-2$
         String base = prefix;
         for (int i = 1; visited.contains(prefix); i++) {
             prefix = base + Integer.toString(i);
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..803478b 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
@@ -576,13 +588,15 @@
      * Utility method that creates a Job to run Lint on the current document.
      * Does not wait for the job to finish - just returns immediately.
      *
-     * @see EclipseLintRunner#startLint(java.util.List, IDocument, boolean, boolean)
+     * @return a new job, or null
+     * @see EclipseLintRunner#startLint(java.util.List, IResource, IDocument,
+     *      boolean, boolean)
      */
     @Nullable
     public Job startLintJob() {
         IFile file = getInputFile();
         if (file != null) {
-            return EclipseLintRunner.startLint(Collections.singletonList(file),
+            return EclipseLintRunner.startLint(Collections.singletonList(file), file,
                     getStructuredDocument(), false /*fatalOnly*/, false /*show*/);
         }
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
index 8ff2a34..bac7c56 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
@@ -39,6 +39,8 @@
 import static com.android.sdklib.xml.AndroidManifest.NODE_ACTIVITY;
 import static com.android.sdklib.xml.AndroidManifest.NODE_SERVICE;
 
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
 import com.android.annotations.VisibleForTesting;
 import com.android.ide.common.resources.ResourceFile;
 import com.android.ide.common.resources.ResourceFolder;
@@ -1079,14 +1081,25 @@
         return getResourceLinks(range, url);
     }
 
+    private static IHyperlink[] getResourceLinks(@Nullable IRegion range, @NonNull String url) {
+        IProject project = Hyperlinks.getProject();
+        FolderConfiguration configuration = getConfiguration();
+        return getResourceLinks(range, url, project, configuration);
+    }
+
     /**
      * Computes hyperlinks to resource definitions for resource urls (e.g.
      * {@code @android:string/ok} or {@code @layout/foo}. May create multiple links.
+     * @param range TBD
+     * @param url the resource url
+     * @param project the relevant project
+     * @param configuration the applicable configuration
+     * @return an array of hyperlinks, or null
      */
-    private static IHyperlink[] getResourceLinks(IRegion range, String url) {
+    @Nullable
+    public static IHyperlink[] getResourceLinks(@Nullable IRegion range, @NonNull String url,
+            @NonNull IProject project,  @Nullable FolderConfiguration configuration) {
         List<IHyperlink> links = new ArrayList<IHyperlink>();
-        IProject project = Hyperlinks.getProject();
-        FolderConfiguration configuration = getConfiguration();
 
         Pair<ResourceType,String> resource = ResourceHelper.parseResource(url);
         if (resource == null || resource.getFirst() == null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
index b00656e..19ff37a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java
@@ -54,6 +54,7 @@
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -100,7 +101,7 @@
      *        entries in the form "elem-name/attr-name". Elem-name can be "*".
      * @param overrides A map [attribute name => ITextAttributeCreator creator].
      */
-    public static void appendAttributes(ArrayList<AttributeDescriptor> attributes,
+    public static void appendAttributes(List<AttributeDescriptor> attributes,
             String elementXmlName,
             String nsUri, AttributeInfo[] infos,
             Set<String> requiredAttributes,
@@ -131,7 +132,7 @@
      *        a "*" to its UI name as a hint for the user.)
      * @param overrides A map [attribute name => ITextAttributeCreator creator].
      */
-    public static void appendAttribute(ArrayList<AttributeDescriptor> attributes,
+    public static void appendAttribute(List<AttributeDescriptor> attributes,
             String elementXmlName,
             String nsUri,
             AttributeInfo info, boolean required,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
index 754da29..9cdbf81 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
@@ -83,7 +83,7 @@
         mPrefs = prefs;
         mStyle = style;
         if (lineSeparator == null) {
-            lineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$
+            lineSeparator = AdtUtils.getLineSeparator();
         }
         mLineSeparator = lineSeparator;
     }
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 9e821ad..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);
@@ -750,7 +753,10 @@
 
             // Check if we can find a custom view specific to this project.
             // This only works if there's an actual matching custom class in the project.
-            desc = CustomViewDescriptorService.getInstance().getDescriptor(project, xmlLocalName);
+            if (xmlLocalName.indexOf('.') != -1) {
+                desc = CustomViewDescriptorService.getInstance().getDescriptor(project,
+                        xmlLocalName);
+            }
 
             if (desc == null) {
                 // If we didn't find a custom view, create a synthetic one using the
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
index 6fe6a95..0a80b5e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
@@ -16,9 +16,16 @@
 
 package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
 
+import static com.android.AndroidConstants.FD_RES_LAYOUT;
 import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX;
 import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_RESOURCE_REF;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_STYLE;
+import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI;
 
+import com.android.AndroidConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
 import com.android.ide.common.api.Rect;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.ide.common.rendering.api.StyleResourceValue;
@@ -38,8 +45,9 @@
 import com.android.ide.common.resources.configuration.VersionQualifier;
 import com.android.ide.common.sdk.LoadStatus;
 import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
@@ -61,27 +69,44 @@
 import com.android.sdklib.repository.PkgProps;
 import com.android.sdklib.util.SparseIntArray;
 import com.android.util.Pair;
+import com.google.common.collect.Maps;
 
+import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.jdt.ui.ISharedImages;
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Combo;
 import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
 import org.eclipse.ui.IEditorPart;
 import org.eclipse.ui.IWorkbench;
 import org.eclipse.ui.IWorkbenchPage;
 import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.PlatformUI;
+import org.eclipse.wb.internal.core.nls.ui.FlagImagesRepository;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -119,9 +144,22 @@
  * - Target reload. This is when the target used by the project is the edited file has finished<br>
  *   loading.<br>
  */
-public class ConfigurationComposite extends Composite {
-    private final static String SEP = ":"; //$NON-NLS-1$
-    private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
+public class ConfigurationComposite extends Composite implements SelectionListener {
+    public static final String ATTR_CONTEXT = "context";          //$NON-NLS-1$
+    private static final String ICON_SQUARE = "square";           //$NON-NLS-1$
+    private static final String ICON_LANDSCAPE = "landscape";     //$NON-NLS-1$
+    private static final String ICON_PORTRAIT = "portrait";       //$NON-NLS-1$
+    private static final String ICON_LANDSCAPE_FLIP = "flip_landscape";//$NON-NLS-1$
+    private static final String ICON_PORTRAIT_FLIP = "flip_portrait";//$NON-NLS-1$
+    private static final String ICON_DISPLAY = "display";         //$NON-NLS-1$
+    private static final String ICON_NEW_CONFIG = "newConfig";    //$NON-NLS-1$
+    private static final String ICON_GLOBE = "globe";             //$NON-NLS-1$
+    private static final String ICON_THEMES = "themes";           //$NON-NLS-1$
+    private static final String ICON_ACTIVITY = "activity";       //$NON-NLS-1$
+    private final static String SEP = ":";                        //$NON-NLS-1$
+    private final static String SEP_LOCALE = "-";                 //$NON-NLS-1$
+    private final static String MARKER_FRAMEWORK = "-";           //$NON-NLS-1$
+    private final static String MARKER_PROJECT = "+";           //$NON-NLS-1$
 
     /**
      * Setting name for project-wide setting controlling rendering target and locale which
@@ -137,28 +175,16 @@
     public final static QualifiedName NAME_CONFIG_STATE =
         new QualifiedName(AdtPlugin.PLUGIN_ID, "state");//$NON-NLS-1$
 
-    private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$
-
     private final static int LOCALE_LANG = 0;
     private final static int LOCALE_REGION = 1;
 
-    private Label mCurrentLayoutLabel;
-    private Button mCreateButton;
-
-    private Combo mDeviceCombo;
-    private Combo mDeviceConfigCombo;
-    private Combo mLocaleCombo;
-    private Combo mUiModeCombo;
-    private Combo mNightCombo;
-    private Combo mThemeCombo;
-    private Combo mTargetCombo;
-
-    /**
-     * List of booleans, matching item for item the theme names in the mThemeCombo
-     * combobox, where each boolean represents whether the corresponding theme is a
-     * project theme
-     */
-    private List<Boolean> mIsProjectTheme = new ArrayList<Boolean>(40);
+    private ToolItem mDeviceCombo;
+    private ToolItem mThemeCombo;
+    private ToolItem mOrientationCombo;
+    private ToolItem mLocaleCombo;
+    private ToolItem mTargetCombo;
+    private ToolItem mConfigCombo;
+    private ToolItem mActivityCombo;
 
     /** updates are disabled if > 0 */
     private int mDisableUpdates = 0;
@@ -166,7 +192,9 @@
     private List<LayoutDevice> mDeviceList;
     private final List<IAndroidTarget> mTargetList = new ArrayList<IAndroidTarget>();
 
-    private final ArrayList<ResourceQualifier[] > mLocaleList =
+    private final List<String> mThemeList = new ArrayList<String>();
+
+    private final List<ResourceQualifier[] > mLocaleList =
         new ArrayList<ResourceQualifier[]>();
 
     private final ConfigState mState = new ConfigState();
@@ -214,7 +242,7 @@
 
         /**
          * Called when the current theme changes. The theme can be queried with
-         * {@link ConfigurationComposite#getTheme()}.
+         * {@link ConfigurationComposite#getThemeName()}.
          */
         void onThemeChange();
 
@@ -224,6 +252,13 @@
         void onCreate();
 
         /**
+         * Called when an associated activity is picked
+         *
+         * @param fqcn the fully qualified class name for the associated activity context
+         */
+        void onSetActivity(String fqcn);
+
+        /**
          * Called before the rendering target changes.
          * @param oldTarget the old rendering target
          */
@@ -253,19 +288,29 @@
         String configName;
         ResourceQualifier[] locale;
         String theme;
+        // TODO: Need to know if it's the project theme or the framework theme!
         /** UI mode. Guaranteed to be non null */
         UiMode uiMode = UiMode.NORMAL;
         /** night mode. Guaranteed to be non null */
         NightMode night = NightMode.NOTNIGHT;
         /** the version being targeted for rendering */
         IAndroidTarget target;
+        String activity;
 
         String getData() {
             StringBuilder sb = new StringBuilder();
             if (device != null) {
                 sb.append(device.getName());
                 sb.append(SEP);
-                sb.append(configName);
+                if (configName == null) {
+                    DeviceConfig config = getSelectedDeviceConfig();
+                    if (config != null) {
+                        configName = config.getName();
+                    }
+                }
+                if (configName != null) {
+                    sb.append(configName);
+                }
                 sb.append(SEP);
                 if (isLocaleSpecificLayout() && locale != null) {
                     if (locale[0] != null && locale[1] != null) {
@@ -276,16 +321,34 @@
                     }
                 }
                 sb.append(SEP);
-                sb.append(theme);
+                // Need to escape the theme: if we write the full theme style, then
+                // we can end up with ":"'s in the string (as in @android:style/Theme) which
+                // can be mistaken for {@link #SEP}. Instead use {@link #MARKER_FRAMEWORK}.
+                if (theme != null) {
+                    String themeName = ResourceHelper.styleToTheme(theme);
+                    if (theme.startsWith(PREFIX_STYLE)) {
+                        sb.append(MARKER_PROJECT);
+                    } else if (theme.startsWith(PREFIX_ANDROID_STYLE)) {
+                        sb.append(MARKER_FRAMEWORK);
+                    }
+                    sb.append(themeName);
+                }
                 sb.append(SEP);
-                sb.append(uiMode.getResourceValue());
+                if (uiMode != null) {
+                    sb.append(uiMode.getResourceValue());
+                }
                 sb.append(SEP);
-                sb.append(night.getResourceValue());
+                if (night != null) {
+                    sb.append(night.getResourceValue());
+                }
                 sb.append(SEP);
 
                 // We used to store the render target here in R9. Leave a marker
                 // to ensure that we don't reuse this slot; add new extra fields after it.
                 sb.append(SEP);
+                if (activity != null) {
+                    sb.append(activity);
+                }
             }
 
             return sb.toString();
@@ -293,14 +356,20 @@
 
         boolean setData(String data) {
             String[] values = data.split(SEP);
-            if (values.length == 6 || values.length == 7) {
+            if (values.length >= 6 && values.length <= 8) {
                 for (LayoutDevice d : mDeviceList) {
                     if (d.getName().equals(values[0])) {
                         device = d;
-                        FolderConfiguration config = device.getFolderConfigByName(values[1]);
-                        if (config != null) {
+                        FolderConfiguration config = null;
+                        if (!values[1].isEmpty() && !values[1].equals("null")) { //$NON-NLS-1$
                             configName = values[1];
-
+                            config = device.getFolderConfigByName(configName);
+                        } else if (device.getConfigs().size() > 0) {
+                            DeviceConfig first = device.getConfigs().get(0);
+                            configName = first.getName();
+                            config = first.getConfig();
+                        }
+                        if (config != null) {
                             // Load locale. Note that this can get overwritten by the
                             // project-wide settings read below.
                             locale = new ResourceQualifier[2];
@@ -314,7 +383,15 @@
                                 }
                             }
 
+                            // Decode the theme name: See {@link #getData}
                             theme = values[3];
+                            if (theme.startsWith(MARKER_FRAMEWORK)) {
+                                theme = PREFIX_ANDROID_STYLE
+                                        + theme.substring(MARKER_FRAMEWORK.length());
+                            } else if (theme.startsWith(MARKER_PROJECT)) {
+                                theme = PREFIX_STYLE + theme.substring(MARKER_PROJECT.length());
+                            }
+
                             uiMode = UiMode.getEnum(values[4]);
                             if (uiMode == null) {
                                 uiMode = UiMode.NORMAL;
@@ -336,6 +413,10 @@
                             }
                             target = pair.getSecond();
 
+                            if (values.length == 8) {
+                                activity = values[7];
+                            }
+
                             return true;
                         }
                     }
@@ -399,133 +480,86 @@
     public ConfigurationComposite(IConfigListener listener,
             Composite parent, int style, String initialState) {
         super(parent, style);
+        setVisible(false); // Delayed until the targets are loaded
+
         mListener = listener;
         mInitialState = initialState;
+        setLayout(new GridLayout(1, false));
 
-        GridLayout gl;
-        GridData gd;
-        int cols = 7;  // device+config+dock+day+separator*2+theme
+        IconFactory icons = IconFactory.getInstance();
 
-        // ---- First line: editing config display, locale, theme, create-button
-        Composite labelParent = new Composite(this, SWT.NONE);
-        labelParent.setLayout(gl = new GridLayout(5, false));
-        gl.marginWidth = gl.marginHeight = 0;
-        gl.marginTop = 3;
-        labelParent.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
-        gd.horizontalSpan = cols;
+        // TODO: Consider switching to a CoolBar instead
+        ToolBar toolBar = new ToolBar(this, SWT.WRAP | SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
+        toolBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
 
-        new Label(labelParent, SWT.NONE).setText("Editing config:");
-        mCurrentLayoutLabel = new Label(labelParent, SWT.NONE);
-        mCurrentLayoutLabel.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
-        gd.widthHint = 50;
+        mConfigCombo = new ToolItem(toolBar, SWT.DROP_DOWN | SWT.BOLD);
+        mConfigCombo.setImage(null);
+        mConfigCombo.setToolTipText("Configuration to render this layout with in Eclipse");
 
-        mLocaleCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY);
-        mLocaleCombo.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                onLocaleChange();
-            }
-        });
+        @SuppressWarnings("unused")
+        ToolItem separator2 = new ToolItem(toolBar, SWT.SEPARATOR);
 
-        // Layout bug workaround. Without this, in -some- scenarios the Locale combo box was
-        // coming up tiny. Setting a minimumWidth hint does not work either. We need to have
-        // 2 or more items in the locale combo box when the layout is first run. These items
-        // are removed as part of the locale initialization when the SDK is loaded.
-        mLocaleCombo.add("Locale"); //$NON-NLS-1$  // Dummy place holders
-        mLocaleCombo.add("Locale"); //$NON-NLS-1$
+        mDeviceCombo = new ToolItem(toolBar, SWT.DROP_DOWN);
+        mDeviceCombo.setImage(icons.getIcon(ICON_DISPLAY));
 
-        mTargetCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY);
-        mTargetCombo.add("Android AOSP"); //$NON-NLS-1$  // Dummy place holders
-        mTargetCombo.add("Android AOSP"); //$NON-NLS-1$
-        mTargetCombo.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                onRenderingTargetChange();
-            }
-        });
+        @SuppressWarnings("unused")
+        ToolItem separator3 = new ToolItem(toolBar, SWT.SEPARATOR);
 
-        mCreateButton = new Button(labelParent, SWT.PUSH | SWT.FLAT);
-        mCreateButton.setText("Create...");
-        mCreateButton.setEnabled(false);
-        mCreateButton.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                if (mListener != null) {
-                    mListener.onCreate();
-                }
-            }
-        });
+        mOrientationCombo = new ToolItem(toolBar, SWT.DROP_DOWN);
+        mOrientationCombo.setImage(icons.getIcon(ICON_PORTRAIT));
+        mOrientationCombo.setToolTipText("Flip Orientation");
 
-        // ---- 2nd line: device/config/locale/theme Combos, create button.
+        @SuppressWarnings("unused")
+        ToolItem separator4 = new ToolItem(toolBar, SWT.SEPARATOR);
 
-        setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
-        setLayout(gl = new GridLayout(cols, false));
-        gl.marginHeight = 0;
-        gl.horizontalSpacing = 0;
+        mThemeCombo = new ToolItem(toolBar, SWT.DROP_DOWN);
+        mThemeCombo.setImage(icons.getIcon(ICON_THEMES));
 
-        mDeviceCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
-        mDeviceCombo.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mDeviceCombo.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                onDeviceChange(true /* recomputeLayout*/);
-            }
-        });
+        @SuppressWarnings("unused")
+        ToolItem separator5 = new ToolItem(toolBar, SWT.SEPARATOR);
 
-        mDeviceConfigCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
-        mDeviceConfigCombo.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mDeviceConfigCombo.addSelectionListener(new SelectionAdapter() {
-            @Override
-             public void widgetSelected(SelectionEvent e) {
-                onDeviceConfigChange();
-            }
-        });
+        mActivityCombo = new ToolItem(toolBar, SWT.DROP_DOWN);
+        mActivityCombo.setToolTipText("Associated activity or fragment providing context");
+        // The JDT class icon is lopsided, presumably because they've left room in the
+        // bottom right corner for badges (for static, final etc). Unfortunately, this
+        // means that the icon looks out of place when sitting close to the language globe
+        // icon, the theme icon, etc so that it looks vertically misaligned:
+        //mActivityCombo.setImage(JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_CLASS));
+        // ...so use one that is centered instead:
+        mActivityCombo.setImage(icons.getIcon(ICON_ACTIVITY));
 
-        // first separator
-        Label separator = new Label(this, SWT.SEPARATOR | SWT.VERTICAL);
-        separator.setLayoutData(gd = new GridData(
-                GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL));
-        gd.heightHint = 0;
+        @SuppressWarnings("unused")
+        ToolItem separator6 = new ToolItem(toolBar, SWT.SEPARATOR);
 
-        mUiModeCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
-        mUiModeCombo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL
-                | GridData.GRAB_HORIZONTAL));
-        for (UiMode mode : UiMode.values()) {
-            mUiModeCombo.add(mode.getLongDisplayValue());
+        //ToolBar rightToolBar = new ToolBar(this, SWT.WRAP | SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
+        //rightToolBar.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+        ToolBar rightToolBar = toolBar;
+
+        mLocaleCombo = new ToolItem(rightToolBar, SWT.DROP_DOWN);
+        mLocaleCombo.setImage(icons.getIcon(ICON_GLOBE));
+        mLocaleCombo.setToolTipText("Locale to use when rendering layouts in Eclipse");
+
+        @SuppressWarnings("unused")
+        ToolItem separator7 = new ToolItem(rightToolBar, SWT.SEPARATOR);
+
+        mTargetCombo = new ToolItem(rightToolBar, SWT.DROP_DOWN);
+        mTargetCombo.setImage(AdtPlugin.getAndroidLogo());
+        mLocaleCombo.setToolTipText("Android version to use when rendering layouts in Eclipse");
+
+        addConfigurationMenuListener(mConfigCombo);
+        addActivityMenuListener(mActivityCombo);
+        addLocaleMenuListener(mLocaleCombo);
+        addDeviceMenuListener(mDeviceCombo);
+        addTargetMenuListener(mTargetCombo);
+        addThemeListener(mThemeCombo);
+        addOrientationMenuListener(mOrientationCombo);
+    }
+
+    private void updateActivity() {
+        if (mEditedFile != null) {
+            String preferred = getPreferredActivity(mEditedFile);
+            selectActivity(preferred);
         }
-        mUiModeCombo.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                onDockChange();
-            }
-        });
-
-        mNightCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
-        mNightCombo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL
-                | GridData.GRAB_HORIZONTAL));
-        for (NightMode mode : NightMode.values()) {
-            mNightCombo.add(mode.getLongDisplayValue());
-        }
-        mNightCombo.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                onDayChange();
-            }
-        });
-
-        mThemeCombo = new Combo(this, SWT.READ_ONLY | SWT.DROP_DOWN);
-        mThemeCombo.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mThemeCombo.setEnabled(false);
-
-        mThemeCombo.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                onThemeChange();
-            }
-        });
     }
 
     // ---- Init and reset/reload methods ----
@@ -575,6 +609,8 @@
             // only attempt to do anything if the SDK and targets are loaded.
             LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus();
             if (sdkStatus == LoadStatus.LOADED) {
+                setVisible(true);
+
                 LoadStatus targetStatus = Sdk.getCurrent().checkAndLoadTargetData(mProjectTarget,
                         null /*project*/);
 
@@ -589,6 +625,8 @@
 
                     // update the string showing the config value
                     updateConfigDisplay(mEditedConfig);
+
+                    updateActivity();
                 }
             }
         } finally {
@@ -682,6 +720,7 @@
                 }
 
                 if (targetStatus == LoadStatus.LOADED) {
+                    setVisible(true);
                     if (mResources == null) {
                         mResources = ResourceManager.getInstance().getProjectResources(iProject);
                     }
@@ -713,13 +752,11 @@
                     if (loadedConfigData) {
                         // first make sure we have the config to adapt
                         selectDevice(mState.device);
-                        fillConfigCombo(mState.configName);
+                        selectConfig(mState.configName);
 
                         adaptConfigSelection(false /*needBestMatch*/);
 
-                        mUiModeCombo.select(UiMode.getIndex(mState.uiMode));
-                        mNightCombo.select(NightMode.getIndex(mState.night));
-                        mTargetCombo.select(mTargetList.indexOf(mState.target));
+                        selectTarget(mState.target);
 
                         targetData = Sdk.getCurrent().getTargetData(mState.target);
                     } else {
@@ -729,10 +766,16 @@
                         IAndroidTarget target = findDefaultRenderTarget();
                         if (target != null) {
                             targetData = Sdk.getCurrent().getTargetData(target);
-                            mTargetCombo.select(mTargetList.indexOf(target));
+                            selectTarget(target);
                         }
                     }
 
+                    // Update activity: This is done before updateThemes() since
+                    // the themes selection can depend on the currently selected activity
+                    // (e.g. when there are manifest registrations for the theme to use
+                    // for a given activity)
+                    updateActivity();
+
                     // Update themes. This is done after updating the devices above,
                     // since we want to look at the chosen device size to decide
                     // what the default theme (for example, with Honeycomb we choose
@@ -756,6 +799,241 @@
         return targetData;
     }
 
+    private void selectActivity(@Nullable String fqcn) {
+        if (fqcn != null) {
+            mActivityCombo.setData(fqcn);
+            String label = getActivityLabel(fqcn, true);
+            mActivityCombo.setText(label);
+        } else {
+            mActivityCombo.setText("(Select)");
+        }
+        resizeToolBar();
+    }
+
+    @Nullable
+    private String getPreferredActivity(@NonNull IFile file) {
+        // Store/restore the activity context in the config state to help with
+        // performance if for some reason we can't write it into the XML file and to
+        // avoid having to open the model below
+        if (mState.activity != null) {
+            return mState.activity;
+        }
+
+        IProject project = file.getProject();
+
+        // Look up from XML file
+        Document document = DomUtilities.getDocument(file);
+        if (document != null) {
+            Element element = document.getDocumentElement();
+            if (element != null) {
+                String activity = element.getAttributeNS(TOOLS_URI, ATTR_CONTEXT);
+                if (activity != null && !activity.isEmpty()) {
+                    if (activity.startsWith(".") || activity.indexOf('.') == -1) { //$NON-NLS-1$
+                        ManifestInfo manifest = ManifestInfo.get(project);
+                        String pkg = manifest.getPackage();
+                        if (!pkg.isEmpty()) {
+                            if (activity.startsWith(".")) { //$NON-NLS-1$
+                                activity = pkg + activity;
+                            } else {
+                                activity = activity + "." + pkg;
+                            }
+                        }
+                    }
+
+                    mState.activity = activity;
+                    storeState();
+                    return activity;
+                }
+            }
+        }
+
+        // No, not available there: try to infer it from the code index
+        String includedIn = mListener != null ? mListener.getIncludedWithin() : null;
+
+        ManifestInfo manifest = ManifestInfo.get(project);
+        String pkg = manifest.getPackage();
+        String layoutName = ResourceHelper.getLayoutName(mEditedFile);
+
+        // If we are rendering a layout in included context, pick the theme
+        // from the outer layout instead
+        if (includedIn != null) {
+            layoutName = includedIn;
+        }
+
+        String activity = ManifestInfo.guessActivity(project, layoutName, pkg);
+
+        if (activity == null) {
+            List<String> activities = ManifestInfo.getProjectActivities(project);
+            if (activities.size() == 1) {
+                activity = activities.get(0);
+            }
+        }
+
+        if (activity != null) {
+            mState.activity = activity;
+            storeState();
+            return activity;
+        }
+
+        // TODO: Do anything else, such as pick the first activity found?
+        // Or just leave some default label instead?
+        // Also, figure out what to store in the mState so I don't keep trying
+
+        return null;
+    }
+
+    private void onSelectActivity() {
+        String activity = getSelectedActivity();;
+        mState.activity = activity;
+        saveState();
+
+        if (activity == null) {
+            return;
+        }
+
+        // See if there is a default theme assigned to this activity, and if so, use it
+        ManifestInfo manifest = ManifestInfo.get(mEditedFile.getProject());
+        Map<String, String> activityThemes = manifest.getActivityThemes();
+        String preferred = activityThemes.get(activity);
+        if (preferred != null) {
+            // Yes, switch to it
+            selectTheme(preferred);
+            onThemeChange();
+        }
+
+        // Persist in XML
+        if (mListener != null) {
+            mListener.onSetActivity(activity);
+        }
+    }
+
+
+    /** Update the toolbar whenever a label has changed, to not only
+     * cause the layout in the current toolbar to update, but to possibly
+     * wrap the toolbars and update the layout of the surrounding area.
+     */
+    private void resizeToolBar() {
+        Point size = getSize();
+        Point newSize = computeSize(size.x, SWT.DEFAULT, true);
+        setSize(newSize);
+        Composite parent = getParent();
+        parent.layout();
+        parent.redraw();
+    }
+
+    private String getActivityLabel(String fqcn, boolean brief) {
+        if (brief) {
+            return fqcn.substring(fqcn.lastIndexOf('.') + 1);
+        }
+
+        return fqcn;
+    }
+
+    String getSelectedActivity() {
+        return (String) mActivityCombo.getData();
+    }
+
+    private void selectTarget(IAndroidTarget target) {
+        mTargetCombo.setData(target);
+        String label = getRenderingTargetLabel(target, true);
+        mTargetCombo.setText(label);
+        resizeToolBar();
+    }
+
+    private static String getRenderingTargetLabel(IAndroidTarget target, boolean brief) {
+        if (target == null) {
+            return "<null>";
+        }
+
+        AndroidVersion version = target.getVersion();
+
+        if (brief) {
+            if (target.isPlatform()) {
+                return Integer.toString(version.getApiLevel());
+            } else {
+                return target.getName() + ':' + Integer.toString(version.getApiLevel());
+            }
+        }
+
+        String label = String.format("API %1$d: %2$s",
+                version.getApiLevel(),
+                target.getShortClasspathName());
+
+        return label;
+    }
+
+    private String getLocaleLabel(ResourceQualifier[] qualifiers, boolean brief) {
+        if (qualifiers == null) {
+            return null;
+        }
+
+        LanguageQualifier language = (LanguageQualifier) qualifiers[LOCALE_LANG];
+
+        if (language.hasFakeValue()) {
+            if (brief) {
+                // Just use the icon
+                return "";
+            }
+
+            boolean hasLocale = false;
+            ResourceRepository projectRes = mListener.getProjectResources();
+            if (projectRes != null) {
+                hasLocale = projectRes.getLanguages().size() > 0;
+            }
+
+            if (hasLocale) {
+                return "Other";
+            } else {
+                return "Any";
+            }
+        }
+
+        RegionQualifier region = (RegionQualifier) qualifiers[LOCALE_REGION];
+        if (region.hasFakeValue()) {
+            // TODO: Make the region string use "Other" instead of "Any" if
+            // there is more than one region for a given language
+            //if (regions.size() > 0) {
+            //    return String.format("%1$s / Other", language);
+            //} else {
+            //    return String.format("%1$s / Any", language);
+            //}
+            return language.getValue();
+        } else {
+            return String.format("%1$s / %2$s", language.getValue(), region.getValue());
+        }
+    }
+
+    private void selectLocale(ResourceQualifier[] qualifiers) {
+        mLocaleCombo.setData(qualifiers);
+        String label = getLocaleLabel(qualifiers, true);
+
+        mLocaleCombo.setText(label);
+
+        Image image = getFlagImage(qualifiers);
+        mLocaleCombo.setImage(image);
+
+        resizeToolBar();
+    }
+
+    private ResourceQualifier[] getSelectedLocale() {
+        return (ResourceQualifier[]) mLocaleCombo.getData();
+    }
+
+    private IAndroidTarget getSelectedTarget() {
+        return (IAndroidTarget) mTargetCombo.getData();
+    }
+
+    void selectTheme(String theme) {
+        assert theme.startsWith(PREFIX_STYLE) || theme.startsWith(PREFIX_ANDROID_STYLE) : theme;
+        mThemeCombo.setData(theme);
+        if (theme != null) {
+            mThemeCombo.setText(getThemeLabel(theme, true));
+        } else {
+            mThemeCombo.setText("(Set Theme)");
+        }
+        resizeToolBar();
+    }
+
     /** Return the default render target to use, or null if no strong preference */
     private IAndroidTarget findDefaultRenderTarget() {
         // Default to layoutlib version 5
@@ -966,10 +1244,11 @@
                 // select the best device anyway.
                 ConfigMatch match = selectConfigMatch(anyMatches);
                 selectDevice(mState.device = match.device);
-                fillConfigCombo(match.name);
-                mLocaleCombo.select(match.bundle.localeIndex);
-                mUiModeCombo.select(match.bundle.dockModeIndex);
-                mNightCombo.select(match.bundle.nightModeIndex);
+                selectConfig(match.name);
+                selectLocale(mLocaleList.get(match.bundle.localeIndex));
+
+                mState.uiMode = UiMode.getByIndex(match.bundle.dockModeIndex);
+                mState.night = NightMode.getByIndex(match.bundle.nightModeIndex);
 
                 // TODO: display a better warning!
                 computeCurrentConfig();
@@ -989,10 +1268,10 @@
         } else {
             ConfigMatch match = selectConfigMatch(bestMatches);
             selectDevice(mState.device = match.device);
-            fillConfigCombo(match.name);
-            mLocaleCombo.select(match.bundle.localeIndex);
-            mUiModeCombo.select(match.bundle.dockModeIndex);
-            mNightCombo.select(match.bundle.nightModeIndex);
+            selectConfig(match.name);
+            selectLocale(mLocaleList.get(match.bundle.localeIndex));
+            mState.uiMode = UiMode.getByIndex(match.bundle.dockModeIndex);
+            mState.night = NightMode.getByIndex(match.bundle.nightModeIndex);
         }
     }
 
@@ -1191,9 +1470,9 @@
         // check the device config (ie sans locale)
         boolean needConfigChange = true; // if still true, we need to find another config.
         boolean currentConfigIsCompatible = false;
-        int configIndex = mDeviceConfigCombo.getSelectionIndex();
-        if (configIndex != -1) {
-            String configName = mDeviceConfigCombo.getItem(configIndex);
+        DeviceConfig selectedConfig = getSelectedDeviceConfig();
+        if (selectedConfig != null) {
+            String configName = selectedConfig.getName();
             FolderConfiguration currentConfig = mState.device.getFolderConfigByName(configName);
             if (currentConfig != null && mEditedConfig.isMatchFor(currentConfig)) {
                 currentConfigIsCompatible = true; // current config is compatible
@@ -1233,7 +1512,7 @@
 
             if (matchName != null) {
                 selectConfig(matchName);
-                mLocaleCombo.select(localeIndex);
+                selectLocale(mLocaleList.get(localeIndex));
             } else {
                 // no match in current device with any config/locale
                 // attempt to find another device that can display this particular config.
@@ -1256,80 +1535,47 @@
         // The only trick is that the region could be null in the fileConfig but in our
         // list of locales, this is represented as a RegionQualifier with value of
         // FAKE_LOCALE_VALUE.
-        final int count = mLocaleList.size();
-        for (int i = 0 ; i < count ; i++) {
-            ResourceQualifier[] locale = mLocaleList.get(i);
-
-            // the language qualifier in the locale list is never null.
-            if (locale[LOCALE_LANG].equals(language)) {
-                // region comparison is more complex, as the region could be null.
-                if (region == null) {
-                    if (RegionQualifier.FAKE_REGION_VALUE.equals(
-                            ((RegionQualifier)locale[LOCALE_REGION]).getValue())) {
-                        // match!
-                        if (mLocaleCombo.getSelectionIndex() != i) {
-                            mLocaleCombo.select(i);
-                            changed = true;
-                        }
-                        break;
-                    }
-                } else if (region.equals(locale[LOCALE_REGION])) {
-                    // match!
-                    if (mLocaleCombo.getSelectionIndex() != i) {
-                        mLocaleCombo.select(i);
-                        changed = true;
-                    }
-                    break;
-                }
-            }
+        ResourceQualifier[] selectedLocale = getSelectedLocale();
+        //changed = prevLanguage != language || region != prevRegion;
+        if (selectedLocale != null) {
+            ResourceQualifier prevLanguage = selectedLocale[LOCALE_LANG];
+            ResourceQualifier prevRegion = selectedLocale[LOCALE_REGION];
+            changed = !prevLanguage.equals(language) || !prevRegion.equals(region);
         }
 
+        selectLocale(new ResourceQualifier[] { language, region});
+
         return changed;
     }
 
     private void updateConfigDisplay(FolderConfiguration fileConfig) {
-        String current = fileConfig.toDisplayString();
-        String layoutLabel = current != null ? current : "(Default)";
-        mCurrentLayoutLabel.setText(layoutLabel);
-        mCurrentLayoutLabel.setToolTipText(layoutLabel);
+        // Label currently hidden
+        //String current = fileConfig.toDisplayString();
+        //String current = fileConfig.getFolderName(ResourceFolderType.LAYOUT);
+        String current = mEditedFile.getParent().getName();
+        if (current.equals(AndroidConstants.FD_RES_LAYOUT)) {
+            current = "default";
+        }
+
+        // Pretty things up a bit
+        //if (current == null || current.equals("default")) {
+        //    current = "Default Configuration";
+        //}
+        mConfigCombo.setText(current);
+        resizeToolBar();
     }
 
     private void saveState() {
         if (mDisableUpdates == 0) {
-            int index = mDeviceConfigCombo.getSelectionIndex();
-            if (index != -1) {
-                mState.configName = mDeviceConfigCombo.getItem(index);
-            } else {
-                mState.configName = null;
-            }
+            DeviceConfig deviceConfig = getSelectedDeviceConfig();
+            String configName = deviceConfig != null ? deviceConfig.getName() : null;
+            mState.configName = configName;
 
             // since the locales are relative to the project, only keeping the index is enough
-            index = mLocaleCombo.getSelectionIndex();
-            if (index != -1) {
-                mState.locale = mLocaleList.get(index);
-            } else {
-                mState.locale = null;
-            }
-
-            index = mThemeCombo.getSelectionIndex();
-            if (index != -1) {
-                mState.theme = mThemeCombo.getItem(index);
-            }
-
-            index = mUiModeCombo.getSelectionIndex();
-            if (index != -1) {
-                mState.uiMode = UiMode.getByIndex(index);
-            }
-
-            index = mNightCombo.getSelectionIndex();
-            if (index != -1) {
-                mState.night = NightMode.getByIndex(index);
-            }
-
-            index = mTargetCombo.getSelectionIndex();
-            if (index != -1) {
-                mState.target = mTargetList.get(index);
-            }
+            mState.locale = getSelectedLocale();
+            mState.theme = getSelectedTheme();
+            mState.target = getRenderingTarget();
+            mState.activity = getSelectedActivity();
         }
     }
 
@@ -1340,6 +1586,475 @@
         AdtPlugin.setFileProperty(mEditedFile, NAME_CONFIG_STATE, mState.getData());
     }
 
+    private void addLocaleMenuListener(final ToolItem combo) {
+        Listener menuListener = new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
+                ResourceQualifier[] current = getSelectedLocale();
+
+                for (final ResourceQualifier[] qualifiers : mLocaleList) {
+                    String title = getLocaleLabel(qualifiers, false);
+                    MenuItem item = new MenuItem(menu, SWT.CHECK);
+                    item.setText(title);
+                    Image image = getFlagImage(qualifiers);
+                    item.setImage(image);
+
+                    boolean selected = current == qualifiers;
+                    if (selected) {
+                        item.setSelection(true);
+                    }
+
+                    item.addSelectionListener(new SelectionAdapter() {
+                        @Override
+                        public void widgetSelected(SelectionEvent e) {
+                            selectLocale(qualifiers);
+                            onLocaleChange();
+                        }
+                    });
+                }
+
+                Rectangle bounds = combo.getBounds();
+                Point location = new Point(bounds.x, bounds.y + bounds.height);
+                location = combo.getParent().toDisplay(location);
+                menu.setLocation(location.x, location.y);
+                menu.setVisible(true);
+            }
+        };
+        combo.addListener(SWT.Selection, menuListener);
+    }
+
+    private Map<String, String> mCountryToLanguage;
+
+    private String getCountry(String language, String region) {
+        if (RegionQualifier.FAKE_REGION_VALUE.equals(region)) {
+            region = "";
+        }
+
+        String country = region;
+
+        // Special cases
+        if (language.equals("ar")) {        //$NON-NLS-1$
+            country = "AE";                 //$NON-NLS-1$
+        } else if (language.equals("zh")) { //$NON-NLS-1$
+            country = "CN";                 //$NON-NLS-1$
+        } else if (language.equals("en")) { //$NON-NLS-1$
+            country = "US";                 //$NON-NLS-1$
+        } else if (language.equals("fa")) { //$NON-NLS-1$
+            country = "IR";                 //$NON-NLS-1$
+        }
+
+        if (country.isEmpty()) {
+            if (mCountryToLanguage == null) {
+                Locale[] locales = Locale.getAvailableLocales();
+                mCountryToLanguage = Maps.newHashMapWithExpectedSize(locales.length);
+                Map<String, Locale> localeMap = Maps.newHashMapWithExpectedSize(locales.length);
+                for (int i = 0; i < locales.length; i++) {
+                    Locale locale = locales[i];
+                    String localeLanguage = locale.getLanguage();
+                    String localeCountry = locale.getCountry();
+                    if (!localeCountry.isEmpty()) {
+                        localeCountry = localeCountry.toLowerCase(Locale.US);
+                        Locale old = localeMap.get(localeLanguage);
+                        if (old != null) {
+                            // For Italian for example it has both a locale with country = Italy
+                            // and one with country = Switzerland, so prefer the one where the
+                            // language code matches the country.
+                            if (!localeLanguage.equals(localeCountry)) {
+                                continue;
+                            }
+                        }
+                        mCountryToLanguage.put(localeLanguage, localeCountry);
+                        localeMap.put(localeLanguage, locale);
+                    }
+                }
+            }
+
+            country = mCountryToLanguage.get(language);
+        }
+
+        return country;
+    }
+
+    @NonNull
+    private Image getFlagImage(@NonNull ResourceQualifier[] qualifiers) {
+        Image image = null;
+        assert qualifiers.length == 2;
+        String language = ((LanguageQualifier) qualifiers[LOCALE_LANG]).getValue();
+        String region = ((RegionQualifier) qualifiers[LOCALE_REGION]).getValue();
+        if (LanguageQualifier.FAKE_LANG_VALUE.equals(language)) {
+            return IconFactory.getInstance().getIcon(ICON_GLOBE);
+        } else {
+            String country = getCountry(language, region);
+            if (country != null) {
+                image = FlagImagesRepository.getFlagImage(country, language);
+            }
+            if (image == null) {
+                image = FlagImagesRepository.getEmptyFlagImage();
+            }
+
+            return image;
+        }
+    }
+
+    private String getDeviceLabel(LayoutDevice device, boolean brief) {
+        String name = device.getName();
+
+        if (brief) {
+            // Produce a really brief summary of the device name, suitable for
+            // use in the narrow space available in the toolbar for example
+            int nexus = name.indexOf("Nexus"); //$NON-NLS-1$
+            if (nexus != -1) {
+                int begin = name.indexOf('(');
+                if (begin != -1) {
+                    begin++;
+                    int end = name.indexOf(')', begin);
+                    if (end != -1) {
+                        return name.substring(begin, end).trim();
+                    }
+                }
+            }
+        }
+
+        return name;
+    }
+
+    private void addDeviceMenuListener(final ToolItem combo) {
+        Listener menuListener = new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
+
+                LayoutDevice current = getSelectedDevice();
+                if (mDeviceList != null && mDeviceList.size() > 0) {
+                    for (final LayoutDevice device : mDeviceList) {
+                        String title = getDeviceLabel(device, false);
+                        MenuItem item = new MenuItem(menu, SWT.CHECK);
+                        item.setText(title);
+
+                        boolean selected = current == device;
+                        if (selected) {
+                            item.setSelection(true);
+                        }
+
+                        item.addSelectionListener(new SelectionAdapter() {
+                            @Override
+                            public void widgetSelected(SelectionEvent e) {
+                                selectDevice(device);
+                                onDeviceChange(true /*recomputeLayout*/);
+                            }
+                        });
+                    }
+
+                    @SuppressWarnings("unused")
+                    MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+
+                    MenuItem item = new MenuItem(menu, SWT.PUSH);
+                    item.setText("Add Custom...");
+                    item.addSelectionListener(new SelectionAdapter() {
+                        @Override
+                        public void widgetSelected(SelectionEvent e) {
+                            onCustomDeviceConfig();
+                        }
+                    });
+
+                }
+
+                // TODO - how do I dispose of this?
+
+                Rectangle bounds = combo.getBounds();
+                Point location = new Point(bounds.x, bounds.y + bounds.height);
+                location = combo.getParent().toDisplay(location);
+                menu.setLocation(location.x, location.y);
+                menu.setVisible(true);
+            }
+        };
+        combo.addListener(SWT.Selection, menuListener);
+    }
+
+    private void addTargetMenuListener(final ToolItem combo) {
+        Listener menuListener = new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
+                IAndroidTarget current = getSelectedTarget();
+
+                for (final IAndroidTarget target : mTargetList) {
+                    String title = getRenderingTargetLabel(target, false);
+                    MenuItem item = new MenuItem(menu, SWT.CHECK);
+                    item.setText(title);
+
+                    boolean selected = current == target;
+                    if (selected) {
+                        item.setSelection(true);
+                    }
+
+                    item.addSelectionListener(new SelectionAdapter() {
+                        @Override
+                        public void widgetSelected(SelectionEvent e) {
+                            selectTarget(target);
+                            onRenderingTargetChange();
+                        }
+                    });
+                }
+
+                Rectangle bounds = combo.getBounds();
+                Point location = new Point(bounds.x, bounds.y + bounds.height);
+                location = combo.getParent().toDisplay(location);
+                menu.setLocation(location.x, location.y);
+                menu.setVisible(true);
+            }
+        };
+        combo.addListener(SWT.Selection, menuListener);
+    }
+
+    private void addThemeListener(final ToolItem combo) {
+        Listener menuListener = new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                ThemeMenuAction.showThemeMenu(ConfigurationComposite.this, combo, mThemeList);
+            }
+        };
+        combo.addListener(SWT.Selection, menuListener);
+    }
+
+    private void addOrientationMenuListener(final ToolItem combo) {
+        Listener menuListener = new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                 if (event.detail == SWT.ARROW) {
+                     OrientationMenuAction.showMenu(ConfigurationComposite.this, combo);
+                 } else {
+                     flipOrientation();
+                 }
+            }
+        };
+        combo.addListener(SWT.Selection, menuListener);
+    }
+
+    /** Flip the current orientation to the next available device orientation, if any */
+    private void flipOrientation() {
+        DeviceConfig config = getSelectedDeviceConfig();
+        DeviceConfig flipped = getNextDeviceConfig(getSelectedDeviceConfig());
+        if (flipped != config) {
+            selectDeviceConfig(flipped);
+            onDeviceConfigChange();
+        }
+    }
+
+    /** Get the next cyclical orientation after the given orientation */
+    @Nullable
+    DeviceConfig getNextDeviceConfig(DeviceConfig config) {
+        LayoutDevice device = getSelectedDevice();
+        List<DeviceConfig> configs = device.getConfigs();
+        for (int i = 0; i < configs.size(); i++) {
+            if (configs.get(i) == config) {
+                return configs.get((i + 1) % configs.size());
+            }
+        }
+
+        return null;
+    }
+
+    protected String getThemeLabel(String theme, boolean brief) {
+        theme = ResourceHelper.styleToTheme(theme);
+
+        if (brief) {
+            int index = theme.lastIndexOf('.');
+            if (index < theme.length() - 1) {
+                return theme.substring(index + 1);
+            }
+        }
+        return theme;
+    }
+
+    private void addActivityMenuListener(final ToolItem combo) {
+        Listener menuListener = new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                // TODO: Allow using fragments here as well?
+                Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
+                ISharedImages sharedImages = JavaUI.getSharedImages();
+                String current = getSelectedActivity();
+
+                if (current != null) {
+                    MenuItem item = new MenuItem(menu, SWT.PUSH);
+                    String label = getActivityLabel(current, true);;
+                    item.setText( String.format("Open %1$s...", label));
+                    Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CUNIT);
+                    item.setImage(image);
+
+                    item.addSelectionListener(new SelectionAdapter() {
+                        @Override
+                        public void widgetSelected(SelectionEvent e) {
+                            String fqcn = getSelectedActivity();
+                            AdtPlugin.openJavaClass(mEditedFile.getProject(), fqcn);
+                        }
+                    });
+
+                    @SuppressWarnings("unused")
+                    MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+                }
+
+                IProject project = mEditedFile.getProject();
+                Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CLASS);
+
+                // Add activities found to be relevant to this layout
+                String layoutName = ResourceHelper.getLayoutName(mEditedFile);
+                String pkg = ManifestInfo.get(project).getPackage();
+                List<String> preferred = ManifestInfo.guessActivities(project, layoutName, pkg);
+                current = addActivities(menu, current, image, preferred);
+
+                // Add all activities
+                List<String> activities = ManifestInfo.getProjectActivities(project);
+                if (preferred.size() > 0) {
+                    // Filter out the activities we've already listed above
+                    List<String> filtered = new ArrayList<String>(activities.size());
+                    Set<String> remove = new HashSet<String>(preferred);
+                    for (String fqcn : activities) {
+                        if (!remove.contains(fqcn)) {
+                            filtered.add(fqcn);
+                        }
+                    }
+                    activities = filtered;
+                }
+
+                if (activities.size() > 0) {
+                    if (preferred.size() > 0) {
+                        @SuppressWarnings("unused")
+                        MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+                    }
+
+                    addActivities(menu, current, image, activities);
+                }
+
+                Rectangle bounds = combo.getBounds();
+                Point location = new Point(bounds.x, bounds.y + bounds.height);
+                location = combo.getParent().toDisplay(location);
+                menu.setLocation(location.x, location.y);
+                menu.setVisible(true);
+            }
+
+            private String addActivities(Menu menu, String current, Image image,
+                    List<String> activities) {
+                for (final String fqcn : activities) {
+                    String title = getActivityLabel(fqcn, false);
+                    MenuItem item = new MenuItem(menu, SWT.CHECK);
+                    item.setText(title);
+                    item.setImage(image);
+
+                    boolean selected = title.equals(current);
+                    if (selected) {
+                        item.setSelection(true);
+                        current = null; // Only show the first occurrence as selected
+                        // such that we don't show it selected again in the full activity list
+                    }
+
+                    item.addSelectionListener(new SelectionAdapter() {
+                        @Override
+                        public void widgetSelected(SelectionEvent e) {
+                            selectActivity(fqcn);
+                            onSelectActivity();
+                        }
+                    });
+                }
+
+                return current;
+            }
+        };
+        combo.addListener(SWT.Selection, menuListener);
+    }
+
+    private void addConfigurationMenuListener(final ToolItem combo) {
+        Listener menuListener = new Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                Menu menu = new Menu(ConfigurationComposite.this.getShell(), SWT.POP_UP);
+
+                // Compute the set of layout files defining this layout resource
+                String name = mEditedFile.getName();
+                IContainer resFolder = mEditedFile.getParent().getParent();
+                List<IFile> variations = new ArrayList<IFile>();
+                try {
+                    for (IResource resource : resFolder.members()) {
+                        if (resource.getName().startsWith(FD_RES_LAYOUT)
+                                && resource instanceof IContainer) {
+                            IContainer layoutFolder = (IContainer) resource;
+                            IResource variation = layoutFolder.findMember(name);
+                            if (variation instanceof IFile) {
+                                variations.add((IFile) variation);
+                            }
+                        }
+                    }
+                } catch (CoreException e1) {
+                    // TODO Auto-generated catch block
+                    e1.printStackTrace();
+                }
+
+                ResourceManager manager = ResourceManager.getInstance();
+                for (final IFile resource : variations) {
+                    MenuItem item = new MenuItem(menu, SWT.CHECK);
+
+                    IFolder parent = (IFolder) resource.getParent();
+                    ResourceFolder parentResource = manager.getResourceFolder(parent);
+                    FolderConfiguration configuration = parentResource.getConfiguration();
+                    String title = configuration.toDisplayString();
+                    item.setText(title);
+
+                    boolean selected = mEditedFile.equals(resource);
+                    if (selected) {
+                        item.setSelection(true);
+                    }
+
+                    item.addSelectionListener(new SelectionAdapter() {
+                        @Override
+                        public void widgetSelected(SelectionEvent e) {
+                            ConfigurationComposite.this.getDisplay().asyncExec(new Runnable() {
+                                @Override
+                                public void run() {
+                                    try {
+                                        AdtPlugin.openFile(resource, null, false);
+                                    } catch (PartInitException ex) {
+                                        AdtPlugin.log(ex, null);
+                                    }
+                                }
+                            });
+                        }
+                    });
+                }
+
+                if (!mEditedConfig.equals(mCurrentConfig)) {
+                    if (variations.size() > 0) {
+                        @SuppressWarnings("unused")
+                        MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+                    }
+
+                    // Add action for creating a new configuration
+                    MenuItem item = new MenuItem(menu, SWT.CHECK);
+                    item.setText("Create New...");
+                    item.setImage(IconFactory.getInstance().getIcon(ICON_NEW_CONFIG));
+                    //item.setToolTipText("Duplicate: Create new configuration for this layout");
+
+                    item.addSelectionListener(new SelectionAdapter() {
+                        @Override
+                        public void widgetSelected(SelectionEvent e) {
+                            if (mListener != null) {
+                                mListener.onCreate();
+                            }
+                        }
+                    });
+                }
+
+                Rectangle bounds = combo.getBounds();
+                Point location = new Point(bounds.x, bounds.y + bounds.height);
+                location = combo.getParent().toDisplay(location);
+                menu.setLocation(location.x, location.y);
+                menu.setVisible(true);
+            }
+        };
+        combo.addListener(SWT.Selection, menuListener);
+    }
+
     /**
      * Updates the locale combo.
      * This must be called from the UI thread.
@@ -1352,12 +2067,9 @@
         mDisableUpdates++;
 
         try {
-            // Reset the combo
-            mLocaleCombo.removeAll();
             mLocaleList.clear();
 
             SortedSet<String> languages = null;
-            boolean hasLocale = false;
 
             // get the languages from the project.
             ResourceRepository projectRes = mListener.getProjectResources();
@@ -1368,25 +2080,16 @@
                 languages = projectRes.getLanguages();
 
                 for (String language : languages) {
-                    hasLocale = true;
-
                     LanguageQualifier langQual = new LanguageQualifier(language);
 
                     // find the matching regions and add them
                     SortedSet<String> regions = projectRes.getRegions(language);
                     for (String region : regions) {
-                        mLocaleCombo.add(
-                                String.format("%1$s / %2$s", language, region));
                         RegionQualifier regionQual = new RegionQualifier(region);
                         mLocaleList.add(new ResourceQualifier[] { langQual, regionQual });
                     }
 
                     // now the entry for the other regions the language alone
-                    if (regions.size() > 0) {
-                        mLocaleCombo.add(String.format("%1$s / Other", language));
-                    } else {
-                        mLocaleCombo.add(String.format("%1$s / Any", language));
-                    }
                     // create a region qualifier that will never be matched by qualified resources.
                     mLocaleList.add(new ResourceQualifier[] {
                             langQual,
@@ -1395,14 +2098,6 @@
                 }
             }
 
-            // add a locale not present in the project resources. This will let the dev
-            // tests his/her default values.
-            if (hasLocale) {
-                mLocaleCombo.add("Other");
-            } else {
-                mLocaleCombo.add("Any locale");
-            }
-
             // create language/region qualifier that will never be matched by qualified resources.
             mLocaleList.add(new ResourceQualifier[] {
                     new LanguageQualifier(LanguageQualifier.FAKE_LANG_VALUE),
@@ -1415,10 +2110,9 @@
                 setLocaleCombo(mState.locale[LOCALE_LANG],
                         mState.locale[LOCALE_REGION]);
             } else {
-                mLocaleCombo.select(0);
+                //mLocaleCombo.select(0);
+                selectLocale(mLocaleList.get(0));
             }
-
-            mThemeCombo.getParent().layout();
         } finally {
             mDisableUpdates--;
         }
@@ -1469,112 +2163,18 @@
         mDisableUpdates++;
 
         try {
-            // Reset the combo
-            mThemeCombo.removeAll();
-            mIsProjectTheme.clear();
-
-            ArrayList<String> themes = new ArrayList<String>();
-            String includedIn = mListener.getIncludedWithin();
-
-            // First list any themes that are declared by the manifest
             if (mEditedFile != null) {
-                IProject project = mEditedFile.getProject();
-                ManifestInfo manifest = ManifestInfo.get(project);
-
-                // Look up the screen size for the current configuration
-                ScreenSize screenSize = null;
-                if (mState.device != null) {
-                    List<DeviceConfig> configs = mState.device.getConfigs();
-                    for (DeviceConfig config : configs) {
-                        ScreenSizeQualifier qualifier =
-                            config.getConfig().getScreenSizeQualifier();
-                        screenSize = qualifier.getValue();
-                        break;
-                    }
+                if (mState.theme == null || mState.theme.isEmpty()
+                        || mListener.getIncludedWithin() != null) {
+                    mState.theme = null;
+                    getPreferredTheme();
                 }
-                // Look up the default/fallback theme to use for this project (which
-                // depends on the screen size when no particular theme is specified
-                // in the manifest)
-                String defaultTheme = manifest.getDefaultTheme(mState.target, screenSize);
-
-                Map<String, String> activityThemes = manifest.getActivityThemes();
-                String pkg = manifest.getPackage();
-                String preferred = null;
-                boolean isIncluded = includedIn != null;
-                if (mState.theme == null || isIncluded) {
-                    String layoutName = ResourceHelper.getLayoutName(mEditedFile);
-
-                    // If we are rendering a layout in included context, pick the theme
-                    // from the outer layout instead
-                    if (includedIn != null) {
-                        layoutName = includedIn;
-                    }
-
-                    String activity = ManifestInfo.guessActivity(project, layoutName, pkg);
-                    if (activity != null) {
-                        preferred = activityThemes.get(activity);
-                    }
-                    if (preferred == null) {
-                        preferred = defaultTheme;
-                    }
-                    String preferredTheme = ResourceHelper.styleToTheme(preferred);
-                    if (includedIn == null) {
-                        mState.theme = preferredTheme;
-                    }
-                    boolean isProjectTheme = !preferred.startsWith(PREFIX_ANDROID_STYLE);
-                    mThemeCombo.add(preferredTheme);
-                    mIsProjectTheme.add(Boolean.valueOf(isProjectTheme));
-
-                    mThemeCombo.add(THEME_SEPARATOR);
-                    mIsProjectTheme.add(Boolean.FALSE);
-                }
-
-                // Create a sorted list of unique themes referenced in the manifest
-                // (sort alphabetically, but place the preferred theme at the
-                // top of the list)
-                Set<String> themeSet = new HashSet<String>(activityThemes.values());
-                themeSet.add(defaultTheme);
-                List<String> themeList = new ArrayList<String>(themeSet);
-                final String first = preferred;
-                Collections.sort(themeList, new Comparator<String>() {
-                    @Override
-                    public int compare(String s1, String s2) {
-                        if (s1 == first) {
-                            return -1;
-                        } else if (s1 == first) {
-                            return 1;
-                        } else {
-                            return s1.compareTo(s2);
-                        }
-                    }
-                });
-
-                if (themeList.size() > 1 ||
-                        (themeList.size() == 1 && (preferred == null ||
-                                !preferred.equals(themeList.get(0))))) {
-                    for (String style : themeList) {
-                        String theme = ResourceHelper.styleToTheme(style);
-
-                        // Initialize the chosen theme to the first item
-                        // in the used theme list (that's what would be chosen
-                        // anyway) such that we stop attempting to look up
-                        // the associated activity (during initialization,
-                        // this method can be called repeatedly.)
-                        if (mState.theme == null) {
-                            mState.theme = theme;
-                        }
-
-                        boolean isProjectTheme = !style.startsWith(PREFIX_ANDROID_STYLE);
-                        mThemeCombo.add(theme);
-                        mIsProjectTheme.add(Boolean.valueOf(isProjectTheme));
-                    }
-                    mThemeCombo.add(THEME_SEPARATOR);
-                    mIsProjectTheme.add(Boolean.FALSE);
-                }
+                assert mState.theme != null;
             }
 
-            // now get the themes and languages from the project.
-            int projectThemeCount = 0;
+            mThemeList.clear();
+
+            ArrayList<String> themes = new ArrayList<String>();
             ResourceRepository projectRes = mListener.getProjectResources();
             // in cases where the opened file is not linked to a project, this could be null.
             if (projectRes != null) {
@@ -1592,19 +2192,21 @@
                         // directly or indirectly a platform theme.
                         for (ResourceValue value : styleMap.values()) {
                             if (isTheme(value, styleMap, null)) {
-                                themes.add(value.getName());
+                                String theme = value.getName();
+                                themes.add(theme);
                             }
                         }
 
                         Collections.sort(themes);
 
                         for (String theme : themes) {
-                            mThemeCombo.add(theme);
-                            mIsProjectTheme.add(Boolean.TRUE);
+                            if (!theme.startsWith(PREFIX_RESOURCE_REF)) {
+                                theme = PREFIX_STYLE + theme;
+                            }
+                            mThemeList.add(theme);
                         }
                     }
                 }
-                projectThemeCount = themes.size();
                 themes.clear();
             }
 
@@ -1618,11 +2220,10 @@
                     // get the styles.
                     Map<String, ResourceValue> styles = frameworResources.get(ResourceType.STYLE);
 
-
                     // collect the themes out of all the styles.
                     for (ResourceValue value : styles.values()) {
                         String name = value.getName();
-                        if (name.startsWith("Theme.") || name.equals("Theme")) {
+                        if (name.startsWith("Theme.") || name.equals("Theme")) { //$NON-NLS-1$ //$NON-NLS-2$
                             themes.add(value.getName());
                         }
                     }
@@ -1630,50 +2231,102 @@
                     // sort them and add them to the combo
                     Collections.sort(themes);
 
-                    if (projectThemeCount > 0 && themes.size() > 0) {
-                        mThemeCombo.add(THEME_SEPARATOR);
-                        mIsProjectTheme.add(Boolean.FALSE);
-                    }
-
                     for (String theme : themes) {
-                        mThemeCombo.add(theme);
-                        mIsProjectTheme.add(Boolean.FALSE);
+                        if (!theme.startsWith(PREFIX_RESOURCE_REF)) {
+                            theme = PREFIX_ANDROID_STYLE + theme;
+                        }
+                        mThemeList.add(theme);
                     }
 
                     themes.clear();
                 }
             }
 
-            // try to reselect the previous theme.
-            boolean needDefaultSelection = true;
-
-            if (mState.theme != null && includedIn == null) {
-                final int count = mThemeCombo.getItemCount();
-                for (int i = 0 ; i < count ; i++) {
-                    if (mState.theme.equals(mThemeCombo.getItem(i))) {
-                        mThemeCombo.select(i);
-                        needDefaultSelection = false;
-                        mThemeCombo.setEnabled(true);
+            // Migration: In the past we didn't store the style prefix in the settings;
+            // this meant we might lose track of whether the theme is a project style
+            // or a framework style. For now we need to migrate. Search through the
+            // theme list until we have a match
+            if (!mState.theme.startsWith(PREFIX_RESOURCE_REF)) {
+                String projectStyle = PREFIX_STYLE + mState.theme;
+                String frameworkStyle = PREFIX_ANDROID_STYLE + mState.theme;
+                for (String theme : mThemeList) {
+                    if (theme.equals(projectStyle)) {
+                        mState.theme = projectStyle;
+                        break;
+                    } else if (theme.equals(frameworkStyle)) {
+                        mState.theme = frameworkStyle;
                         break;
                     }
                 }
             }
 
-            if (needDefaultSelection) {
-                if (mThemeCombo.getItemCount() > 0) {
-                    mThemeCombo.select(0);
-                    mThemeCombo.setEnabled(true);
-                } else {
-                    mThemeCombo.setEnabled(false);
-                }
-            }
+            // TODO: Handle the case where you have a theme persisted that isn't available??
+            // We could look up mState.theme and make sure it appears in the list! And if not,
+            // picking one.
 
-            mThemeCombo.getParent().layout();
+            selectTheme(mState.theme);
         } finally {
             mDisableUpdates--;
         }
+    }
 
-        assert mIsProjectTheme.size() == mThemeCombo.getItemCount();
+    /** Returns the preferred theme, or null */
+    @Nullable
+    String getPreferredTheme() {
+        if (mListener == null) {
+            return null;
+        }
+        String includedIn = mListener.getIncludedWithin();
+        IProject project = mEditedFile.getProject();
+        ManifestInfo manifest = ManifestInfo.get(project);
+
+        // Look up the screen size for the current configuration
+        ScreenSize screenSize = null;
+        if (mState.device != null) {
+            List<DeviceConfig> configs = mState.device.getConfigs();
+            for (DeviceConfig config : configs) {
+                ScreenSizeQualifier qualifier =
+                    config.getConfig().getScreenSizeQualifier();
+                screenSize = qualifier.getValue();
+                break;
+            }
+        }
+
+        // Look up the default/fallback theme to use for this project (which
+        // depends on the screen size when no particular theme is specified
+        // in the manifest)
+        String defaultTheme = manifest.getDefaultTheme(mState.target, screenSize);
+
+        String preferred = defaultTheme;
+        boolean isIncluded = includedIn != null;
+        if (mState.theme == null || isIncluded) {
+            // If we are rendering a layout in included context, pick the theme
+            // from the outer layout instead
+
+            // TODO: Pick a different activity when we're configuring an
+            // included layout??
+            //if (includedIn != null) {
+            //    layoutName = includedIn;
+            //}
+
+            String activity = getSelectedActivity();
+            if (activity != null) {
+                Map<String, String> activityThemes = manifest.getActivityThemes();
+                preferred = activityThemes.get(activity);
+            }
+            if (preferred == null) {
+                preferred = defaultTheme;
+            }
+            if (includedIn == null) {
+                mState.theme = preferred;
+            }
+        }
+
+        if (mState.theme == null && includedIn == null) {
+            mState.theme = preferred;
+        }
+
+        return preferred;
     }
 
     // ---- getters for the config selection values ----
@@ -1774,13 +2427,19 @@
      *
      * @return the theme name, or null
      */
-    public String getTheme() {
-        int themeIndex = mThemeCombo.getSelectionIndex();
-        if (themeIndex != -1) {
-            return mThemeCombo.getItem(themeIndex);
+    @Nullable
+    public String getThemeName() {
+        String theme = getSelectedTheme();
+        if (theme != null) {
+            theme = ResourceHelper.styleToTheme(theme);
         }
 
-        return null;
+        return theme;
+    }
+
+    @Nullable
+    String getSelectedTheme() {
+        return (String) mThemeCombo.getData();
     }
 
     /**
@@ -1789,9 +2448,9 @@
      * @return the device name, or null
      */
     public String getDevice() {
-        int deviceIndex = mDeviceCombo.getSelectionIndex();
-        if (deviceIndex != -1) {
-            return mDeviceCombo.getItem(deviceIndex);
+        LayoutDevice device = getSelectedDevice();
+        if (device != null) {
+            return device.getName();
         }
 
         return null;
@@ -1799,62 +2458,63 @@
 
     /**
      * Returns whether the current theme selection is a project theme.
-     * <p/>The returned value is meaningless if {@link #getTheme()} returns <code>null</code>.
+     * <p/>The returned value is meaningless if {@link #getThemeName()} returns <code>null</code>.
      * @return true for project theme, false for framework theme
      */
     public boolean isProjectTheme() {
-        return mIsProjectTheme.get(mThemeCombo.getSelectionIndex()).booleanValue();
-    }
+        String theme = getSelectedTheme();
+        if (theme != null) {
+            assert theme.startsWith(PREFIX_STYLE) || theme.startsWith(PREFIX_ANDROID_STYLE);
 
-    public IAndroidTarget getRenderingTarget() {
-        int index = mTargetCombo.getSelectionIndex();
-        if (index >= 0) {
-            return mTargetList.get(index);
+            return ResourceHelper.isProjectStyle(theme);
         }
 
-        return null;
+        return false;
+    }
+
+    @Nullable
+    public IAndroidTarget getRenderingTarget() {
+        return getSelectedTarget();
     }
 
     /**
      * Loads the list of {@link IAndroidTarget} and inits the UI with it.
      */
     private void initTargets() {
-        mTargetCombo.removeAll();
         mTargetList.clear();
 
         Sdk currentSdk = Sdk.getCurrent();
         if (currentSdk != null) {
             IAndroidTarget[] targets = currentSdk.getTargets();
-            int match = -1;
+            IAndroidTarget match = null;
             for (int i = 0 ; i < targets.length; i++) {
                 // FIXME: add check based on project minSdkVersion
                 if (targets[i].hasRenderingLibrary()) {
-                    mTargetCombo.add(targets[i].getShortClasspathName());
                     mTargetList.add(targets[i]);
 
                     if (mRenderingTarget != null) {
                         // use equals because the rendering could be from a previous SDK, so
                         // it may not be the same instance.
                         if (mRenderingTarget.equals(targets[i])) {
-                            match = mTargetList.indexOf(targets[i]);
+                            match = targets[i];
                         }
                     } else if (mProjectTarget == targets[i]) {
-                        match = mTargetList.indexOf(targets[i]);
+                        match = targets[i];
                     }
                 }
             }
 
             mTargetCombo.setEnabled(mTargetList.size() > 1);
-            if (match == -1) {
-                mTargetCombo.deselectAll();
+            if (match == null) {
+                selectTarget(null);
 
                 // the rendering target is the same as the project.
                 mRenderingTarget = mProjectTarget;
             } else {
-                mTargetCombo.select(match);
+                selectTarget(match);
 
                 // set the rendering target to the new object.
-                mRenderingTarget = mTargetList.get(match);
+                mRenderingTarget = match;
             }
         }
     }
@@ -1872,31 +2532,68 @@
         }
 
 
-        // remove older devices if applicable
-        mDeviceCombo.removeAll();
-        mDeviceConfigCombo.removeAll();
-
         // fill with the devices
-        if (mDeviceList != null) {
-            for (LayoutDevice device : mDeviceList) {
-                mDeviceCombo.add(device.getName());
-            }
-            mDeviceCombo.select(0);
+        if (mDeviceList != null && mDeviceList.size() > 0) {
+            LayoutDevice first = mDeviceList.get(0);
+            selectDevice(first);
+            List<DeviceConfig> configs = first.getConfigs();
+            selectDeviceConfig(configs.get(0));
+        } else {
+            selectDevice(null);
+        }
+    }
 
-            if (mDeviceList.size() > 0) {
-                List<DeviceConfig> configs = mDeviceList.get(0).getConfigs();
-                for (DeviceConfig config : configs) {
-                    mDeviceConfigCombo.add(config.getName());
-                }
-                mDeviceConfigCombo.select(0);
-                if (configs.size() == 1) {
-                    mDeviceConfigCombo.setEnabled(false);
-                }
-            }
+    Image getOrientationIcon(ScreenOrientation orientation, boolean flip) {
+        IconFactory icons = IconFactory.getInstance();
+        switch (orientation) {
+            case LANDSCAPE:
+                return icons.getIcon(flip ? ICON_LANDSCAPE_FLIP : ICON_LANDSCAPE);
+            case SQUARE:
+                return icons.getIcon(ICON_SQUARE);
+            case PORTRAIT:
+            default:
+                return icons.getIcon(flip ? ICON_PORTRAIT_FLIP : ICON_PORTRAIT);
+        }
+    }
+
+    ImageDescriptor getOrientationImage(ScreenOrientation orientation, boolean flip) {
+        IconFactory icons = IconFactory.getInstance();
+        switch (orientation) {
+            case LANDSCAPE:
+                return icons.getImageDescriptor(flip ? ICON_LANDSCAPE_FLIP : ICON_LANDSCAPE);
+            case SQUARE:
+                return icons.getImageDescriptor(ICON_SQUARE);
+            case PORTRAIT:
+            default:
+                return icons.getImageDescriptor(flip ? ICON_PORTRAIT_FLIP : ICON_PORTRAIT);
+        }
+    }
+
+    @NonNull
+    ScreenOrientation getOrientation(DeviceConfig config) {
+        ScreenOrientationQualifier qualifier = config.getConfig().getScreenOrientationQualifier();
+        ScreenOrientation orientation = null;
+        if (qualifier != null) {
+            orientation = qualifier.getValue();
         }
 
-        // add the custom item
-        mDeviceCombo.add("Custom...");
+        if (orientation == null) {
+            orientation = ScreenOrientation.PORTRAIT;
+        }
+
+        return orientation;
+    }
+
+    void selectDeviceConfig(@Nullable DeviceConfig config) {
+        mOrientationCombo.setData(config);
+
+        DeviceConfig nextConfig = getNextDeviceConfig(config);
+        mOrientationCombo.setImage(getOrientationIcon(getOrientation(nextConfig),
+                nextConfig != config));
+    }
+
+    DeviceConfig getSelectedDeviceConfig() {
+        return (DeviceConfig) mOrientationCombo.getData();
     }
 
     /**
@@ -1904,32 +2601,33 @@
      * @param device the device to select
      * @return true if the device was found.
      */
-    private boolean selectDevice(LayoutDevice device) {
-        final int count = mDeviceList.size();
-        for (int i = 0 ; i < count ; i++) {
-            // since device comes from mDeviceList, we can use the == operator.
-            if (device == mDeviceList.get(i)) {
-                mDeviceCombo.select(i);
-                return true;
-            }
+    private boolean selectDevice(@Nullable LayoutDevice device) {
+        mDeviceCombo.setData(device);
+        if (device != null) {
+            mDeviceCombo.setText(getDeviceLabel(device, true));
+        } else {
+            mDeviceCombo.setText("Device");
         }
+        resizeToolBar();
 
         return false;
     }
 
+    LayoutDevice getSelectedDevice() {
+        return (LayoutDevice) mDeviceCombo.getData();
+    }
+
     /**
      * Selects a config by name.
      * @param name the name of the config to select.
      */
     private void selectConfig(String name) {
-        final int count = mDeviceConfigCombo.getItemCount();
-        for (int i = 0 ; i < count ; i++) {
-            String item = mDeviceConfigCombo.getItem(i);
-            if (name.equals(item)) {
-                mDeviceConfigCombo.select(i);
-                return;
-            }
+        LayoutDevice device = getSelectedDevice();
+        DeviceConfig config = null;
+        if (device != null) {
+            config = device.getDeviceConfigByName(name);
         }
+        selectDeviceConfig(config);
     }
 
     /**
@@ -1945,33 +2643,17 @@
 
         String newConfigName = null;
 
-        int deviceIndex = mDeviceCombo.getSelectionIndex();
-        if (deviceIndex != -1) {
-            // check if the user is asking for the custom item
-            if (deviceIndex == mDeviceCombo.getItemCount() - 1) {
-                onCustomDeviceConfig();
-                return;
-            }
-
+        DeviceConfig prevConfig = getSelectedDeviceConfig();
+        LayoutDevice device = getSelectedDevice();
+        if (mState.device != null && prevConfig != null && device != null) {
             // get the previous config, so that we can look for a close match
-            if (mState.device != null) {
-                int index = mDeviceConfigCombo.getSelectionIndex();
-                if (index != -1) {
-                    FolderConfiguration oldConfig = mState.device.getFolderConfigByName(
-                            mDeviceConfigCombo.getItem(index));
-
-                    LayoutDevice newDevice = mDeviceList.get(deviceIndex);
-
-                    newConfigName = getClosestMatch(oldConfig, newDevice.getConfigs());
-                }
-            }
-
-            mState.device = mDeviceList.get(deviceIndex);
-        } else {
-            mState.device = null;
+            FolderConfiguration oldConfig = mState.device.getFolderConfigByName(
+                    prevConfig.getName());
+            newConfigName = getClosestMatch(oldConfig, device.getConfigs());
         }
+        mState.device = device;
 
-        fillConfigCombo(newConfigName);
+        selectConfig(newConfigName);
 
         computeCurrentConfig();
 
@@ -2086,34 +2768,9 @@
     }
 
     /**
-     * fills the config combo with new values based on {@link #mState}.device.
-     * @param refName an optional name. if set the selection will match this name (if found)
-     */
-    private void fillConfigCombo(String refName) {
-        mDeviceConfigCombo.removeAll();
-
-        if (mState.device != null) {
-            int selectionIndex = 0;
-            int i = 0;
-
-            for (DeviceConfig config : mState.device.getConfigs()) {
-                mDeviceConfigCombo.add(config.getName());
-
-                if (config.getName().equals(refName)) {
-                    selectionIndex = i;
-                }
-                i++;
-            }
-
-            mDeviceConfigCombo.select(selectionIndex);
-            mDeviceConfigCombo.setEnabled(mState.device.getConfigs().size() > 1);
-        }
-    }
-
-    /**
      * Called when the device config selection changes.
      */
-    private void onDeviceConfigChange() {
+    void onDeviceConfigChange() {
         // because changing the content of a combo triggers a change event, respect the
         // mDisableUpdates flag
         if (mDisableUpdates > 0) {
@@ -2144,18 +2801,6 @@
         saveRenderState();
     }
 
-    private void onDockChange() {
-        if (computeCurrentConfig() &&  mListener != null) {
-            mListener.onConfigurationChange();
-        }
-    }
-
-    private void onDayChange() {
-        if (computeCurrentConfig() &&  mListener != null) {
-            mListener.onConfigurationChange();
-        }
-    }
-
     /**
      * Call back for api level combo selection
      */
@@ -2172,8 +2817,7 @@
             mListener.onRenderingTargetPreChange(mRenderingTarget);
         }
 
-        int index = mTargetCombo.getSelectionIndex();
-        mRenderingTarget = mTargetList.get(index);
+        mRenderingTarget = getRenderingTarget();
 
         boolean computeOk = computeCurrentConfig();
 
@@ -2208,74 +2852,43 @@
 
         if (mState.device != null) {
             // get the device config from the device/config combos.
-            int configIndex = mDeviceConfigCombo.getSelectionIndex();
-            String name = mDeviceConfigCombo.getItem(configIndex);
-            FolderConfiguration config = mState.device.getFolderConfigByName(name);
+            FolderConfiguration config = mState.device.getFolderConfigByName(
+                    getSelectedDeviceConfig().getName());
 
             // replace the config with the one from the device
             mCurrentConfig.set(config);
 
             // replace the locale qualifiers with the one coming from the locale combo
-            int index = mLocaleCombo.getSelectionIndex();
-            if (index != -1) {
-                ResourceQualifier[] localeQualifiers = mLocaleList.get(index);
-
+            ResourceQualifier[] localeQualifiers = getSelectedLocale();
+            if (localeQualifiers != null) {
                 mCurrentConfig.setLanguageQualifier(
                         (LanguageQualifier)localeQualifiers[LOCALE_LANG]);
                 mCurrentConfig.setRegionQualifier(
                         (RegionQualifier)localeQualifiers[LOCALE_REGION]);
             }
 
-            index = mUiModeCombo.getSelectionIndex();
-            if (index == -1) {
-                index = 0; // no selection = 0
-            }
-            mCurrentConfig.setUiModeQualifier(new UiModeQualifier(UiMode.getByIndex(index)));
-
-            index = mNightCombo.getSelectionIndex();
-            if (index == -1) {
-                index = 0; // no selection = 0
-            }
-            mCurrentConfig.setNightModeQualifier(
-                    new NightModeQualifier(NightMode.getByIndex(index)));
-
             // replace the API level by the selection of the combo
-            index = mTargetCombo.getSelectionIndex();
-            if (index == -1) {
-                index = mTargetList.indexOf(mProjectTarget);
+            IAndroidTarget target = getRenderingTarget();
+            if (target == null) {
+                target = mProjectTarget;
             }
-            if (index != -1) {
-                IAndroidTarget target = mTargetList.get(index);
-
-                if (target != null) {
-                    mCurrentConfig.setVersionQualifier(
-                            new VersionQualifier(target.getVersion().getApiLevel()));
-                }
+            if (target != null) {
+                mCurrentConfig.setVersionQualifier(
+                        new VersionQualifier(target.getVersion().getApiLevel()));
             }
 
-            // update the create button.
-            checkCreateEnable();
-
             return true;
         }
 
         return false;
     }
 
-    private void onThemeChange() {
+    void onThemeChange() {
         saveState();
 
-        int themeIndex = mThemeCombo.getSelectionIndex();
-        if (themeIndex != -1) {
-            String theme = mThemeCombo.getItem(themeIndex);
-
-            if (theme.equals(THEME_SEPARATOR)) {
-                mThemeCombo.select(0);
-            }
-
-            if (mListener != null) {
-                mListener.onThemeChange();
-            }
+        String theme = getSelectedTheme();
+        if (theme != null && mListener != null) {
+            mListener.onThemeChange();
         }
     }
 
@@ -2349,10 +2962,6 @@
         return false;
     }
 
-    private void checkCreateEnable() {
-        mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
-    }
-
     /**
      * Checks whether the current edited file is the best match for a given config.
      * <p/>
@@ -2434,9 +3043,8 @@
         // Sync render target
         IAndroidTarget target = pair.getSecond();
         if (target != null) {
-            int targetIndex = mTargetList.indexOf(target);
-            if (targetIndex != mTargetCombo.getSelectionIndex()) {
-                mTargetCombo.select(targetIndex);
+            if (getRenderingTarget() != target) {
+                selectTarget(target);
                 renderTargetChanged = true;
             }
         }
@@ -2453,8 +3061,7 @@
             if (mListener != null && mRenderingTarget != null) {
                 mListener.onRenderingTargetPreChange(mRenderingTarget);
             }
-            int targetIndex = mTargetCombo.getSelectionIndex();
-            mRenderingTarget = mTargetList.get(targetIndex);
+            mRenderingTarget = target;
         }
 
         // Compute the new configuration; we want to do this both for locale changes
@@ -2556,10 +3163,8 @@
     private void saveRenderState() {
         IProject project = mEditedFile.getProject();
         try {
-            int index = mLocaleCombo.getSelectionIndex();
-            ResourceQualifier[] locale = mLocaleList.get(index);
-            index = mTargetCombo.getSelectionIndex();
-            IAndroidTarget target = mTargetList.get(index);
+            ResourceQualifier[] locale = getSelectedLocale();
+            IAndroidTarget target = getRenderingTarget();
 
             // Generate a persistent string from locale+target
             StringBuilder sb = new StringBuilder();
@@ -2583,4 +3188,49 @@
             AdtPlugin.log(e, null);
         }
     }
+
+    // ---- Implements SelectionListener ----
+
+    @Override
+    public void widgetSelected(SelectionEvent e) {
+        if (mDisableUpdates > 0) {
+            return;
+        }
+
+        final Object source = e.getSource();
+        if (source == mOrientationCombo) {
+            flipOrientation();
+        }
+    }
+
+    @Override
+    public void widgetDefaultSelected(SelectionEvent e) {
+    }
+
+    /** Returns the file whose rendering is being configured by this configuration composite */
+    IFile getEditedFile() {
+        return mEditedFile;
+    }
+
+    UiMode getSelectedUiMode() {
+        return mState.uiMode;
+    }
+
+    NightMode getSelectedNightMode() {
+        return mState.night;
+    }
+
+    void selectNightMode(NightMode night) {
+        mState.night = night;
+        if (computeCurrentConfig() && mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    void selectUiMode(UiMode uiMode) {
+        mState.uiMode = uiMode;
+        if (computeCurrentConfig() && mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
 }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java
new file mode 100644
index 0000000..c2f32c5
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/OrientationMenuAction.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SubmenuAction;
+import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice;
+import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig;
+import com.android.resources.NightMode;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.UiMode;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.ToolItem;
+
+import java.util.List;
+
+/**
+ * Action which creates a submenu that shows the available orientations as well
+ * as some related options for night mode and dock mode
+ */
+class OrientationMenuAction extends SubmenuAction {
+    // Constants used to indicate what type of menu is being shown, such that
+    // the submenus can lazily construct their contents
+    private static final int MENU_NIGHTMODE = 1;
+    private static final int MENU_UIMODE = 2;
+
+    private final ConfigurationComposite mConfiguration;
+    /** Type of menu; one of the constants {@link #MENU_NIGHTMODE} etc */
+    private final int mType;
+
+    OrientationMenuAction(int type, String title, ConfigurationComposite configuration) {
+        super(title);
+        mType = type;
+        mConfiguration = configuration;
+    }
+
+    static void showMenu(ConfigurationComposite configuration, ToolItem combo) {
+        MenuManager manager = new MenuManager();
+
+        // Show toggles for all the available configurations
+        DeviceConfig current = configuration.getSelectedDeviceConfig();
+        LayoutDevice device = configuration.getSelectedDevice();
+        if (device != null) {
+            List<DeviceConfig> configs = device.getConfigs();
+
+            if (configs.size() > 1 && current != null) {
+                DeviceConfig flip = configuration.getNextDeviceConfig(current);
+                manager.add(new DeviceConfigAction(configuration,
+                        String.format("Switch to %1$s", flip.getName()), flip, false, true));
+                manager.add(new Separator());
+            }
+
+            for (DeviceConfig config : configs) {
+                manager.add(new DeviceConfigAction(configuration, config.getName(),
+                        config, config == current, false));
+            }
+            manager.add(new Separator());
+        }
+        manager.add(new OrientationMenuAction(MENU_UIMODE, "UI Mode", configuration));
+        manager.add(new Separator());
+        manager.add(new OrientationMenuAction(MENU_NIGHTMODE, "Night Mode", configuration));
+
+        Menu menu = manager.createContextMenu(configuration.getShell());
+        Rectangle bounds = combo.getBounds();
+        Point location = new Point(bounds.x, bounds.y + bounds.height);
+        location = combo.getParent().toDisplay(location);
+        menu.setLocation(location.x, location.y);
+        menu.setVisible(true);
+    }
+
+    @Override
+    protected void addMenuItems(Menu menu) {
+        switch (mType) {
+            case MENU_NIGHTMODE: {
+                NightMode selected = mConfiguration.getSelectedNightMode();
+                for (NightMode mode : NightMode.values()) {
+                    boolean checked = mode == selected;
+                    SelectNightModeAction action = new SelectNightModeAction(mode, checked);
+                    new ActionContributionItem(action).fill(menu, -1);
+
+                }
+                break;
+            }
+            case MENU_UIMODE: {
+                UiMode selected = mConfiguration.getSelectedUiMode();
+                for (UiMode mode : UiMode.values()) {
+                    boolean checked = mode == selected;
+                    SelectUiModeAction action = new SelectUiModeAction(mode, checked);
+                    new ActionContributionItem(action).fill(menu, -1);
+                }
+                break;
+            }
+        }
+    }
+
+    private class SelectNightModeAction extends Action {
+        private final NightMode mMode;
+
+        private SelectNightModeAction(NightMode mode, boolean checked) {
+            super(mode.getLongDisplayValue(), IAction.AS_RADIO_BUTTON);
+            mMode = mode;
+            if (checked) {
+                setChecked(true);
+            }
+        }
+
+        @Override
+        public void run() {
+            mConfiguration.selectNightMode(mMode);
+        }
+    }
+
+    private class SelectUiModeAction extends Action {
+        private final UiMode mMode;
+
+        private SelectUiModeAction(UiMode mode, boolean checked) {
+            super(mode.getLongDisplayValue(), IAction.AS_RADIO_BUTTON);
+            mMode = mode;
+            if (checked) {
+                setChecked(true);
+            }
+        }
+
+        @Override
+        public void run() {
+            mConfiguration.selectUiMode(mMode);
+        }
+    }
+
+    private static class DeviceConfigAction extends Action {
+        private final ConfigurationComposite mConfiguration;
+        private final DeviceConfig mConfig;
+
+        private DeviceConfigAction(ConfigurationComposite configuration, String title,
+                DeviceConfig config, boolean checked, boolean flip) {
+            super(title, IAction.AS_RADIO_BUTTON);
+            mConfiguration = configuration;
+            mConfig = config;
+            if (checked) {
+                setChecked(true);
+            }
+            ScreenOrientation orientation = configuration.getOrientation(config);
+            setImageDescriptor(configuration.getOrientationImage(orientation, flip));
+        }
+
+        @Override
+        public void run() {
+            mConfiguration.selectDeviceConfig(mConfig);
+            mConfiguration.onDeviceConfigChange();
+        }
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java
new file mode 100644
index 0000000..b8ba48e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/SelectThemeAction.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+
+/**
+ * Action which brings up the "Create new XML File" wizard, pre-selected with the
+ * animation category
+ */
+class SelectThemeAction extends Action {
+    private final ConfigurationComposite mConfiguration;
+    private final String mTheme;
+
+    public SelectThemeAction(ConfigurationComposite configuration, String title, String theme,
+            boolean selected) {
+        super(title, IAction.AS_RADIO_BUTTON);
+        mConfiguration = configuration;
+        mTheme = theme;
+        if (selected) {
+            setChecked(selected);
+        }
+    }
+
+    @Override
+    public void run() {
+        mConfiguration.selectTheme(mTheme);
+        mConfiguration.onThemeChange();
+    }
+}
\ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeChooser.java
new file mode 100644
index 0000000..262d317
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeChooser.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog;
+
+/**
+ * A dialog to let the user select a theme
+ */
+public class ThemeChooser extends AbstractElementListSelectionDialog {
+    /** The return code from the dialog for the user choosing "Clear" */
+    public static final int CLEAR_RETURN_CODE = -5;
+    /** The dialog button ID for the user choosing "Clear" */
+    private static final int CLEAR_BUTTON_ID = CLEAR_RETURN_CODE;
+
+    private String mCurrentResource;
+    private String[] mThemes;
+
+    private ThemeChooser(String[] themes, Shell parent) {
+        super(parent, new LabelProvider());
+        mThemes = themes;
+
+        setTitle("Theme Chooser");
+        setMessage(String.format("Choose a theme"));
+    }
+
+    @Override
+    protected void createButtonsForButtonBar(Composite parent) {
+        createButton(parent, CLEAR_BUTTON_ID, "Clear", false /*defaultButton*/);
+        super.createButtonsForButtonBar(parent);
+    }
+
+    @Override
+    protected void buttonPressed(int buttonId) {
+        super.buttonPressed(buttonId);
+
+        if (buttonId == CLEAR_BUTTON_ID) {
+            assert CLEAR_RETURN_CODE != Window.OK && CLEAR_RETURN_CODE != Window.CANCEL;
+            setReturnCode(CLEAR_RETURN_CODE);
+            close();
+        }
+    }
+
+    private void setCurrentResource(String resource) {
+        mCurrentResource = resource;
+    }
+
+    private String getCurrentResource() {
+        return mCurrentResource;
+    }
+
+    @Override
+    protected void computeResult() {
+        computeResultFromSelection();
+    }
+
+    private void computeResultFromSelection() {
+        if (getSelectionIndex() == -1) {
+            mCurrentResource = null;
+            return;
+        }
+
+        Object[] elements = getSelectedElements();
+        if (elements.length == 1 && elements[0] instanceof String) {
+            String item = (String) elements[0];
+
+            mCurrentResource = item;
+        }
+    }
+
+    @Override
+    protected Control createDialogArea(Composite parent) {
+        Composite top = (Composite)super.createDialogArea(parent);
+
+        createMessageArea(top);
+
+        createFilterText(top);
+        createFilteredList(top);
+
+        setupResourceList();
+        selectResourceString(mCurrentResource);
+
+        return top;
+    }
+
+    /**
+     * Setups the current list.
+     */
+    private String[] setupResourceList() {
+        setListElements(mThemes);
+        fFilteredList.setEnabled(mThemes.length > 0);
+
+        return mThemes;
+    }
+
+    /**
+     * Select an item by its name, if possible.
+     */
+    private void selectItemName(String itemName, String[] items) {
+        if (itemName == null || items == null) {
+            return;
+        }
+        setSelection(new String[] { itemName });
+    }
+
+    /**
+     * Select an item by its full resource string.
+     * This also selects between project and system repository based on the resource string.
+     */
+    private void selectResourceString(String item) {
+        String itemName = item;
+
+        // Update the list
+        String[] items = setupResourceList();
+
+        // If we have a selection name, select it
+        if (itemName != null) {
+            selectItemName(itemName, items);
+        }
+    }
+
+    public static String chooseResource(
+            String[] themes,
+            String currentTheme) {
+        Shell shell = AdtPlugin.getDisplay().getActiveShell();
+        if (shell == null) {
+            return null;
+        }
+
+        ThemeChooser dialog = new ThemeChooser(themes, shell);
+        dialog.setCurrentResource(currentTheme);
+
+        int result = dialog.open();
+        if (result == ThemeChooser.CLEAR_RETURN_CODE) {
+            return ""; //$NON-NLS-1$
+        } else if (result == Window.OK) {
+            return dialog.getCurrentResource();
+        }
+
+        return null;
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
new file mode 100644
index 0000000..194cb3f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
+
+import com.android.ide.eclipse.adt.internal.editors.Hyperlinks;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SubmenuAction;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.text.hyperlink.IHyperlink;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.ToolItem;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Action which creates a submenu displaying available themes
+ */
+class ThemeMenuAction extends SubmenuAction {
+    private static final String DEVICE_LIGHT_PREFIX =
+            PREFIX_ANDROID_STYLE + "Theme.DeviceDefault.Light";  //$NON-NLS-1$
+    private static final String HOLO_LIGHT_PREFIX =
+            PREFIX_ANDROID_STYLE + "Theme.Holo.Light";           //$NON-NLS-1$
+    private static final String DEVICE_PREFIX =
+            PREFIX_ANDROID_STYLE + "Theme.DeviceDefault";        //$NON-NLS-1$
+    private static final String HOLO_PREFIX =
+            PREFIX_ANDROID_STYLE + "Theme.Holo";                 //$NON-NLS-1$
+    private static final String LIGHT_PREFIX =
+            PREFIX_ANDROID_STYLE +"Theme.Light";                 //$NON-NLS-1$
+    private static final String THEME_PREFIX =
+            PREFIX_ANDROID_STYLE +"Theme";                       //$NON-NLS-1$
+
+    // Constants used to indicate what type of menu is being shown, such that
+    // the submenus can lazily construct their contents
+    private static final int MENU_MANIFEST = 1;
+    private static final int MENU_PROJECT = 2;
+    private static final int MENU_THEME = 3;
+    private static final int MENU_THEME_LIGHT = 4;
+    private static final int MENU_HOLO = 5;
+    private static final int MENU_HOLO_LIGHT = 6;
+    private static final int MENU_DEVICE = 7;
+    private static final int MENU_DEVICE_LIGHT = 8;
+    private static final int MENU_ALL = 9;
+
+    private final ConfigurationComposite mConfiguration;
+    private final List<String> mThemeList;
+    /** Type of menu; one of the constants {@link #MENU_ALL} etc */
+    private final int mType;
+
+    ThemeMenuAction(int type, String title, ConfigurationComposite configuration,
+            List<String> themeList) {
+        super(title);
+        mType = type;
+        mConfiguration = configuration;
+        mThemeList = themeList;
+    }
+
+    static void showThemeMenu(ConfigurationComposite configuration, ToolItem combo,
+            List<String> themeList) {
+        MenuManager manager = new MenuManager();
+
+        // First show the currently selected theme (grayed out since you can't
+        // reselect it)
+        String currentTheme = configuration.getSelectedTheme();
+        String currentName = null;
+        if (currentTheme != null) {
+            currentName = ResourceHelper.styleToTheme(currentTheme);
+            SelectThemeAction action = new SelectThemeAction(configuration,
+                    currentName,
+                    currentTheme,
+                    true /* selected */);
+            action.setEnabled(false);
+            manager.add(action);
+            manager.add(new Separator());
+        }
+
+        String preferred = configuration.getPreferredTheme();
+        if (preferred != null && !preferred.equals(currentTheme)) {
+            manager.add(new SelectThemeAction(configuration,
+                    ResourceHelper.styleToTheme(preferred),
+                    preferred, false /* selected */));
+            manager.add(new Separator());
+        }
+
+        IAndroidTarget target = configuration.getRenderingTarget();
+        int apiLevel = target.getVersion().getApiLevel();
+        boolean hasHolo = apiLevel >= 11;   // Honeycomb
+        boolean hasDeviceDefault = apiLevel >= 14; // ICS
+
+        // TODO: Add variations of the current theme here, e.g.
+        // if you're using Theme.Holo, add Theme.Holo.Dialog, Theme.Holo.Panel,
+        // Theme.Holo.Wallpaper etc
+
+        manager.add(new ThemeMenuAction(MENU_PROJECT, "Project Themes",
+                configuration, themeList));
+        manager.add(new ThemeMenuAction(MENU_MANIFEST, "Manifest Themes",
+                configuration, themeList));
+
+        manager.add(new Separator());
+
+        if (hasHolo) {
+            manager.add(new ThemeMenuAction(MENU_HOLO, "Holo",
+                    configuration, themeList));
+            manager.add(new ThemeMenuAction(MENU_HOLO_LIGHT, "Holo.Light",
+                    configuration, themeList));
+        }
+        if (hasDeviceDefault) {
+            manager.add(new ThemeMenuAction(MENU_DEVICE, "DeviceDefault",
+                    configuration, themeList));
+            manager.add(new ThemeMenuAction(MENU_DEVICE_LIGHT, "DeviceDefault.Light",
+                    configuration, themeList));
+        }
+        manager.add(new ThemeMenuAction(MENU_THEME, "Theme",
+                configuration, themeList));
+        manager.add(new ThemeMenuAction(MENU_THEME_LIGHT, "Theme.Light",
+                configuration, themeList));
+
+        // TODO: Add generic types like Wallpaper, Dialog, Alert, etc here, with
+        // submenus for picking it within each theme category?
+
+        manager.add(new Separator());
+        manager.add(new ThemeMenuAction(MENU_ALL, "All",
+                configuration, themeList));
+
+        if (currentTheme != null) {
+            assert currentName != null;
+            manager.add(new Separator());
+            String title = String.format("Open %1$s Declaration...", currentName);
+            manager.add(new OpenThemeAction(title, configuration.getEditedFile(), currentTheme));
+        }
+
+        Menu menu = manager.createContextMenu(configuration.getShell());
+
+        Rectangle bounds = combo.getBounds();
+        Point location = new Point(bounds.x, bounds.y + bounds.height);
+        location = combo.getParent().toDisplay(location);
+        menu.setLocation(location.x, location.y);
+        menu.setVisible(true);
+    }
+
+    @Override
+    protected void addMenuItems(Menu menu) {
+        switch (mType) {
+            case MENU_ALL:
+                addMenuItems(menu, mThemeList);
+                break;
+
+            case MENU_MANIFEST: {
+                IProject project = mConfiguration.getEditedFile().getProject();
+                ManifestInfo manifest = ManifestInfo.get(project);
+                Map<String, String> activityThemes = manifest.getActivityThemes();
+                String activity = mConfiguration.getSelectedActivity();
+                if (activity != null) {
+                    String theme = activityThemes.get(activity);
+                    if (theme != null) {
+                        addMenuItem(menu, theme, isSelectedTheme(theme));
+                    }
+                }
+
+                String manifestTheme = manifest.getmManifestTheme();
+                if (activityThemes.size() > 0 || manifestTheme != null) {
+                    Set<String> allThemes = new HashSet<String>(activityThemes.values());
+                    if (manifestTheme != null) {
+                        allThemes.add(manifestTheme);
+                    }
+                    List<String> sorted = new ArrayList<String>(allThemes);
+                    Collections.sort(sorted);
+                    String current = mConfiguration.getSelectedTheme();
+                    for (String theme : sorted) {
+                        boolean selected = theme.equals(current);
+                        theme = ResourceHelper.styleToTheme(theme);
+                        addMenuItem(menu, theme, selected);
+                    }
+                } else {
+                    addDisabledMessageItem("No themes are registered in the manifest");
+                }
+                break;
+            }
+            case MENU_PROJECT: {
+                int size = mThemeList.size();
+                List<String> themes = new ArrayList<String>(size);
+                for (int i = 0; i < size; i++) {
+                    String theme = mThemeList.get(i);
+                    if (ResourceHelper.isProjectStyle(theme)) {
+                        themes.add(theme);
+                    }
+                }
+                if (themes.isEmpty()) {
+                    addDisabledMessageItem("There are no local theme styles in the project");
+                } else {
+                    addMenuItems(menu, themes);
+                }
+                break;
+            }
+            case MENU_THEME: {
+                // Can't just use the usual filterThemes() call here because we need
+                // to exclude on multiple prefixes: Holo, DeviceDefault, Light, ...
+                List<String> themes = new ArrayList<String>(mThemeList.size());
+                for (String theme : mThemeList) {
+                    if (theme.startsWith(THEME_PREFIX)
+                            && !theme.startsWith(LIGHT_PREFIX)
+                            && !theme.startsWith(HOLO_PREFIX)
+                            && !theme.startsWith(DEVICE_PREFIX)) {
+                        themes.add(theme);
+                    }
+                }
+
+                addMenuItems(menu, themes);
+                break;
+            }
+            case MENU_THEME_LIGHT:
+                addMenuItems(menu, filterThemes(LIGHT_PREFIX, null));
+                break;
+            case MENU_HOLO:
+                addMenuItems(menu, filterThemes(HOLO_PREFIX, HOLO_LIGHT_PREFIX));
+                break;
+            case MENU_HOLO_LIGHT:
+                addMenuItems(menu, filterThemes(HOLO_LIGHT_PREFIX, null));
+                break;
+            case MENU_DEVICE:
+                addMenuItems(menu, filterThemes(DEVICE_PREFIX, DEVICE_LIGHT_PREFIX));
+                break;
+            case MENU_DEVICE_LIGHT:
+                addMenuItems(menu, filterThemes(DEVICE_LIGHT_PREFIX, null));
+                break;
+        }
+    }
+
+    private List<String> filterThemes(String include, String exclude) {
+        List<String> themes = new ArrayList<String>(mThemeList.size());
+        for (String theme : mThemeList) {
+            if (theme.startsWith(include) && (exclude == null || !theme.startsWith(exclude))) {
+                themes.add(theme);
+            }
+        }
+
+        return themes;
+    }
+
+    private void addMenuItems(Menu menu, List<String> themes) {
+        String current = mConfiguration.getSelectedTheme();
+        for (String theme : themes) {
+            addMenuItem(menu, theme, theme.equals(current));
+        }
+    }
+
+    private boolean isSelectedTheme(String theme) {
+        return theme.equals(mConfiguration.getSelectedTheme());
+    }
+
+    private void addMenuItem(Menu menu, String theme, boolean selected) {
+        String title = ResourceHelper.styleToTheme(theme);
+        SelectThemeAction action = new SelectThemeAction(mConfiguration, title, theme, selected);
+        new ActionContributionItem(action).fill(menu, -1);
+    }
+
+    private static class OpenThemeAction extends Action {
+        private final String mTheme;
+        private final IFile mFile;
+
+        private OpenThemeAction(String title, IFile file, String theme) {
+            super(title, IAction.AS_PUSH_BUTTON);
+            mFile = file;
+            mTheme = theme;
+        }
+
+        @Override
+        public void run() {
+            IProject project = mFile.getProject();
+            IHyperlink[] links = Hyperlinks.getResourceLinks(null, mTheme, project, null);
+            if (links != null && links.length > 0) {
+                IHyperlink link = links[0];
+                link.open();
+            }
+        }
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
index 5abbb0c..23aa2a0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
@@ -16,19 +16,43 @@
 
 package com.android.ide.eclipse.adt.internal.editors.layout.descriptors;
 
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
 import static com.android.sdklib.SdkConstants.CLASS_VIEWGROUP;
+import static com.android.tools.lint.detector.api.LintConstants.AUTO_URI;
+import static com.android.tools.lint.detector.api.LintConstants.URI_PREFIX;
 
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceFile;
+import com.android.ide.common.resources.ResourceItem;
+import com.android.ide.common.resources.platform.AttributeInfo;
+import com.android.ide.common.resources.platform.AttrsXmlParser;
 import com.android.ide.common.resources.platform.ViewClassInfo;
+import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceType;
 import com.android.sdklib.IAndroidTarget;
+import com.google.common.collect.ObjectArrays;
 
 import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jdt.core.IClassFile;
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.IType;
 import org.eclipse.jdt.core.ITypeHierarchy;
@@ -36,8 +60,13 @@
 import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.swt.graphics.Image;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Service responsible for creating/managing {@link ViewElementDescriptor} objects for custom
@@ -162,11 +191,24 @@
 
                     if (parentDescriptor != null) {
                         // we have a valid parent, lets create a new ViewElementDescriptor.
+                        List<AttributeDescriptor> attrList = new ArrayList<AttributeDescriptor>();
+                        List<AttributeDescriptor> paramList = new ArrayList<AttributeDescriptor>();
+                        findCustomDescriptors(project, type, attrList, paramList);
 
+                        AttributeDescriptor[] attributes =
+                                getAttributeDescriptor(type, parentDescriptor);
+                        if (!attrList.isEmpty()) {
+                            attributes = join(attrList, attributes);
+                        }
+                        AttributeDescriptor[] layoutAttributes =
+                                getLayoutAttributeDescriptors(type, parentDescriptor);
+                        if (!paramList.isEmpty()) {
+                            layoutAttributes = join(paramList, layoutAttributes);
+                        }
                         String name = DescriptorsUtils.getBasename(fqcn);
                         ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
-                                getAttributeDescriptor(type, parentDescriptor),
-                                getLayoutAttributeDescriptors(type, parentDescriptor),
+                                attributes,
+                                layoutAttributes,
                                 parentDescriptor.getChildren());
                         descriptor.setSuperClass(parentDescriptor);
 
@@ -193,6 +235,168 @@
         return null;
     }
 
+    private static AttributeDescriptor[] join(
+            @NonNull List<AttributeDescriptor> attributeList,
+            @NonNull AttributeDescriptor[] attributes) {
+        if (!attributeList.isEmpty()) {
+            return ObjectArrays.concat(
+                    attributeList.toArray(new AttributeDescriptor[attributeList.size()]),
+                    attributes,
+                    AttributeDescriptor.class);
+        } else {
+            return attributes;
+        }
+
+    }
+
+    /** Cache used by {@link #getParser(ResourceFile)} */
+    private Map<ResourceFile, AttrsXmlParser> mParserCache;
+
+    private AttrsXmlParser getParser(ResourceFile file) {
+        if (mParserCache == null) {
+            mParserCache = new HashMap<ResourceFile, AttrsXmlParser>();
+        }
+
+        AttrsXmlParser parser = mParserCache.get(file);
+        if (parser == null) {
+            parser = new AttrsXmlParser(
+                    file.getFile().getOsLocation(),
+                    AdtPlugin.getDefault());
+            parser.preload();
+            mParserCache.put(file, parser);
+        }
+
+        return parser;
+    }
+
+    /** Compute/find the styleable resources for the given type, if possible */
+    private void findCustomDescriptors(
+            IProject project,
+            IType type,
+            List<AttributeDescriptor> customAttributes,
+            List<AttributeDescriptor> customLayoutAttributes) {
+        // Look up the project where the type is declared (could be a library project;
+        // we cannot use type.getJavaProject().getProject())
+        IProject library = getProjectDeclaringType(type);
+        if (library == null) {
+            library = project;
+        }
+
+        String className = type.getElementName();
+        Set<ResourceFile> resourceFiles = findAttrsFiles(library, className);
+        if (resourceFiles != null && resourceFiles.size() > 0) {
+            String appUri = getAppResUri(project);
+            for (ResourceFile file : resourceFiles) {
+                AttrsXmlParser attrsXmlParser = getParser(file);
+                String fqcn = type.getFullyQualifiedName();
+
+                // Attributes
+                ViewClassInfo classInfo = new ViewClassInfo(true, fqcn, className);
+                attrsXmlParser.loadViewAttributes(classInfo);
+                appendAttributes(customAttributes, classInfo.getAttributes(), appUri);
+
+                // Layout params
+                LayoutParamsInfo layoutInfo = new ViewClassInfo.LayoutParamsInfo(
+                        classInfo, "Layout", null /*superClassInfo*/); //$NON-NLS-1$
+                attrsXmlParser.loadLayoutParamsAttributes(layoutInfo);
+                appendAttributes(customLayoutAttributes, layoutInfo.getAttributes(), appUri);
+            }
+        }
+    }
+
+    /**
+     * Finds the set of XML files (if any) in the given library declaring
+     * attributes for the given class name
+     */
+    @Nullable
+    private Set<ResourceFile> findAttrsFiles(IProject library, String className) {
+        Set<ResourceFile> resourceFiles = null;
+        ResourceManager manager = ResourceManager.getInstance();
+        ProjectResources resources = manager.getProjectResources(library);
+        if (resources != null) {
+            Collection<ResourceItem> items =
+                resources.getResourceItemsOfType(ResourceType.DECLARE_STYLEABLE);
+            for (ResourceItem item : items) {
+                String viewName = item.getName();
+                if (viewName.equals(className)
+                        || (viewName.startsWith(className)
+                            && viewName.equals(className + "_Layout"))) { //$NON-NLS-1$
+                    if (resourceFiles == null) {
+                        resourceFiles = new HashSet<ResourceFile>();
+                    }
+                    resourceFiles.addAll(item.getSourceFileList());
+                }
+            }
+        }
+        return resourceFiles;
+    }
+
+    /**
+     * Find the project containing this type declaration. We cannot use
+     * {@link IType#getJavaProject()} since that will return the including
+     * project and we're after the library project such that we can find the
+     * attrs.xml file in the same project.
+     */
+    @Nullable
+    private IProject getProjectDeclaringType(IType type) {
+        IClassFile classFile = type.getClassFile();
+        if (classFile != null) {
+            IPath path = classFile.getPath();
+            IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+            IResource resource;
+            if (path.isAbsolute()) {
+                resource = AdtUtils.fileToResource(path.toFile());
+            } else {
+                resource = workspace.findMember(path);
+            }
+            if (resource != null && resource.getProject() != null) {
+                return resource.getProject();
+            }
+        }
+
+        return null;
+    }
+
+    /** Returns the name space to use for application attributes */
+    private String getAppResUri(IProject project) {
+        String appResource;
+        ProjectState projectState = Sdk.getProjectState(project);
+        if (projectState != null && projectState.isLibrary()) {
+            appResource = AUTO_URI;
+        } else {
+            ManifestInfo manifestInfo = ManifestInfo.get(project);
+            appResource = URI_PREFIX + manifestInfo.getPackage();
+        }
+        return appResource;
+    }
+
+
+    /** Append the {@link AttributeInfo} objects converted {@link AttributeDescriptor}
+     * objects into the given attribute list.
+     * <p>
+     * This is nearly identical to
+     *  {@link DescriptorsUtils#appendAttribute(List, String, String, AttributeInfo, boolean, Map)}
+     * but it handles namespace declarations in the attrs.xml file where the android:
+     * namespace is included in the names.
+     */
+    private static void appendAttributes(List<AttributeDescriptor> attributes,
+            AttributeInfo[] attributeInfos, String appResource) {
+        // Custom attributes
+        for (AttributeInfo info : attributeInfos) {
+            String nsUri;
+            if (info.getName().startsWith(ANDROID_NS_NAME_PREFIX)) {
+                info.setName(info.getName().substring(ANDROID_NS_NAME_PREFIX.length()));
+                nsUri = ANDROID_URI;
+            } else {
+                nsUri = appResource;
+            }
+
+            DescriptorsUtils.appendAttribute(attributes,
+                    null /*elementXmlName*/, nsUri, info, false /*required*/,
+                    null /*overrides*/);
+        }
+    }
+
     /**
      * Computes (if needed) and returns the {@link ViewElementDescriptor} for the specified type.
      *
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..51328f3 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,10 +21,13 @@
 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;
 
+import org.eclipse.core.resources.IFile;
 import org.eclipse.jface.text.IDocument;
 import org.eclipse.wst.sse.core.StructuredModelManager;
 import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
@@ -70,7 +73,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 +96,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 +121,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 +137,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) {
@@ -144,6 +149,41 @@
     }
 
     /**
+     * Returns the DOM document for the given file
+     *
+     * @param file the XML file
+     * @return the document, or null if not found or not parsed properly (no
+     *         errors are generated/thrown)
+     */
+    @Nullable
+    public static Document getDocument(@NonNull IFile file) {
+        IModelManager modelManager = StructuredModelManager.getModelManager();
+        if (modelManager == null) {
+            return null;
+        }
+        try {
+            IStructuredModel model = modelManager.getExistingModelForRead(file);
+            if (model == null) {
+                model = modelManager.getModelForRead(file);
+            }
+            if (model != null) {
+                if (model instanceof IDOMModel) {
+                    IDOMModel domModel = (IDOMModel) model;
+                    return domModel.getDocument();
+                }
+                try {
+                } finally {
+                    model.releaseFromRead();
+                }
+            }
+        } catch (Exception e) {
+            // Ignore exceptions.
+        }
+
+        return null;
+    }
+
+    /**
      * Returns the XML DOM node corresponding to the given offset of the given
      * document.
      *
@@ -151,7 +191,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 +245,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 +325,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 +361,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 +427,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 +442,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 +457,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 +473,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 +494,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 +523,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 +537,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 +563,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 +597,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 +621,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 +674,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 +766,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 +790,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 +827,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 +844,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 +860,7 @@
      *
      * @return a new Eclipse XML model
      */
+    @NonNull
     public static IStructuredModel createEmptyModel() {
         IModelManager modelManager = StructuredModelManager.getModelManager();
         return modelManager.createUnManagedStructuredModelFor(ContentTypeID_XML);
@@ -813,6 +871,7 @@
      *
      * @return an empty Eclipse XML document
      */
+    @Nullable
     public static Document createEmptyDocument() {
         IStructuredModel model = createEmptyModel();
         if (model instanceof IDOMModel) {
@@ -832,7 +891,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/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index 06cce40..354ef9a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -53,9 +53,11 @@
 import com.android.ide.common.sdk.LoadStatus;
 import com.android.ide.eclipse.adt.AdtConstants;
 import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
 import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;
 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;
@@ -73,6 +75,7 @@
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
 import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
@@ -160,6 +163,7 @@
 import org.eclipse.wb.core.controls.flyout.IFlyoutListener;
 import org.eclipse.wb.core.controls.flyout.PluginFlyoutPreferences;
 import org.eclipse.wb.internal.core.editor.structure.PageSiteComposite;
+import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
 import java.io.File;
@@ -350,8 +354,6 @@
             }
         }
 
-        mConfigComposite = new ConfigurationComposite(mConfigListener, parent,
-                SWT.BORDER, initialState);
         PluginFlyoutPreferences preferences =
                 new PluginFlyoutPreferences(AdtPlugin.getDefault().getPreferenceStore(),
                         "design.palette"); //$NON-NLS-1$
@@ -389,8 +391,13 @@
         gridLayout.marginWidth = 0;
         gridLayout.marginHeight = 0;
         layoutBarAndCanvas.setLayout(gridLayout);
+
+        mConfigComposite = new ConfigurationComposite(mConfigListener, layoutBarAndCanvas,
+                SWT.NONE /*SWT.BORDER*/, initialState);
+        mConfigComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
         mActionBar = new LayoutActionBar(layoutBarAndCanvas, SWT.NONE, this);
-        GridData detailsData = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1);
+        GridData detailsData = new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1);
         mActionBar.setLayoutData(detailsData);
         if (file != null) {
             mActionBar.updateErrorIndicator(EclipseLintClient.hasMarkers(file));
@@ -695,7 +702,15 @@
                     // display the error.
                     FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();
                     displayError(
-                            "No resources match the configuration\n \n\t%1$s\n \nChange the configuration or create:\n \n\tres/%2$s/%3$s\n \nYou can also click the 'Create' button above.",
+                            "No resources match the configuration\n" +
+                            " \n" +
+                            "\t%1$s\n" +
+                            " \n" +
+                            "Change the configuration or create:\n" +
+                            " \n" +
+                            "\tres/%2$s/%3$s\n" +
+                            " \n" +
+                            "You can also click the 'Create New...' item in the configuration dropdown menu above.",
                             currentConfig.toDisplayString(),
                             currentConfig.getFolderName(ResourceFolderType.LAYOUT),
                             mEditedFile.getName());
@@ -729,6 +744,21 @@
         }
 
         @Override
+        public void onSetActivity(String activity) {
+            ManifestInfo manifest = ManifestInfo.get(mEditedFile.getProject());
+            String pkg = manifest.getPackage();
+            if (activity.startsWith(pkg) && activity.length() > pkg.length()
+                    && activity.charAt(pkg.length()) == '.') {
+                activity = activity.substring(pkg.length());
+            }
+            CommonXmlEditor editor = getEditorDelegate().getEditor();
+            Element element = editor.getUiRootNode().getXmlDocument().getDocumentElement();
+            AdtUtils.setToolsAttribute(editor,
+                    element, "Choose Activity", ConfigurationComposite.ATTR_CONTEXT,
+                    activity, false /*reveal*/, false /*append*/);
+        }
+
+        @Override
         public void onRenderingTargetPreChange(IAndroidTarget oldTarget) {
             preRenderingTargetChangeCleanUp(oldTarget);
         }
@@ -912,6 +942,12 @@
                                 // pass
                             }
                         });
+
+                        // Switch to the new file as well
+                        IFile file = AdtUtils.fileToIFile(newLayoutFile);
+                        if (file != null) {
+                            AdtPlugin.openFile(file, null, false);
+                        }
                     } catch (IOException e2) {
                         String message = String.format(
                                 "Failed to create File 'res/%1$s/%2$s' : %3$s",
@@ -1588,7 +1624,7 @@
      */
     public ResourceResolver getResourceResolver() {
         if (mResourceResolver == null) {
-            String theme = mConfigComposite.getTheme();
+            String theme = mConfigComposite.getThemeName();
             if (theme == null) {
                 displayError("Missing theme.");
                 return null;
@@ -1643,12 +1679,7 @@
      * @return the resource name of this layout, NOT including the @layout/ prefix
      */
     public String getLayoutResourceName() {
-        String name = mEditedFile.getName();
-        int dotIndex = name.indexOf('.');
-        if (dotIndex != -1) {
-            name = name.substring(0, dotIndex);
-        }
-        return name;
+        return ResourceHelper.getLayoutName(mEditedFile);
     }
 
     /**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
index 4704791..a5df4bd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
@@ -126,9 +126,17 @@
             assert awtImage instanceof SwtReadyBufferedImage;
 
             if (isAlphaChannelImage) {
+                if (mImage != null) {
+                    mImage.dispose();
+                }
+
                 mImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), awtImage, true, -1);
             } else {
+                Image prev = mImage;
                 mImage = ((SwtReadyBufferedImage)awtImage).getSwtImage();
+                if (prev != mImage && prev != null) {
+                    prev.dispose();
+                }
             }
         }
 
@@ -195,6 +203,9 @@
                         scaledAwtImage = ImageUtils.scale(mAwtImage, xScale, yScale);
                     }
                     assert scaledAwtImage.getWidth() == hi.getScalledImgSize();
+                    if (mPreScaledImage != null && !mPreScaledImage.isDisposed()) {
+                        mPreScaledImage.dispose();
+                    }
                     mPreScaledImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), scaledAwtImage,
                             true /*transferAlpha*/, -1);
                 }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
index e912f53..30a2e78 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
@@ -22,6 +22,8 @@
 import static com.android.ide.eclipse.adt.AdtConstants.DOT_PNG;
 import static com.android.ide.eclipse.adt.AdtUtils.endsWithIgnoreCase;
 
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
 import com.android.ide.common.api.Rect;
 
 import org.eclipse.swt.graphics.RGB;
@@ -124,7 +126,10 @@
      * @return a cropped version of the source image, or null if the whole image was blank
      *         and cropping completely removed everything
      */
-    public static BufferedImage cropBlank(BufferedImage image, Rect initialCrop) {
+    @Nullable
+    public static BufferedImage cropBlank(
+            @NonNull BufferedImage image,
+            @Nullable Rect initialCrop) {
         return cropBlank(image, initialCrop, image.getType());
     }
 
@@ -167,8 +172,11 @@
      * @return a cropped version of the source image, or null if the whole image was blank
      *         and cropping completely removed everything
      */
-    public static BufferedImage cropColor(BufferedImage image,
-            final int blankArgb, Rect initialCrop) {
+    @Nullable
+    public static BufferedImage cropColor(
+            @NonNull BufferedImage image,
+            final int blankArgb,
+            @Nullable Rect initialCrop) {
         return cropColor(image, blankArgb, initialCrop, image.getType());
     }
 
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..9898a89 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;
@@ -39,6 +41,7 @@
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
@@ -72,6 +75,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.
@@ -87,10 +91,10 @@
         GridLayout layout = new GridLayout(3, false);
         setLayout(layout);
 
-        mLayoutToolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
-        mLayoutToolBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, true, false));
+        mLayoutToolBar = new ToolBar(this, /*SWT.WRAP |*/ SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
+        mLayoutToolBar.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
         mZoomToolBar = createZoomControls();
-        mZoomToolBar.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, true, false));
+        mZoomToolBar.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, false, false));
         mLintToolBar = createLintControls();
 
         GridData lintData = new GridData(SWT.END, SWT.BEGINNING, false, false);
@@ -98,14 +102,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 +171,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 +312,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 +323,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 +351,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 +362,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 +375,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 +391,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 +410,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,16 +419,13 @@
         } else {
             combo.setText(choices.getTitle());
         }
+        combo.setData(choices);
 
         Listener menuListener = new Listener() {
             @Override
             public void handleEvent(Event event) {
-                // if (event.detail == SWT.ARROW) {
-                Point point = new Point(event.x, event.y);
-                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,15 +450,17 @@
                     item.addSelectionListener(new SelectionAdapter() {
                         @Override
                         public void widgetSelected(SelectionEvent e) {
+                            RuleAction.Choices choices = (Choices) combo.getData();
                             choices.getCallback().action(choices, getSelectedNodes(), id, null);
                             updateSelection();
                         }
                     });
                 }
 
-                // TODO - how do I dispose of this?
-
-                menu.setLocation(point);
+                Rectangle bounds = combo.getBounds();
+                Point location = new Point(bounds.x, bounds.y + bounds.height);
+                location = combo.getParent().toDisplay(location);
+                menu.setLocation(location.x, location.y);
                 menu.setVisible(true);
             }
         };
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index 1354ebe..7040584 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
@@ -1392,7 +1392,7 @@
         if (ViewMetadataRepository.INSETS_SUPPORTED) {
             ConfigurationComposite configComposite =
                     mEditorDelegate.getGraphicalEditor().getConfigurationComposite();
-            String theme = configComposite.getTheme();
+            String theme = configComposite.getThemeName();
             Density density = configComposite.getDensity();
             return ViewMetadataRepository.getInsets(fqcn, density, theme);
         } else {
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.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
index f043205..8af4f2a 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
@@ -375,7 +375,7 @@
      */
     public void reloadPalette(IAndroidTarget target) {
         ConfigurationComposite configuration = mEditor.getConfigurationComposite();
-        String theme = configuration.getTheme();
+        String theme = configuration.getThemeName();
         String device = configuration.getDevice();
         AndroidTargetData targetData =
             target != null ? Sdk.getCurrent().getTargetData(target) : null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java
index f031028..0923dda 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SubmenuAction.java
@@ -14,10 +14,10 @@
 /**
  * Action which creates a submenu that is dynamically populated by subclasses
  */
-abstract class SubmenuAction extends Action implements MenuListener, IMenuCreator {
+public abstract class SubmenuAction extends Action implements MenuListener, IMenuCreator {
     private Menu mMenu;
 
-    SubmenuAction(String title) {
+    public SubmenuAction(String title) {
         super(title, IAction.AS_DROP_DOWN_MENU);
     }
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
index 5bd35a1..435529a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
@@ -60,6 +60,7 @@
         AttributeDescriptor[] direct_attrs = super.getAttributeDescriptors();
         mCachedAttributeDescriptors = direct_attrs;
 
+        // Compute layout attributes: These depend on the *parent* this widget is within
         AttributeDescriptor[] layout_attrs = null;
         boolean need_xmlns = false;
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
index f749e2b..c08a67e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
@@ -51,6 +51,7 @@
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.core.runtime.QualifiedName;
 import org.eclipse.jdt.core.IField;
 import org.eclipse.jdt.core.IJavaElement;
@@ -59,12 +60,14 @@
 import org.eclipse.jdt.core.IPackageFragment;
 import org.eclipse.jdt.core.IPackageFragmentRoot;
 import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeHierarchy;
 import org.eclipse.jdt.core.search.IJavaSearchScope;
 import org.eclipse.jdt.core.search.SearchEngine;
 import org.eclipse.jdt.core.search.SearchMatch;
 import org.eclipse.jdt.core.search.SearchParticipant;
 import org.eclipse.jdt.core.search.SearchPattern;
 import org.eclipse.jdt.core.search.SearchRequestor;
+import org.eclipse.jdt.internal.core.BinaryType;
 import org.eclipse.jface.text.IDocument;
 import org.eclipse.ui.editors.text.TextFileDocumentProvider;
 import org.eclipse.ui.texteditor.IDocumentProvider;
@@ -74,10 +77,11 @@
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -159,6 +163,9 @@
      * with respect to the manifest file
      */
     private void sync() {
+        // TODO: Add a last synced timestamp so that I can avoid doing all this with rapid
+        // burst calls on separate methods which all call sync() first!
+
         if (mManifestFile == null) {
             IFolderWrapper projectFolder = new IFolderWrapper(mProject);
             mManifestFile = AndroidManifest.getManifest(projectFolder);
@@ -297,6 +304,17 @@
     }
 
     /**
+     * Returns the manifest theme registered on the application, if any
+     *
+     * @return a manifest theme, or null if none was registered
+     */
+    @Nullable
+    public String getmManifestTheme() {
+        sync();
+        return mManifestTheme;
+    }
+
+    /**
      * Returns the default theme for this project, by looking at the manifest default
      * theme registration, target SDK, rendering target, etc.
      *
@@ -404,7 +422,27 @@
      */
     @Nullable
     public static String guessActivity(IProject project, String layoutName, String pkg) {
-        final AtomicReference<String> activity = new AtomicReference<String>();
+        List<String> activities = guessActivities(project, layoutName, pkg);
+        if (activities.size() > 0) {
+            return activities.get(0);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the activities associated with the given layout file. Makes an educated guess
+     * by peeking at the usages of the R.layout.name field corresponding to the layout and
+     * if it finds a usage.
+     *
+     * @param project the project containing the layout
+     * @param layoutName the layout whose activity we want to look up
+     * @param pkg the package containing activities
+     * @return the activity name
+     */
+    @NonNull
+    public static List<String> guessActivities(IProject project, String layoutName, String pkg) {
+        final LinkedList<String> activities = new LinkedList<String>();
         SearchRequestor requestor = new SearchRequestor() {
             @Override
             public void acceptSearchMatch(SearchMatch match) throws CoreException {
@@ -413,11 +451,13 @@
                     IMethod method = (IMethod) element;
                     IType declaringType = method.getDeclaringType();
                     String fqcn = declaringType.getFullyQualifiedName();
-                    if (activity.get() == null
-                            || (declaringType.getSuperclassName() != null &&
-                                    declaringType.getSuperclassName().endsWith("Activity")) //$NON-NLS-1$
-                            || method.getElementName().equals("onCreate")) { //$NON-NLS-1$
-                        activity.set(fqcn);
+
+                    if ((declaringType.getSuperclassName() != null &&
+                            declaringType.getSuperclassName().endsWith("Activity")) //$NON-NLS-1$
+                        || method.getElementName().equals("onCreate")) { //$NON-NLS-1$
+                        activities.addFirst(fqcn);
+                    } else {
+                        activities.addLast(fqcn);
                     }
                 }
             }
@@ -441,17 +481,58 @@
                 IField field = type.getField(layoutName);
                 if (field.exists()) {
                     SearchPattern pattern = SearchPattern.createPattern(field, REFERENCES);
-                    search(requestor, javaProject, pattern);
+                    try {
+                        search(requestor, javaProject, pattern);
+                    } catch (OperationCanceledException canceled) {
+                        // pass
+                    }
                 }
             }
         } catch (CoreException e) {
             AdtPlugin.log(e, null);
         }
 
-        return activity.get();
+        return activities;
     }
 
     /**
+     * Returns all activities found in the given project (including those in libraries,
+     * except for android.jar itself)
+     *
+     * @param project the project
+     * @return a list of activity classes as fully qualified class names
+     */
+    @SuppressWarnings("restriction") // BinaryType
+    @NonNull
+    public static List<String> getProjectActivities(IProject project) {
+        final List<String> activities = new ArrayList<String>();
+        try {
+            final IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+            if (javaProject != null) {
+                IType[] activityTypes = new IType[0];
+                IType activityType = javaProject.findType(SdkConstants.CLASS_ACTIVITY);
+                if (activityType != null) {
+                    ITypeHierarchy hierarchy =
+                        activityType.newTypeHierarchy(javaProject, new NullProgressMonitor());
+                    activityTypes = hierarchy.getAllSubtypes(activityType);
+                    for (IType type : activityTypes) {
+                        if (type instanceof BinaryType && (type.getClassFile() == null
+                                    || type.getClassFile().getResource() == null)) {
+                            continue;
+                        }
+                        activities.add(type.getFullyQualifiedName());
+                    }
+                }
+            }
+        } catch (CoreException e) {
+            AdtPlugin.log(e, null);
+        }
+
+        return activities;
+    }
+
+
+    /**
      * Returns the activity associated with the given layout file.
      * <p>
      * This is an alternative to {@link #guessActivity(IFile, String)}. Whereas
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
index d4bb476..16519fe 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java
@@ -17,6 +17,7 @@
 package com.android.ide.eclipse.adt.internal.editors.manifest.pages;
 
 import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
 import com.android.ide.eclipse.adt.internal.editors.ui.UiElementPart;
@@ -219,7 +220,7 @@
                 }
                 mUndoXmlParent.insertBefore(mUndoXmlNode, next);
                 if (next == null) {
-                    Text sep = mUndoXmlDocument.createTextNode("\n");  //$NON-NLS-1$
+                    Text sep = mUndoXmlDocument.createTextNode(AdtUtils.getLineSeparator());
                     mUndoXmlParent.insertBefore(sep, null);  // insert separator before end tag
                 }
                 success = true;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
index 219754b..42a8679 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
@@ -17,6 +17,8 @@
 package com.android.ide.eclipse.adt.internal.editors.uimodel;
 
 import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_PKG_PREFIX;
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_SUPPORT_PKG_PREFIX;
 import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
 import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX;
 import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
@@ -30,6 +32,7 @@
 import com.android.ide.common.api.IAttributeInfo.Format;
 import com.android.ide.common.resources.platform.AttributeInfo;
 import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
@@ -48,10 +51,11 @@
 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
 import com.android.sdklib.SdkConstants;
 
-import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.text.TextUtilities;
 import org.eclipse.jface.viewers.StyledString;
 import org.eclipse.ui.views.properties.IPropertyDescriptor;
 import org.eclipse.ui.views.properties.IPropertySource;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
 import org.eclipse.wst.xml.core.internal.document.ElementImpl;
 import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
@@ -1033,7 +1037,14 @@
         }
 
         // Insert indent text node before the new element
-        Text indentNode = doc.createTextNode("\n" + indent); //$NON-NLS-1$
+        IStructuredDocument document = editor.getStructuredDocument();
+        String newLine;
+        if (document != null) {
+            newLine = TextUtilities.getDefaultLineDelimiter(document);
+        } else {
+            newLine = AdtUtils.getLineSeparator();
+        }
+        Text indentNode = doc.createTextNode(newLine + indent);
         parentXmlNode.insertBefore(indentNode, xmlNextSibling);
 
         // Insert the element itself
@@ -1043,7 +1054,7 @@
         // a tag into an area where there was no whitespace before
         // (e.g. a new child of <LinearLayout></LinearLayout>).
         if (insertAfter != null) {
-            Text sep = doc.createTextNode("\n" + insertAfter); //$NON-NLS-1$
+            Text sep = doc.createTextNode(newLine + insertAfter);
             parentXmlNode.insertBefore(sep, xmlNextSibling);
         }
 
@@ -1165,10 +1176,19 @@
             if (xmlChild.getNodeType() == Node.ELEMENT_NODE) {
                 String elementName = xmlChild.getNodeName();
                 UiElementNode uiNode = null;
+                CustomViewDescriptorService service = CustomViewDescriptorService.getInstance();
                 if (mUiChildren.size() <= uiIndex) {
                     // A new node is being added at the end of the list
                     ElementDescriptor desc = mDescriptor.findChildrenDescriptor(elementName,
                             false /* recursive */);
+                    if (desc == null && elementName.indexOf('.') != -1 &&
+                            (!elementName.startsWith(ANDROID_PKG_PREFIX)
+                                    || elementName.startsWith(ANDROID_SUPPORT_PKG_PREFIX))) {
+                        AndroidXmlEditor editor = getEditor();
+                        if (editor != null && editor.getProject() != null) {
+                            desc = service.getDescriptor(editor.getProject(), elementName);
+                        }
+                    }
                     if (desc == null) {
                         // Unknown node. Create a temporary descriptor for it.
                         // We'll add unknown attributes to it later.
@@ -1227,11 +1247,12 @@
                         // Inserting new node
                         ElementDescriptor desc = mDescriptor.findChildrenDescriptor(elementName,
                                 false /* recursive */);
-                        if (desc == null && elementName.indexOf('.') != -1) {
-                            IProject project = getEditor().getProject();
-                            if (project != null) {
-                                desc = CustomViewDescriptorService.getInstance().getDescriptor(
-                                        project, elementName);
+                        if (desc == null && elementName.indexOf('.') != -1 &&
+                                (!elementName.startsWith(ANDROID_PKG_PREFIX)
+                                        || elementName.startsWith(ANDROID_SUPPORT_PKG_PREFIX))) {
+                            AndroidXmlEditor editor = getEditor();
+                            if (editor != null && editor.getProject() != null) {
+                                desc = service.getDescriptor(editor.getProject(), elementName);
                             }
                         }
                         if (desc == null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
index cd077ec..1d80c8c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
@@ -18,16 +18,14 @@
 
 import static com.android.tools.lint.detector.api.LintConstants.ATTR_IGNORE;
 import static com.android.tools.lint.detector.api.LintConstants.DOT_XML;
-import static com.android.tools.lint.detector.api.LintConstants.TOOLS_PREFIX;
-import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI;
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
 import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
 
 import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.runtime.CoreException;
@@ -36,9 +34,6 @@
 import org.eclipse.jface.text.contentassist.IContextInformation;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
-import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -90,60 +85,8 @@
 
     @Override
     public void apply(IDocument document) {
-        mEditor.wrapUndoEditXmlModel("Suppress Lint Warning", new Runnable() {
-            @Override
-            public void run() {
-                String prefix = UiElementNode.lookupNamespacePrefix(mElement,
-                        TOOLS_URI, null);
-                if (prefix == null) {
-                    // Add in new prefix...
-                    prefix = UiElementNode.lookupNamespacePrefix(mElement,
-                            TOOLS_URI, TOOLS_PREFIX);
-                    // ...and ensure that the header is formatted such that
-                    // the XML namespace declaration is placed in the right
-                    // position and wrapping is applied etc.
-                    mEditor.scheduleNodeReformat(mEditor.getUiRootNode(),
-                            true /*attributesOnly*/);
-                }
-
-                String ignore = mElement.getAttributeNS(TOOLS_URI, ATTR_IGNORE);
-                if (ignore.length() > 0) {
-                    ignore = ignore + ',' + mId;
-                } else {
-                    ignore = mId;
-                }
-
-                // Use the non-namespace form of set attribute since we can't
-                // reference the namespace until the model has been reloaded
-                mElement.setAttribute(prefix + ':' + ATTR_IGNORE, ignore);
-
-                UiElementNode rootUiNode = mEditor.getUiRootNode();
-                if (rootUiNode != null) {
-                    final UiElementNode uiNode = rootUiNode.findXmlNode(mElement);
-                    if (uiNode != null) {
-                        mEditor.scheduleNodeReformat(uiNode, true /*attributesOnly*/);
-
-                        // Update editor selection after format
-                        Display display = AdtPlugin.getDisplay();
-                        if (display != null) {
-                            display.asyncExec(new Runnable() {
-                                @Override
-                                public void run() {
-                                    Node xmlNode = uiNode.getXmlNode();
-                                    Attr attribute = ((Element) xmlNode).getAttributeNodeNS(
-                                            TOOLS_URI, ATTR_IGNORE);
-                                    if (attribute instanceof IndexedRegion) {
-                                        IndexedRegion region = (IndexedRegion) attribute;
-                                        mEditor.getStructuredTextEditor().selectAndReveal(
-                                                region.getStartOffset(), region.getLength());
-                                    }
-                                }
-                            });
-                        }
-                    }
-                }
-            }
-        });
+        AdtUtils.setToolsAttribute(mEditor, mElement, "Suppress Lint Warning", ATTR_IGNORE, mId,
+                true /*reveal*/, true /*append*/);
 
         try {
             // Remove the marker now that the suppress attribute has been added
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
index 9424bf0..68b3407 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
@@ -266,8 +266,12 @@
         return null;
     }
 
-    /** Clears any lint markers from the given resource (project, folder or file) */
-    static void clearMarkers(IResource resource) {
+    /**
+     * Clears any lint markers from the given resource (project, folder or file)
+     *
+     * @param resource the resource to remove markers from
+     */
+    public static void clearMarkers(@NonNull IResource resource) {
         clearMarkers(Collections.singletonList(resource));
     }
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
index 36a388a..25e80fc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
@@ -15,8 +15,12 @@
  */
 package com.android.ide.eclipse.adt.internal.lint;
 
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_CLASS;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAVA;
 import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
 
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.AdtUtils;
 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
@@ -60,14 +64,19 @@
      * true if fatal errors were found.
      *
      * @param resources the resources (project, folder or file) to be analyzed
+     * @param source if checking a single source file, the source file
      * @param doc the associated document, if known, or null
      * @param fatalOnly if true, only report fatal issues (severity=error)
      * @return true if any fatal errors were encountered.
      */
-    private static boolean runLint(List<? extends IResource> resources, IDocument doc,
+    private static boolean runLint(
+            @NonNull List<? extends IResource> resources,
+            @Nullable IResource source,
+            @Nullable IDocument doc,
             boolean fatalOnly) {
         resources = addLibraries(resources);
-        CheckFileJob job = (CheckFileJob) startLint(resources, doc, fatalOnly, false /*show*/);
+        CheckFileJob job = (CheckFileJob) startLint(resources, source,  doc, fatalOnly,
+                false /*show*/);
         try {
             job.join();
             boolean fatal = job.isFatal();
@@ -85,23 +94,34 @@
     }
 
     /**
-     * Runs lint and updates the markers. Does not wait for the job to
-     * finish - just returns immediately.
+     * Runs lint and updates the markers. Does not wait for the job to finish -
+     * just returns immediately.
      *
      * @param resources the resources (project, folder or file) to be analyzed
+     * @param source if checking a single source file, the source file. When
+     *            single checking an XML file, this is typically the same as the
+     *            file passed in the list in the first parameter, but when
+     *            checking the .class files of a Java file for example, the
+     *            .class file and all the inner classes of the Java file are
+     *            passed in the first parameter, and the corresponding .java
+     *            source file is passed here.
      * @param doc the associated document, if known, or null
      * @param fatalOnly if true, only report fatal issues (severity=error)
      * @param show if true, show the results in a {@link LintViewPart}
      * @return the job running lint in the background.
      */
-    public static Job startLint(List<? extends IResource> resources,
-            IDocument doc, boolean fatalOnly, boolean show) {
+    public static Job startLint(
+            @NonNull List<? extends IResource> resources,
+            @Nullable IResource source,
+            @Nullable IDocument doc,
+            boolean fatalOnly,
+            boolean show) {
         if (resources != null && !resources.isEmpty()) {
             resources = addLibraries(resources);
 
             cancelCurrentJobs(false);
 
-            CheckFileJob job = new CheckFileJob(resources, doc, fatalOnly);
+            CheckFileJob job = new CheckFileJob(resources, source, doc, fatalOnly);
             job.schedule();
 
             if (show) {
@@ -125,8 +145,8 @@
      */
     public static boolean runLintOnExport(Shell shell, IProject project) {
         if (AdtPrefs.getPrefs().isLintOnExport()) {
-            boolean fatal = EclipseLintRunner.runLint(Collections.singletonList(project), null,
-                    true /*fatalOnly*/);
+            boolean fatal = EclipseLintRunner.runLint(Collections.singletonList(project),
+                    null, null, true /*fatalOnly*/);
             if (fatal) {
                 MessageDialog.openWarning(shell,
                         "Export Aborted",
@@ -214,15 +234,20 @@
         /** Job family */
         private static final Object FAMILY_RUN_LINT = new Object();
         private final List<? extends IResource> mResources;
+        private final IResource mSource;
         private final IDocument mDocument;
         private LintDriver mLint;
         private boolean mFatal;
         private boolean mFatalOnly;
 
-        private CheckFileJob(List<? extends IResource> resources, IDocument doc,
+        private CheckFileJob(
+                @NonNull List<? extends IResource> resources,
+                @Nullable IResource source,
+                @Nullable IDocument doc,
                 boolean fatalOnly) {
             super("Running Android Lint");
             mResources = resources;
+            mSource = source;
             mDocument = doc;
             mFatalOnly = fatalOnly;
         }
@@ -245,36 +270,64 @@
             try {
                 monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN);
                 IssueRegistry registry = EclipseLintClient.getRegistry();
-                EnumSet<Scope> scope = Scope.ALL;
+                EnumSet<Scope> scope = null;
                 List<File> files = new ArrayList<File>(mResources.size());
                 for (IResource resource : mResources) {
                     File file = AdtUtils.getAbsolutePath(resource).toFile();
                     files.add(file);
 
-                    if (resource instanceof IProject) {
+                    if (resource instanceof IProject && mSource == null) {
                         scope = Scope.ALL;
-                    } else if (resource instanceof IFile
-                            && AdtUtils.endsWithIgnoreCase(resource.getName(), DOT_XML)) {
-                        if (resource.getName().equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
-                            scope = EnumSet.of(Scope.MANIFEST);
-                        } else {
-                            scope = Scope.RESOURCE_FILE_SCOPE;
-                        }
                     } else {
-                        return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
-                                "Only XML files are supported for single file lint", null); //$NON-NLS-1$
+                        String name = resource.getName();
+                        if (AdtUtils.endsWithIgnoreCase(name, DOT_XML)) {
+                            if (name.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
+                                scope = EnumSet.of(Scope.MANIFEST);
+                            } else {
+                                scope = Scope.RESOURCE_FILE_SCOPE;
+                            }
+                        } else if (name.endsWith(DOT_JAVA) && resource instanceof IFile) {
+                            if (scope != null) {
+                                if (!scope.contains(Scope.JAVA_FILE)) {
+                                    scope = EnumSet.copyOf(scope);
+                                    scope.add(Scope.JAVA_FILE);
+                                }
+                            } else {
+                                scope = Scope.JAVA_FILE_SCOPE;
+                            }
+                        } else if (name.endsWith(DOT_CLASS) && resource instanceof IFile) {
+                            if (scope != null) {
+                                if (!scope.contains(Scope.CLASS_FILE)) {
+                                    scope = EnumSet.copyOf(scope);
+                                    scope.add(Scope.CLASS_FILE);
+                                }
+                            } else {
+                                scope = Scope.CLASS_FILE_SCOPE;
+                            }
+                        } else {
+                            return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
+                                    "Only XML files are supported for single file lint", null); //$NON-NLS-1$
+                        }
                     }
                 }
-                if (Scope.checkSingleFile(scope)) {
+                if (scope == null) {
+                    scope = Scope.ALL;
+                }
+                if (mSource == null) {
+                    assert !Scope.checkSingleFile(scope) : scope + " with " + mResources;
+                }
+                // Check single file?
+                if (mSource != null) {
                     // Delete specific markers
-                    for (IResource resource : mResources) {
-                        IMarker[] markers = EclipseLintClient.getMarkers(resource);
-                        for (IMarker marker : markers) {
-                            String id = marker.getAttribute(MARKER_CHECKID_PROPERTY, ""); //$NON-NLS-1$
-                            Issue issue = registry.getIssue(id);
-                            if (issue == null || issue.getScope().equals(scope)) {
-                                marker.delete();
-                            }
+                    IMarker[] markers = EclipseLintClient.getMarkers(mSource);
+                    for (IMarker marker : markers) {
+                        String id = marker.getAttribute(MARKER_CHECKID_PROPERTY, ""); //$NON-NLS-1$
+                        Issue issue = registry.getIssue(id);
+                        if (issue == null) {
+                            continue;
+                        }
+                        if (issue.isAdequate(scope)) {
+                            marker.delete();
                         }
                     }
                 } else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java
new file mode 100644
index 0000000..e5fe46e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt.internal.lint;
+
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_CLASS;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAVA;
+
+import com.android.annotations.NonNull;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.swt.widgets.Display;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Delta processor for Java files, which runs single-file lints if it finds that
+ * the currently active file has been updated.
+ */
+public class LintDeltaProcessor implements Runnable {
+    private List<IResource> mFiles;
+    private IFile mActiveFile;
+
+    private LintDeltaProcessor() {
+    }
+
+    /**
+     * Creates a new {@link LintDeltaProcessor}
+     *
+     * @return a visitor
+     */
+    @NonNull
+    public static LintDeltaProcessor create() {
+        return new LintDeltaProcessor();
+    }
+
+    /**
+     * Process the given delta: update lint on any Java source and class files found.
+     *
+     * @param delta the delta describing recently changed files
+     */
+    public void process(@NonNull IResourceDelta delta)  {
+        // Get the active editor file, if any
+        Display display = AdtPlugin.getDisplay();
+        if (display == null) {
+            return;
+        }
+        if (display.getThread() != Thread.currentThread()) {
+            display.syncExec(this);
+        } else {
+            run();
+        }
+
+        if (mActiveFile == null || !mActiveFile.getName().endsWith(DOT_JAVA)) {
+            return;
+        }
+
+        mFiles = new ArrayList<IResource>();
+        gatherFiles(delta);
+
+        if (!mFiles.isEmpty()) {
+            EclipseLintRunner.startLint(mFiles, mActiveFile, null,
+                    false /*fatalOnly*/, false /*show*/);
+        }
+    }
+
+    /**
+     * Collect .java and .class files to be run in lint. Only collects files
+     * that match the active editor.
+     */
+    private void gatherFiles(@NonNull IResourceDelta delta) {
+        IResource resource = delta.getResource();
+        String name = resource.getName();
+        if (name.endsWith(DOT_JAVA)) {
+            if (resource.equals(mActiveFile)) {
+                mFiles.add(resource);
+            }
+        } else if (name.endsWith(DOT_CLASS)) {
+            // Make sure this class corresponds to the .java file, meaning it has
+            // the same basename, or that it is an inner class of a class that
+            // matches the same basename. (We could potentially make sure the package
+            // names match too, but it's unlikely that the class names match without a
+            // package match, and there's no harm in including some extra classes here,
+            // since lint will resolve full paths and the resource markers won't go
+            // to the wrong place, we simply end up analyzing some extra files.)
+            String className = mActiveFile.getName();
+            if (name.regionMatches(0, className, 0, className.length() - DOT_JAVA.length())) {
+                if (name.length() == className.length() - DOT_JAVA.length() + DOT_CLASS.length()
+                        || name.charAt(className.length() - DOT_JAVA.length()) == '$') {
+                    mFiles.add(resource);
+                }
+            }
+        } else {
+            IResourceDelta[] children = delta.getAffectedChildren();
+            if (children != null && children.length > 0) {
+                for (IResourceDelta d : children) {
+                    gatherFiles(d);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void run() {
+        // Get the active file: this must be run on the GUI thread
+        mActiveFile = AdtUtils.getActiveFile();
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
index 478a07c..c81c0fc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
@@ -262,6 +262,8 @@
             }
 
             try {
+                // See if the current active file is the one containing this marker;
+                // if so we can take some shortcuts
                 IEditorPart activeEditor = AdtUtils.getActiveEditor();
                 IEditorPart part = null;
                 if (activeEditor != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
index c40f3b7..561c5fd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
@@ -48,7 +48,6 @@
 import org.eclipse.jface.viewers.StyledString;
 import org.eclipse.jface.viewers.TableLayout;
 import org.eclipse.jface.viewers.TreeNodeContentProvider;
-import org.eclipse.jface.viewers.TreePath;
 import org.eclipse.jface.viewers.TreeViewer;
 import org.eclipse.jface.viewers.TreeViewerColumn;
 import org.eclipse.jface.viewers.Viewer;
@@ -64,6 +63,8 @@
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TreeEvent;
+import org.eclipse.swt.events.TreeListener;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
@@ -83,6 +84,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -103,6 +105,7 @@
     private final IWorkbenchPartSite mSite;
     private final TreeViewer mTreeViewer;
     private final Tree mTree;
+    private Set<String> mExpandedIds;
     private ContentProvider mContentProvider;
     private String mSelectedId;
     private List<? extends IResource> mResources;
@@ -179,6 +182,34 @@
                 }
             }
         });
+        mTree.addTreeListener(new TreeListener() {
+            @Override
+            public void treeExpanded(TreeEvent e) {
+                Object data = e.item.getData();
+                if (data instanceof IMarker) {
+                    String id = EclipseLintClient.getId((IMarker) data);
+                    if (id != null) {
+                        if (mExpandedIds == null) {
+                            mExpandedIds = new HashSet<String>();
+                        }
+                        mExpandedIds.add(id);
+                    }
+                }
+            }
+
+            @Override
+            public void treeCollapsed(TreeEvent e) {
+                if (mExpandedIds != null) {
+                    Object data = e.item.getData();
+                    if (data instanceof IMarker) {
+                        String id = EclipseLintClient.getId((IMarker) data);
+                        if (id != null) {
+                            mExpandedIds.remove(id);
+                        }
+                    }
+                }
+            }
+        });
     }
 
     private boolean treePainted;
@@ -479,9 +510,6 @@
                 return Status.CANCEL_STATUS;
             }
 
-            Object[] expandedElements = mTreeViewer.getExpandedElements();
-            TreePath[] expandedTreePaths = mTreeViewer.getExpandedTreePaths();
-
             mTreeViewer.setInput(null);
             List<IMarker> markerList = getMarkers();
             if (markerList.size() == 0) {
@@ -504,8 +532,21 @@
             mTreeViewer.setInput(markerList);
             mTreeViewer.refresh();
 
-            mTreeViewer.setExpandedElements(expandedElements);
-            mTreeViewer.setExpandedTreePaths(expandedTreePaths);
+            if (mExpandedIds != null) {
+                List<IMarker> expanded = new ArrayList<IMarker>(mExpandedIds.size());
+                IMarker[] topMarkers = mContentProvider.getTopMarkers();
+                if (topMarkers != null) {
+                    for (IMarker marker : topMarkers) {
+                        String id = EclipseLintClient.getId(marker);
+                        if (id != null && mExpandedIds.contains(id)) {
+                            expanded.add(marker);
+                        }
+                    }
+                }
+                if (!expanded.isEmpty()) {
+                    mTreeViewer.setExpandedElements(expanded.toArray());
+                }
+            }
 
             if (mSelectedId != null) {
                 IMarker[] topMarkers = mContentProvider.getTopMarkers();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
index e9f94cc..39c6d25 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
@@ -474,7 +474,7 @@
                         if (resources == null) {
                             return;
                         }
-                        Job job = EclipseLintRunner.startLint(resources, null,
+                        Job job = EclipseLintRunner.startLint(resources, null, null,
                                 false /*fatalOnly*/, false /*show*/);
                         if (job != null && workbench != null) {
                             job.addJobChangeListener(LintViewPart.this);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
index e77e910..18f3db3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
@@ -76,7 +76,7 @@
         List<IProject> projects = getProjects(mSelection, true /* warn */);
 
         if (!projects.isEmpty()) {
-            EclipseLintRunner.startLint(projects, null, false /*fatalOnly*/, true /*show*/);
+            EclipseLintRunner.startLint(projects, null, null, false /*fatalOnly*/, true /*show*/);
         } else {
             MessageDialog.openWarning(AdtPlugin.getDisplay().getActiveShell(), "Lint",
                     "Could not run Lint: Select a project first.");
@@ -210,7 +210,7 @@
 
         ISharedImages images = PlatformUI.getWorkbench().getSharedImages();
         ImageDescriptor clear = images.getImageDescriptor(ISharedImages.IMG_ELCL_REMOVEALL);
-        LintMenuAction clearAction = new LintMenuAction("Clear Lint Markers", clear, true, null);
+        LintMenuAction clearAction = new LintMenuAction("Clear Lint Warnings", clear, true, null);
         addSeparator();
         addAction(clearAction);
 
@@ -271,7 +271,8 @@
             if (mClear) {
                 EclipseLintClient.clearMarkers(resources);
             } else {
-                EclipseLintRunner.startLint(resources, null, false /*fatalOnly*/, true /*show*/);
+                EclipseLintRunner.startLint(resources, null, null, false /*fatalOnly*/,
+                        true /*show*/);
             }
         }
     }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
index bd2423e..827f8a4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
@@ -378,7 +378,7 @@
                     }
                 }
 
-                EclipseLintRunner.startLint(androidProjects, null, false /*fatalOnly*/,
+                EclipseLintRunner.startLint(androidProjects, null,  null, false /*fatalOnly*/,
                         true /*show*/);
             }
         }
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
index ef47494..5d38bf1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
@@ -172,6 +172,10 @@
 
         // check if the project has a valid target.
         ProjectState state = Sdk.getProjectState(iProject);
+        if (state == null) {
+            // getProjectState should already have logged an error. Just bail out.
+            return null;
+        }
 
         /*
          * At this point we're going to gather a list of all that need to go in the
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java
index 584d49f..212263a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java
@@ -143,7 +143,9 @@
                     mPropertiesWorkingCopy.save();
 
                     IResource projectProp = mProject.findMember(SdkConstants.FN_PROJECT_PROPERTIES);
-                    projectProp.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
+                    if (projectProp != null) {
+                        projectProp.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
+                    }
                 } catch (Exception e) {
                     String msg = String.format(
                             "Failed to save %1$s for project %2$s",
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
index a87f992..57c016e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
@@ -19,6 +19,7 @@
 import static com.android.AndroidConstants.FD_RES_VALUES;
 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
 import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_RESOURCE_REF;
 import static com.android.ide.common.resources.ResourceResolver.PREFIX_STYLE;
 import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG;
 import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
@@ -507,11 +508,30 @@
             style = style.substring(PREFIX_STYLE.length());
         } else if (style.startsWith(PREFIX_ANDROID_STYLE)) {
             style = style.substring(PREFIX_ANDROID_STYLE.length());
+        } else if (style.startsWith(PREFIX_RESOURCE_REF)) {
+            // @package:style/foo
+            int index = style.indexOf('/');
+            if (index != -1) {
+                style = style.substring(index + 1);
+            }
         }
         return style;
     }
 
     /**
+     * Returns true if the given style represents a project theme
+     *
+     * @param style a theme style string
+     * @return true if the style string represents a project theme, as opposed
+     *         to a framework theme
+     */
+    public static boolean isProjectStyle(String style) {
+        assert style.startsWith(PREFIX_STYLE) || style.startsWith(PREFIX_ANDROID_STYLE) : style;
+
+        return style.startsWith(PREFIX_STYLE);
+    }
+
+    /**
      * Returns the layout resource name for the given layout file, e.g. for
      * /res/layout/foo.xml returns foo.
      *
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
index 2a1fba9..4f9bb09 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
@@ -442,6 +442,10 @@
 
                             // delete the old file.
                             ProjectProperties.delete(projectLocation, PropertyType.LEGACY_DEFAULT);
+
+                            // make sure to use the new properties
+                            properties = ProjectProperties.load(projectLocation,
+                                    PropertyType.PROJECT);
                         } catch (Exception e) {
                             AdtPlugin.log(IStatus.ERROR,
                                     "Failed to rename properties file to %1$s for project '%s2$'",
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitor.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitor.java
index f9c94a7..65ddbd4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitor.java
@@ -19,7 +19,7 @@
 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
 import com.android.ddmlib.IDevice;
 import com.android.ddmlib.Log.LogLevel;
-import com.android.ddmuilib.logcat.ILogCatMessageEventListener;
+import com.android.ddmuilib.logcat.ILogCatBufferChangeListener;
 import com.android.ddmuilib.logcat.LogCatMessage;
 import com.android.ddmuilib.logcat.LogCatReceiver;
 import com.android.ddmuilib.logcat.LogCatReceiverFactory;
@@ -28,7 +28,9 @@
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.jface.window.Window;
 import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
 import org.eclipse.ui.IViewPart;
 import org.eclipse.ui.IWorkbenchPage;
 import org.eclipse.ui.IWorkbenchWindow;
@@ -38,6 +40,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * LogCatMonitor helps in monitoring the logcat output from a set of devices.
@@ -45,15 +48,27 @@
  * if any message is deemed important.
  */
 public class LogCatMonitor {
-    public static final String AUTO_MONITOR_PREFKEY = "ddms.logcat.automonitor";
+    public static final String AUTO_MONITOR_PREFKEY = "ddms.logcat.automonitor"; //$NON-NLS-1$
+    public static final String AUTO_MONITOR_LOGLEVEL = "ddms.logcat.auotmonitor.level"; //$NON-NLS-1$
+    private static final String AUTO_MONITOR_PROMPT_SHOWN = "ddms.logcat.automonitor.userprompt"; //$NON-NLS-1$
 
     private IPreferenceStore mPrefStore;
     private Map<String, DeviceData> mMonitoredDevices;
     private IDebuggerConnector[] mConnectors;
 
+    private int mMinMessagePriority;
+
+    /**
+     * Flag that controls when the logcat stream is checked. This flag is set when the user
+     * performs a launch, and is reset as soon as the logcat view is displayed.
+     */
+    final AtomicBoolean mMonitorEnabled = new AtomicBoolean(false);
+
     public LogCatMonitor(IDebuggerConnector[] debuggerConnectors, IPreferenceStore prefStore) {
         mConnectors = debuggerConnectors;
         mPrefStore = prefStore;
+        mMinMessagePriority =
+                LogLevel.getByString(mPrefStore.getString(AUTO_MONITOR_LOGLEVEL)).getPriority();
 
         mMonitoredDevices = new HashMap<String, DeviceData>();
 
@@ -79,6 +94,9 @@
                 if (AUTO_MONITOR_PREFKEY.equals(event.getProperty())
                         && event.getNewValue().equals(false)) {
                     unmonitorAllDevices();
+                } else if (AUTO_MONITOR_LOGLEVEL.equals(event.getProperty())) {
+                    mMinMessagePriority =
+                            LogLevel.getByString((String) event.getNewValue()).getPriority();
                 }
             }
         });
@@ -98,7 +116,7 @@
             return;
         }
 
-        data.receiver.removeMessageReceivedEventListener(data.messageEventListener);
+        data.receiver.removeMessageReceivedEventListener(data.bufferChangeListener);
     }
 
     public void monitorDevice(final IDevice device) {
@@ -107,16 +125,19 @@
             return;
         }
 
+        mMonitorEnabled.set(true);
+
         if (mMonitoredDevices.keySet().contains(device.getSerialNumber())) {
             // the device is already monitored
             return;
         }
 
         LogCatReceiver r = LogCatReceiverFactory.INSTANCE.newReceiver(device, mPrefStore);
-        ILogCatMessageEventListener l = new ILogCatMessageEventListener() {
+        ILogCatBufferChangeListener l = new ILogCatBufferChangeListener() {
             @Override
-            public void messageReceived(List<LogCatMessage> receivedMessages) {
-                checkMessages(receivedMessages, device);
+            public void bufferChanged(List<LogCatMessage> addedMessages,
+                    List<LogCatMessage> deletedMessages) {
+                checkMessages(addedMessages, device);
             }
         };
         r.addMessageReceivedEventListener(l);
@@ -125,12 +146,20 @@
     }
 
     private void checkMessages(List<LogCatMessage> receivedMessages, IDevice device) {
+        if (!mMonitorEnabled.get()) {
+            return;
+        }
+
         // check the received list of messages to see if any of them are
         // significant enough to be seen by the user. If so, activate the logcat view
         // to display those messages
         for (LogCatMessage m : receivedMessages) {
             if (isImportantMessage(m)) {
                 focusLogCatView(device, m.getAppName());
+
+                // now that logcat view is active, no need to check messages until the next
+                // time user launches an application.
+                mMonitorEnabled.set(false);
                 break;
             }
         }
@@ -141,7 +170,7 @@
      * it is of severity level error or higher, and it belongs to an app currently in the workspace.
      */
     private boolean isImportantMessage(LogCatMessage m) {
-        if (m.getLogLevel().getPriority() < LogLevel.ERROR.getPriority()) {
+        if (m.getLogLevel().getPriority() < mMinMessagePriority) {
             return false;
         }
 
@@ -169,6 +198,15 @@
                     return;
                 }
 
+                // if the logcat view is not visible, then prompt the user once to set
+                // logcat monitoring preferences
+                if (!isLogCatViewVisible(page)) {
+                    boolean showLogCatView = promptUserOnce(page.getWorkbenchWindow().getShell());
+                    if (!showLogCatView) {
+                        return;
+                    }
+                }
+
                 // display view
                 final LogCatView v = displayLogCatView(page);
                 if (v == null) {
@@ -182,6 +220,11 @@
                 v.selectTransientAppFilter(appName);
             }
 
+            private boolean isLogCatViewVisible(IWorkbenchPage page) {
+                IViewPart view = page.findView(LogCatView.ID);
+                return view != null && page.isPartVisible(view);
+            }
+
             private LogCatView displayLogCatView(IWorkbenchPage page) {
                 // if the view is already in the page, just bring it to the front
                 // without giving it focus.
@@ -200,16 +243,35 @@
                     return null;
                 }
             }
+
+            private boolean promptUserOnce(Shell shell) {
+                // see if this prompt was already displayed
+                boolean promptShown = mPrefStore.getBoolean(AUTO_MONITOR_PROMPT_SHOWN);
+                if (promptShown) {
+                    return mPrefStore.getBoolean(AUTO_MONITOR_PREFKEY);
+                }
+
+                LogCatMonitorDialog dlg = new LogCatMonitorDialog(shell);
+                int r = dlg.open();
+
+                // save preference indicating that this dialog has been displayed once
+                mPrefStore.setValue(AUTO_MONITOR_PROMPT_SHOWN, true);
+                mPrefStore.setValue(AUTO_MONITOR_PREFKEY, dlg.shouldMonitor());
+                mPrefStore.setValue(AUTO_MONITOR_LOGLEVEL, dlg.getMinimumPriority());
+
+                return r == Window.OK && dlg.shouldMonitor();
+            }
+
         });
     }
 
     private static class DeviceData {
         public final LogCatReceiver receiver;
-        public final ILogCatMessageEventListener messageEventListener;
+        public final ILogCatBufferChangeListener bufferChangeListener;
 
-        public DeviceData(LogCatReceiver r, ILogCatMessageEventListener l) {
+        public DeviceData(LogCatReceiver r, ILogCatBufferChangeListener l) {
             receiver = r;
-            messageEventListener = l;
+            bufferChangeListener = l;
         }
     }
 }
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitorDialog.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitorDialog.java
new file mode 100644
index 0000000..6194a0d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/LogCatMonitorDialog.java
@@ -0,0 +1,118 @@
+/*
+ * 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.ide.eclipse.ddms;
+
+import com.android.ddmlib.Log.LogLevel;
+
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+
+public class LogCatMonitorDialog extends TitleAreaDialog {
+    private static final String TITLE = "Auto Monitor Logcat";
+    private static final String DEFAULT_MESSAGE =
+            "Would you like ADT to automatically monitor logcat \n" +
+            "output for messages from applications in the workspace?";
+
+    private boolean mShouldMonitor = true;
+
+    private static final String[] LOG_PRIORITIES = new String[] {
+        LogLevel.VERBOSE.getStringValue(),
+        LogLevel.DEBUG.getStringValue(),
+        LogLevel.INFO.getStringValue(),
+        LogLevel.WARN.getStringValue(),
+        LogLevel.ERROR.getStringValue(),
+        LogLevel.ASSERT.getStringValue(),
+    };
+    private static final int ERROR_PRIORITY_INDEX = 4;
+
+    private String mMinimumLogPriority = LOG_PRIORITIES[ERROR_PRIORITY_INDEX];
+
+    public LogCatMonitorDialog(Shell parentShell) {
+        super(parentShell);
+        setHelpAvailable(false);
+    }
+
+    @Override
+    protected Control createDialogArea(Composite parent) {
+        setTitle(TITLE);
+        setMessage(DEFAULT_MESSAGE);
+
+        parent = (Composite) super.createDialogArea(parent);
+        Composite c = new Composite(parent, SWT.BORDER);
+        c.setLayout(new GridLayout(2, false));
+        GridData gd_c = new GridData(GridData.FILL_BOTH);
+        gd_c.grabExcessVerticalSpace = false;
+        gd_c.grabExcessHorizontalSpace = false;
+        c.setLayoutData(gd_c);
+
+        final Button disableButton = new Button(c, SWT.RADIO);
+        disableButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+        disableButton.setText("No, do not monitor logcat output.");
+
+        final Button enableButton = new Button(c, SWT.RADIO);
+        enableButton.setText("Yes, monitor logcat and display logcat view if there are\n" +
+                "messages with priority higher than:");
+        enableButton.setSelection(true);
+
+        final Combo levelCombo = new Combo(c, SWT.READ_ONLY | SWT.DROP_DOWN);
+        levelCombo.setItems(LOG_PRIORITIES);
+        levelCombo.select(ERROR_PRIORITY_INDEX);
+
+        SelectionListener s = new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                if (e.getSource() == enableButton) {
+                    mShouldMonitor = enableButton.getSelection();
+                    levelCombo.setEnabled(mShouldMonitor);
+                } else if (e.getSource() == levelCombo) {
+                    mMinimumLogPriority = LOG_PRIORITIES[levelCombo.getSelectionIndex()];
+                }
+            }
+        };
+
+        levelCombo.addSelectionListener(s);
+        enableButton.addSelectionListener(s);
+
+        return parent;
+    }
+
+    @Override
+    protected void createButtonsForButtonBar(Composite parent) {
+        // Only need OK button
+        createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL,
+                true);
+    }
+
+    public boolean shouldMonitor() {
+        return mShouldMonitor;
+    }
+
+    public String getMinimumPriority() {
+        return mMinimumLogPriority;
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/Messages.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/Messages.java
index 45602e6..576b598 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/Messages.java
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/Messages.java
@@ -80,6 +80,7 @@
     public static String LogCatPreferencePage_Switch_Perspective;
     public static String LogCatPreferencePage_Switch_To;
     public static String LogCatPreferencePage_AutoMonitorLogcat;
+    public static String LogCatPreferencePage_SessionFilterLogLevel;
     public static String LogCatView_Clear_Log;
     public static String LogCatView_Copy;
     public static String LogCatView_Create_Filter;
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/messages.properties b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/messages.properties
index 07c4f16..3698ebc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/messages.properties
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/i18n/messages.properties
@@ -73,7 +73,8 @@
 LogCatPreferencePage_MaxMessages=Maximum number of logcat messages to buffer:
 LogCatPreferencePage_Switch_Perspective=Switch Perspective
 LogCatPreferencePage_Switch_To=Switch to:
-LogCatPreferencePage_AutoMonitorLogcat=Display logcat view when there are messages from an application in the workspace
+LogCatPreferencePage_AutoMonitorLogcat=Monitor logcat for messages from applications in workspace
+LogCatPreferencePage_SessionFilterLogLevel=Show logcat view if message priority is atleast:
 LogCatView_Clear_Log=Clear Log
 LogCatView_Copy=Copy
 LogCatView_Create_Filter=Create Filter
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java
index b315ee9..9971e18 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java
@@ -16,6 +16,7 @@
 
 package com.android.ide.eclipse.ddms.preferences;
 
+import com.android.ddmlib.Log.LogLevel;
 import com.android.ddmuilib.logcat.LogCatMessageList;
 import com.android.ddmuilib.logcat.LogCatPanel;
 import com.android.ide.eclipse.base.InstallDetails;
@@ -43,6 +44,7 @@
     private ComboFieldEditor mWhichPerspective;
     private IntegerFieldEditor mMaxMessages;
     private BooleanFieldEditor mAutoMonitorLogcat;
+    private ComboFieldEditor mAutoMonitorLogcatLevel;
 
     public LogCatPreferencePage() {
         super(GRID);
@@ -92,6 +94,22 @@
                 Messages.LogCatPreferencePage_AutoMonitorLogcat,
                 getFieldEditorParent());
         addField(mAutoMonitorLogcat);
+
+        mAutoMonitorLogcatLevel = new ComboFieldEditor(LogCatMonitor.AUTO_MONITOR_LOGLEVEL,
+                Messages.LogCatPreferencePage_SessionFilterLogLevel,
+                new String[][] {
+                    { LogLevel.VERBOSE.toString(), LogLevel.VERBOSE.getStringValue() },
+                    { LogLevel.DEBUG.toString(), LogLevel.DEBUG.getStringValue() },
+                    { LogLevel.INFO.toString(), LogLevel.INFO.getStringValue() },
+                    { LogLevel.WARN.toString(), LogLevel.WARN.getStringValue() },
+                    { LogLevel.ERROR.toString(), LogLevel.ERROR.getStringValue() },
+                    { LogLevel.ASSERT.toString(), LogLevel.ASSERT.getStringValue() },
+                },
+                getFieldEditorParent());
+        mAutoMonitorLogcatLevel.setEnabled(
+                getPreferenceStore().getBoolean(LogCatMonitor.AUTO_MONITOR_PREFKEY),
+                getFieldEditorParent());
+        addField(mAutoMonitorLogcatLevel);
     }
 
     @Override
@@ -101,8 +119,11 @@
     @Override
     public void propertyChange(PropertyChangeEvent event) {
         if (event.getSource().equals(mSwitchPerspective)) {
-            mWhichPerspective.setEnabled(mSwitchPerspective.getBooleanValue()
-                    , getFieldEditorParent());
+            mWhichPerspective.setEnabled(mSwitchPerspective.getBooleanValue(),
+                    getFieldEditorParent());
+        } else if (event.getSource().equals(mAutoMonitorLogcat)) {
+            mAutoMonitorLogcatLevel.setEnabled(mAutoMonitorLogcat.getBooleanValue(),
+                    getFieldEditorParent());
         }
     }
 
@@ -113,5 +134,8 @@
 
         mMaxMessages.setStringValue(
                 Integer.toString(LogCatMessageList.MAX_MESSAGES_DEFAULT));
+
+        mAutoMonitorLogcatLevel.setEnabled(mAutoMonitorLogcat.getBooleanValue(),
+                getFieldEditorParent());
     }
 }
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..2415e20 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
@@ -21,8 +21,11 @@
 import com.android.ide.eclipse.ddms.views.DeviceView.HProfHandler;
 import com.android.ide.eclipse.ddms.views.LogCatView;
 import com.android.ddmlib.DdmPreferences;
+import com.android.ddmlib.Log.LogLevel;
 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 +109,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);
 
@@ -116,6 +127,7 @@
         store.setDefault(ATTR_PERSPECTIVE_ID, LogCatView.DEFAULT_PERSPECTIVE_ID);
 
         store.setDefault(LogCatMonitor.AUTO_MONITOR_PREFKEY, true);
+        store.setDefault(LogCatMonitor.AUTO_MONITOR_LOGLEVEL, LogLevel.VERBOSE.getStringValue());
     }
 
     /**
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
index 9b7544d..2b07680 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java
@@ -18,6 +18,7 @@
 
 import com.android.ddmlib.AdbCommandRejectedException;
 import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.Client;
 import com.android.ddmlib.IDevice;
 import com.android.ddmlib.IDevice.DeviceUnixSocketNamespace;
 import com.android.ddmlib.IShellOutputReceiver;
@@ -168,8 +169,10 @@
     private void startActivity(IDevice device, String appName)
             throws TimeoutException, AdbCommandRejectedException,
             ShellCommandUnresponsiveException, IOException, InterruptedException {
+        killApp(device, appName); // kill app if it is already running
+
         String startAppCmd = String.format(
-                "am start -S --opengl-trace %s -a android.intent.action.MAIN -c android.intent.category.LAUNCHER", //$NON-NLS-1$
+                "am start --opengl-trace %s -a android.intent.action.MAIN -c android.intent.category.LAUNCHER", //$NON-NLS-1$
                 appName);
 
         Semaphore launchCompletionSempahore = new Semaphore(0);
@@ -187,6 +190,13 @@
         }
     }
 
+    private void killApp(IDevice device, String appName) {
+        Client client = device.getClient(appName);
+        if (client != null) {
+            client.kill();
+        }
+    }
+
     private void setupForwarding(IDevice device, int i)
             throws TimeoutException, AdbCommandRejectedException, IOException {
         device.createForward(i, GLTRACE_UDS, DeviceUnixSocketNamespace.ABSTRACT);
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/ProtoBufUtils.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/ProtoBufUtils.java
index 7165e63..ed6a277 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/ProtoBufUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/ProtoBufUtils.java
@@ -30,6 +30,10 @@
         int width = glMsg.getFb().getWidth();
         int height = glMsg.getFb().getHeight();
 
+        if (width * height == 0) {
+            return null;
+        }
+
         byte[] compressed = glMsg.getFb().getContents(0).toByteArray();
         byte[] uncompressed = new byte[width * height * 4];
 
@@ -59,7 +63,12 @@
             return null;
         }
 
-        return new Image(display, getImageData(glMsg));
+        ImageData imageData = getImageData(glMsg);
+        if (imageData == null) {
+            return null;
+        }
+
+        return new Image(display, imageData);
     }
 
     /**
@@ -72,6 +81,10 @@
         }
 
         ImageData imageData = getImageData(glMsg);
+        if (imageData == null) {
+            return null;
+        }
+
         return new Image(display, imageData.scaledTo(width, height));
     }
 }
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/BufferSubDataTransform.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/BufferSubDataTransform.java
index 844c7dc..393f1b9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/BufferSubDataTransform.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/BufferSubDataTransform.java
@@ -44,12 +44,17 @@
         IGLProperty property = mAccessor.getProperty(state);
         mOldData = (byte[]) property.getValue();
 
-        if (mNewData != null) {
+        if (mOldData != null) {
             mNewData = new byte[mOldData.length];
             ByteBuffer bb = ByteBuffer.wrap(mNewData);
-            bb.put(mOldData); // copy all of the old buffer
+
+            // copy all of the old buffer
+            bb.put(mOldData);
             bb.rewind();
-            bb.put(mSubData, mOffset, mSubData.length); // update with the sub buffer data
+
+            // update with the sub buffer data at specified offset
+            bb.position(mOffset);
+            bb.put(mSubData);
         }
 
         property.setValue(mNewData);
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java
index 576e87b..df92ea7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java
@@ -425,7 +425,7 @@
         IStateTransform transform = new BufferSubDataTransform(
                 new CurrentVboPropertyAccessor(msg.getContextId(),
                         target,
-                        GLStateType.BUFFER_SIZE),
+                        GLStateType.BUFFER_DATA),
                 offset, data);
 
         return Collections.singletonList(transform);
diff --git a/eclipse/plugins/com.android.ide.eclipse.monitor/build.properties b/eclipse/plugins/com.android.ide.eclipse.monitor/build.properties
index 97144a1..44471de 100644
--- a/eclipse/plugins/com.android.ide.eclipse.monitor/build.properties
+++ b/eclipse/plugins/com.android.ide.eclipse.monitor/build.properties
@@ -6,4 +6,5 @@
                libs/,\
                plugin_customization.ini,\
                plugin.properties,\
-               images/
+               images/,\
+               splash.bmp
diff --git a/eclipse/plugins/com.android.ide.eclipse.monitor/splash.bmp b/eclipse/plugins/com.android.ide.eclipse.monitor/splash.bmp
new file mode 100644
index 0000000..54880bf
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.monitor/splash.bmp
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
index 177662d..9eeb2ea 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
@@ -114,4 +114,14 @@
         assertSame("Foo", AdtUtils.capitalize("Foo"));
         assertNull(null, AdtUtils.capitalize(null));
     }
+
+    public void testStripSuffix() {
+        assertEquals("Foo", AdtUtils.stripSuffix("Foo", ""));
+        assertEquals("Fo", AdtUtils.stripSuffix("Foo", "o"));
+        assertEquals("F", AdtUtils.stripSuffix("Fo", "o"));
+        assertEquals("", AdtUtils.stripSuffix("Foo", "Foo"));
+        assertEquals("LinearLayout_Layout",
+                AdtUtils.stripSuffix("LinearLayout_LayoutParams", "Params"));
+        assertEquals("Foo", AdtUtils.stripSuffix("Foo", "Bar"));
+    }
 }
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java
index 4bd0bba..9e9c734 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java
@@ -157,12 +157,6 @@
         assertEquals(0xFFFF0000, crop.getRGB(49, 49));
     }
 
-    public void testNullOk() throws Exception {
-        ImageUtils.cropBlank(null, null);
-        ImageUtils.cropColor(null, 0, null);
-    }
-
-
     public void testNothingTodo() throws Exception {
         BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE);
         Graphics g = image.getGraphics();
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java
index 48765ec..0b498e5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java
@@ -32,6 +32,7 @@
 /**
  * Test ResourceHelper
  */
+@SuppressWarnings("javadoc")
 public class ResourceHelperTest extends TestCase {
 
     /**
@@ -188,5 +189,11 @@
         assertEquals("Foo", ResourceHelper.styleToTheme("Foo"));
         assertEquals("Theme", ResourceHelper.styleToTheme("@android:style/Theme"));
         assertEquals("LocalTheme", ResourceHelper.styleToTheme("@style/LocalTheme"));
+        assertEquals("LocalTheme", ResourceHelper.styleToTheme("@foo.bar/style/LocalTheme"));
+    }
+
+    public void testIsProjectStyle() throws Exception {
+        assertTrue(ResourceHelper.isProjectStyle("@android:style/Theme"));
+        assertFalse(ResourceHelper.isProjectStyle("@style/LocalTheme"));
     }
 }
diff --git a/eclipse/scripts/create_all_symlinks.sh b/eclipse/scripts/create_all_symlinks.sh
index 3ab01d3..0e11631 100755
--- a/eclipse/scripts/create_all_symlinks.sh
+++ b/eclipse/scripts/create_all_symlinks.sh
@@ -126,7 +126,7 @@
 ADT_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.adt/libs"
 ADT_LIBS="layoutlib_api lint_api lint_checks ide_common rule_api ninepatch sdkuilib assetstudio propertysheet"
 ADT_PREBUILTS="\
-    prebuilt/common/kxml2/kxml2-2.3.0.jar \
+    prebuilts/misc/common/kxml2/kxml2-2.3.0.jar \
     prebuilts/tools/common/asm-tools/asm-4.0.jar \
     prebuilts/tools/common/asm-tools/asm-tree-4.0.jar \
     prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar"
@@ -152,7 +152,7 @@
 
 TEST_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.tests"
 TEST_LIBS="easymock"
-TEST_PREBUILTS="prebuilt/common/kxml2/kxml2-2.3.0.jar"
+TEST_PREBUILTS="prebuilts/misc/common/kxml2/kxml2-2.3.0.jar"
 
 LIBS="$LIBS $TEST_LIBS"
 CP_FILES="$CP_FILES @:$TEST_DEST $TEST_LIBS $TEST_PREBUILTS"
diff --git a/emulator/opengl/host/include/libOpenglRender/render_api.h b/emulator/opengl/host/include/libOpenglRender/render_api.h
index 9c76b3e..4fe54f4 100644
--- a/emulator/opengl/host/include/libOpenglRender/render_api.h
+++ b/emulator/opengl/host/include/libOpenglRender/render_api.h
@@ -16,31 +16,85 @@
 #ifndef _OPENGL_RENDERER_RENDER_API_H
 #define _OPENGL_RENDERER_RENDER_API_H
 
+/* This header and its declarations must be usable from C code.
+ *
+ * If RENDER_API_NO_PROTOTYPES is #defined before including this header, only
+ * the interface function pointer types will be declared, not the prototypes.
+ * This allows the client to use those names for its function pointer variables.
+ *
+ * All interfaces which can fail return an int, with zero indicating failure
+ * and anything else indicating success.
+ */
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 #include "render_api_platform_types.h"
 
-/* If a function with this signature is passed to initOpenGLRenderer(),
- * it will be called by the renderer just before each new frame is displayed,
- * providing a copy of the framebuffer contents.
+#if defined(RENDER_API_NO_PROTOTYPES)
+#define DECL(ret, name, args) \
+	typedef ret (* name##Fn) args
+#else
+#define DECL(ret, name, args) \
+	typedef ret (* name##Fn) args ; \
+	ret name args
+#endif
+
+/* initLibrary - initialize the library and tries to load the corresponding
+ *     GLES translator libraries. This function must be called before anything
+ *     else to ensure that everything works. If it returns an error, then
+ *     you cannot use the library at all (this can happen under certain
+ *     environments where the desktop GL libraries are not available)
+ */
+DECL(int, initLibrary, (void));
+
+/* list of constants to be passed to setStreamMode */
+#define STREAM_MODE_DEFAULT   0
+#define STREAM_MODE_TCP       1
+#define STREAM_MODE_UNIX      2
+#define STREAM_MODE_PIPE      3
+
+/* Change the stream mode. This must be called before initOpenGLRenderer */
+DECL(int, setStreamMode, (int mode));
+
+/* initOpenGLRenderer - initialize the OpenGL renderer process.
+ *     portNum is the tcp port number the renderer is listening to.
+ *     width and height are the framebuffer dimensions that will be
+ *     reported to the guest display driver.
+ *
+ * This function is *NOT* thread safe and should be called first
+ * to initialize the renderer after initLibrary().
+ */
+DECL(int, initOpenGLRenderer, (int width, int height, int portNum));
+
+/* getHardwareStrings - describe the GPU hardware and driver.
+ *    The underlying GL's vendor/renderer/version strings are returned to the
+ *    caller. The pointers become invalid after a call to stopOpenGLRenderer().
+ */
+DECL(void, getHardwareStrings, (const char** vendor, const char** renderer,
+		const char** version));
+
+/* A per-frame callback can be registered with setPostCallback(); to remove it
+ * pass NULL for both parameters. While a callback is registered, the renderer
+ * will call it just before each new frame is displayed, providing a copy of
+ * the framebuffer contents.
  *
  * The callback will be called from one of the renderer's threads, so will
  * probably need synchronization on any data structures it modifies. The
- * pixels buffer may be overwritten as soon as the callback returns; if it needs
- * the pixels afterwards it must copy them.
+ * pixels buffer may be overwritten as soon as the callback returns; if it
+ * needs the pixels afterwards it must copy them.
  *
  * The pixels buffer is intentionally not const: the callback may modify the
- * data without copying to another buffer if it wants, e.g. in-place RGBA to RGB
- * conversion, or in-place y-inversion.
+ * data without copying to another buffer if it wants, e.g. in-place RGBA to
+ * RGB conversion, or in-place y-inversion.
  *
  * Parameters are:
  *   context        The pointer optionally provided when the callback was
  *                  registered. The client can use this to pass whatever
  *                  information it wants to the callback.
- *   width, height  Dimensions of the image, in pixels. Rows are tightly packed;
- *                  there is no inter-row padding.
+ *   width, height  Dimensions of the image, in pixels. Rows are tightly
+ *                  packed; there is no inter-row padding.
  *   ydir           Indicates row order: 1 means top-to-bottom order, -1 means
  *                  bottom-to-top order.
  *   format, type   Format and type GL enums, as used in glTexImage2D() or
@@ -53,81 +107,43 @@
  */
 typedef void (*OnPostFn)(void* context, int width, int height, int ydir,
                          int format, int type, unsigned char* pixels);
+DECL(void, setPostCallback, (OnPostFn onPost, void* onPostContext));
 
-// initLibrary - initialize the library and tries to load the corresponding
-//     GLES translator libraries. This function must be called before anything
-//     else to ensure that everything works. If it returns an error, then
-//     you cannot use the library at all (this can happen under certain
-//     environments where the desktop GL libraries are not available)
-//
-// returns true if the library could be initialized successfully;
-//
-bool initLibrary(void);
+/* createOpenGLSubwindow -
+ *     Create a native subwindow which is a child of 'window'
+ *     to be used for framebuffer display.
+ *     Framebuffer will not get displayed if a subwindow is not
+ *     created.
+ *     x,y,width,height are the dimensions of the rendering subwindow.
+ *     zRot is the rotation to apply on the framebuffer display image.
+ */
+DECL(int, createOpenGLSubwindow, (FBNativeWindowType window,
+		int x, int y, int width, int height, float zRot));
 
-// list of constants to be passed to setStreamMode, which determines
-// which
-#define STREAM_MODE_DEFAULT   0
-#define STREAM_MODE_TCP       1
-#define STREAM_MODE_UNIX      2
-#define STREAM_MODE_PIPE      3
+/* destroyOpenGLSubwindow -
+ *   destroys the created native subwindow. Once destroyed,
+ *   Framebuffer content will not be visible until a new
+ *   subwindow will be created.
+ */
+DECL(int, destroyOpenGLSubwindow, (void));
 
-// Change the stream mode. This must be called before initOpenGLRenderer
-int setStreamMode(int mode);
+/* setOpenGLDisplayRotation -
+ *    set the framebuffer display image rotation in units
+ *    of degrees around the z axis
+ */
+DECL(void, setOpenGLDisplayRotation, (float zRot));
 
-//
-// initOpenGLRenderer - initialize the OpenGL renderer process.
-//     portNum is the tcp port number the renderer is listening to.
-//     width and height are the framebuffer dimensions that will be
-//     reported to the guest display driver.
-//
-// returns true if renderer has been started successfully;
-//
-// This function is *NOT* thread safe and should be called first
-// to initialize the renderer after initLibrary().
-//
-bool initOpenGLRenderer(int width, int height, int portNum,
-                        OnPostFn onPost, void* onPostContext);
+/* repaintOpenGLDisplay -
+ *    causes the OpenGL subwindow to get repainted with the
+ *    latest framebuffer content.
+ */
+DECL(void, repaintOpenGLDisplay, (void));
 
-//
-// createOpenGLSubwindow -
-//     Create a native subwindow which is a child of 'window'
-//     to be used for framebuffer display.
-//     Framebuffer will not get displayed if a subwindow is not
-//     created.
-//     x,y,width,height are the dimensions of the rendering subwindow.
-//     zRot is the rotation to apply on the framebuffer display image.
-//
-bool createOpenGLSubwindow(FBNativeWindowType window,
-                           int x, int y, int width, int height, float zRot);
-
-//
-// destroyOpenGLSubwindow -
-//   destroys the created native subwindow. Once destroyed,
-//   Framebuffer content will not be visible until a new
-//   subwindow will be created.
-//
-bool destroyOpenGLSubwindow();
-
-//
-// setOpenGLDisplayRotation -
-//    set the framebuffer display image rotation in units
-//    of degrees around the z axis
-//
-void setOpenGLDisplayRotation(float zRot);
-
-//
-// repaintOpenGLDisplay -
-//    causes the OpenGL subwindow to get repainted with the
-//    latest framebuffer content.
-//
-void repaintOpenGLDisplay();
-
-//
-// stopOpenGLRenderer - stops the OpenGL renderer process.
-//     This functions is *NOT* thread safe and should be called
-//     only if previous initOpenGLRenderer has returned true.
-//
-bool stopOpenGLRenderer();
+/* stopOpenGLRenderer - stops the OpenGL renderer process.
+ *     This functions is *NOT* thread safe and should be called
+ *     only if previous initOpenGLRenderer has returned true.
+ */
+DECL(int, stopOpenGLRenderer, (void));
 
 #ifdef __cplusplus
 }
diff --git a/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmContext.cpp b/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmContext.cpp
index abf461c..ecf51bb 100644
--- a/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmContext.cpp
+++ b/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmContext.cpp
@@ -31,13 +31,10 @@
         m_texCoords = new GLESpointer[s_glSupport.maxTexUnits];
         m_map[GL_TEXTURE_COORD_ARRAY]  = &m_texCoords[m_clientActiveTexture];
 
-        const char* baseRenderer = (const char*)dispatcher().glGetString(GL_RENDERER);
-        size_t baseRendererLen = strlen(baseRenderer);
-        s_glRenderer.clear();
-        s_glRenderer.reserve(19 + baseRendererLen);
-        s_glRenderer.append("OpenGL ES-CM 1.1 (", 18);
-        s_glRenderer.append(baseRenderer, baseRendererLen);
-        s_glRenderer.append(")", 1);
+        buildStrings((const char*)dispatcher().glGetString(GL_VENDOR),
+                     (const char*)dispatcher().glGetString(GL_RENDERER),
+                     (const char*)dispatcher().glGetString(GL_VERSION),
+                     "OpenGL ES-CM 1.1");
     }
     m_initialized = true;
 }
diff --git a/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmImp.cpp b/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmImp.cpp
index b78a022..3ae271f 100644
--- a/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmImp.cpp
+++ b/emulator/opengl/host/libs/Translator/GLES_CM/GLEScmImp.cpp
@@ -248,16 +248,13 @@
 GL_API const GLubyte * GL_APIENTRY  glGetString( GLenum name) {
 
     GET_CTX_RET(NULL)
-    static const GLubyte VENDOR[]  = "Google";
-    static const GLubyte VERSION[] = "OpenGL ES-CM 1.1";
-
     switch(name) {
         case GL_VENDOR:
-            return VENDOR;
+            return (const GLubyte*)ctx->getVendorString();
         case GL_RENDERER:
             return (const GLubyte*)ctx->getRendererString();
         case GL_VERSION:
-            return VERSION;
+            return (const GLubyte*)ctx->getVersionString();
         case GL_EXTENSIONS:
             return (const GLubyte*)ctx->getExtensionString();
         default:
diff --git a/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Context.cpp b/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Context.cpp
index 73acb61..1457cec 100644
--- a/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Context.cpp
+++ b/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Context.cpp
@@ -28,13 +28,10 @@
         }
         setAttribute0value(0.0, 0.0, 0.0, 1.0);
 
-        const char* baseRenderer = (const char*)dispatcher().glGetString(GL_RENDERER);
-        size_t baseRendererLen = strlen(baseRenderer);
-        s_glRenderer.clear();
-        s_glRenderer.reserve(16 + baseRendererLen);
-        s_glRenderer.append("OpenGL ES 2.0 (", 15);
-        s_glRenderer.append(baseRenderer, baseRendererLen);
-        s_glRenderer.append(")", 1);
+        buildStrings((const char*)dispatcher().glGetString(GL_VENDOR),
+                     (const char*)dispatcher().glGetString(GL_RENDERER),
+                     (const char*)dispatcher().glGetString(GL_VERSION),
+                     "OpenGL ES 2.0");
     }
     m_initialized = true;
 }
diff --git a/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Imp.cpp b/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Imp.cpp
index 412584d..7ae9427 100644
--- a/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Imp.cpp
+++ b/emulator/opengl/host/libs/Translator/GLES_V2/GLESv2Imp.cpp
@@ -1316,16 +1316,14 @@
 
 GL_APICALL const GLubyte* GL_APIENTRY glGetString(GLenum name){
     GET_CTX_RET(NULL)
-    static const GLubyte VENDOR[]  = "Google";
-    static const GLubyte VERSION[] = "OpenGL ES 2.0";
     static const GLubyte SHADING[] = "OpenGL ES GLSL ES 1.0.17";
     switch(name) {
         case GL_VENDOR:
-            return VENDOR;
+            return (const GLubyte*)ctx->getVendorString();
         case GL_RENDERER:
             return (const GLubyte*)ctx->getRendererString();
         case GL_VERSION:
-            return VERSION;
+            return (const GLubyte*)ctx->getVersionString();
         case GL_SHADING_LANGUAGE_VERSION:
             return SHADING;
         case GL_EXTENSIONS:
diff --git a/emulator/opengl/host/libs/Translator/GLcommon/GLEScontext.cpp b/emulator/opengl/host/libs/Translator/GLcommon/GLEScontext.cpp
index cff639e..387eb2d 100644
--- a/emulator/opengl/host/libs/Translator/GLcommon/GLEScontext.cpp
+++ b/emulator/opengl/host/libs/Translator/GLcommon/GLEScontext.cpp
@@ -86,7 +86,9 @@
 GLDispatch     GLEScontext::s_glDispatch;
 android::Mutex GLEScontext::s_lock;
 std::string*   GLEScontext::s_glExtensions= NULL;
+std::string    GLEScontext::s_glVendor;
 std::string    GLEScontext::s_glRenderer;
+std::string    GLEScontext::s_glVersion;
 GLSupport      GLEScontext::s_glSupport;
 
 Version::Version():m_major(0),
@@ -483,10 +485,18 @@
     return ret;
 }
 
+const char * GLEScontext::getVendorString() const {
+    return s_glVendor.c_str();
+}
+
 const char * GLEScontext::getRendererString() const {
     return s_glRenderer.c_str();
 }
 
+const char * GLEScontext::getVersionString() const {
+    return s_glVersion.c_str();
+}
+
 void GLEScontext::getGlobalLock() {
     s_lock.lock();
 }
@@ -547,6 +557,38 @@
 
 }
 
+void GLEScontext::buildStrings(const char* baseVendor,
+        const char* baseRenderer, const char* baseVersion, const char* version)
+{
+    static const char VENDOR[]   = {"Google ("};
+    static const char RENDERER[] = {"Android Emulator OpenGL ES Translator ("};
+    const size_t VENDOR_LEN   = sizeof(VENDOR) - 1;
+    const size_t RENDERER_LEN = sizeof(RENDERER) - 1;
+
+    size_t baseVendorLen = strlen(baseVendor);
+    s_glVendor.clear();
+    s_glVendor.reserve(baseVendorLen + VENDOR_LEN + 1);
+    s_glVendor.append(VENDOR, VENDOR_LEN);
+    s_glVendor.append(baseVendor, baseVendorLen);
+    s_glVendor.append(")", 1);
+
+    size_t baseRendererLen = strlen(baseRenderer);
+    s_glRenderer.clear();
+    s_glRenderer.reserve(baseRendererLen + RENDERER_LEN + 1);
+    s_glRenderer.append(RENDERER, RENDERER_LEN);
+    s_glRenderer.append(baseRenderer, baseRendererLen);
+    s_glRenderer.append(")", 1);
+
+    size_t baseVersionLen = strlen(baseVersion);
+    size_t versionLen = strlen(version);
+    s_glVersion.clear();
+    s_glVersion.reserve(baseVersionLen + versionLen + 3);
+    s_glVersion.append(version, versionLen);
+    s_glVersion.append(" (", 2);
+    s_glVersion.append(baseVersion, baseVersionLen);
+    s_glVersion.append(")", 1);
+}
+
 bool GLEScontext::isTextureUnitEnabled(GLenum unit) {
     for (int i=0;i<NUM_TEXTURE_TARGETS;++i) {
         if (m_texState[unit-GL_TEXTURE0][i].enabled)
diff --git a/emulator/opengl/host/libs/Translator/include/GLcommon/GLEScontext.h b/emulator/opengl/host/libs/Translator/include/GLcommon/GLEScontext.h
index fbc118f..20509fc 100644
--- a/emulator/opengl/host/libs/Translator/include/GLcommon/GLEScontext.h
+++ b/emulator/opengl/host/libs/Translator/include/GLcommon/GLEScontext.h
@@ -147,7 +147,9 @@
     bool setBufferData(GLenum target,GLsizeiptr size,const GLvoid* data,GLenum usage);
     bool setBufferSubData(GLenum target,GLintptr offset,GLsizeiptr size,const GLvoid* data);
     const char * getExtensionString();
+    const char * getVendorString() const;
     const char * getRendererString() const;
+    const char * getVersionString() const;
     void getGlobalLock();
     void releaseGlobalLock();
     virtual GLSupport*  getCaps(){return &s_glSupport;};
@@ -176,6 +178,7 @@
     virtual bool glGetFixedv(GLenum pname, GLfixed *params);
 
 protected:
+    static void buildStrings(const char* baseVendor, const char* baseRenderer, const char* baseVersion, const char* version);
     virtual bool needConvert(GLESConversionArrays& fArrs,GLint first,GLsizei count,GLenum type,const GLvoid* indices,bool direct,GLESpointer* p,GLenum array_id) = 0;
     void convertDirect(GLESConversionArrays& fArrs,GLint first,GLsizei count,GLenum array_id,GLESpointer* p);
     void convertDirectVBO(GLESConversionArrays& fArrs,GLint first,GLsizei count,GLenum array_id,GLESpointer* p);
@@ -183,6 +186,7 @@
     void convertIndirectVBO(GLESConversionArrays& fArrs,GLsizei count,GLenum indices_type,const GLvoid* indices,GLenum array_id,GLESpointer* p);
     void initCapsLocked(const GLubyte * extensionString);
     virtual void initExtensionString() =0;
+
     static android::Mutex s_lock;
     static GLDispatch     s_glDispatch;
     bool                  m_initialized;
@@ -190,7 +194,6 @@
     GLint                 m_unpackAlignment;
     ArraysMap             m_map;
     static std::string*   s_glExtensions;
-    static std::string    s_glRenderer;
     static GLSupport      s_glSupport;
 
 private:
@@ -205,6 +208,10 @@
     unsigned int          m_elementBuffer;
     GLuint                m_renderbuffer;
     GLuint                m_framebuffer;
+
+    static std::string    s_glVendor;
+    static std::string    s_glRenderer;
+    static std::string    s_glVersion;
 };
 
 #endif
diff --git a/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.cpp b/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.cpp
index b7599ce..2f7cd61 100644
--- a/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.cpp
+++ b/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.cpp
@@ -101,7 +101,7 @@
     }
 }
 
-bool FrameBuffer::initialize(int width, int height, OnPostFn onPost, void* onPostContext)
+bool FrameBuffer::initialize(int width, int height)
 {
     if (s_theFrameBuffer != NULL) {
         return true;
@@ -110,7 +110,7 @@
     //
     // allocate space for the FrameBuffer object
     //
-    FrameBuffer *fb = new FrameBuffer(width, height, onPost, onPostContext);
+    FrameBuffer *fb = new FrameBuffer(width, height);
     if (!fb) {
         ERR("Failed to create fb\n");
         return false;
@@ -335,15 +335,12 @@
     fb->initGLState();
 
     //
-    // Allocate space for the onPost framebuffer image
+    // Cache the GL strings so we don't have to think about threading or
+    // current-context when asked for them.
     //
-    if (onPost) {
-        fb->m_fbImage = (unsigned char*)malloc(4 * width * height);
-        if (!fb->m_fbImage) {
-            delete fb;
-            return false;
-        }
-    }
+    fb->m_glVendor = (const char*)s_gl.glGetString(GL_VENDOR);
+    fb->m_glRenderer = (const char*)s_gl.glGetString(GL_RENDERER);
+    fb->m_glVersion = (const char*)s_gl.glGetString(GL_VERSION);
 
     // release the FB context
     fb->unbind_locked();
@@ -355,8 +352,7 @@
     return true;
 }
 
-FrameBuffer::FrameBuffer(int p_width, int p_height,
-        OnPostFn onPost, void* onPostContext) :
+FrameBuffer::FrameBuffer(int p_width, int p_height) :
     m_width(p_width),
     m_height(p_height),
     m_eglDisplay(EGL_NO_DISPLAY),
@@ -366,16 +362,19 @@
     m_prevContext(EGL_NO_CONTEXT),
     m_prevReadSurf(EGL_NO_SURFACE),
     m_prevDrawSurf(EGL_NO_SURFACE),
-    m_subWin(NULL),
+    m_subWin((EGLNativeWindowType)0),
     m_subWinDisplay(NULL),
     m_lastPostedColorBuffer(0),
     m_zRot(0.0f),
     m_eglContextInitialized(false),
     m_statsNumFrames(0),
     m_statsStartTime(0LL),
-    m_onPost(onPost),
-    m_onPostContext(onPostContext),
-    m_fbImage(NULL)
+    m_onPost(NULL),
+    m_onPostContext(NULL),
+    m_fbImage(NULL),
+    m_glVendor(NULL),
+    m_glRenderer(NULL),
+    m_glVersion(NULL)
 {
     m_fpsStats = getenv("SHOW_FPS_STATS") != NULL;
 }
@@ -385,6 +384,22 @@
     free(m_fbImage);
 }
 
+void FrameBuffer::setPostCallback(OnPostFn onPost, void* onPostContext)
+{
+    android::Mutex::Autolock mutex(m_lock);
+    m_onPost = onPost;
+    m_onPostContext = onPostContext;
+    if (m_onPost && !m_fbImage) {
+        m_fbImage = (unsigned char*)malloc(4 * m_width * m_height);
+        if (!m_fbImage) {
+            ERR("out of memory, cancelling OnPost callback");
+            m_onPost = NULL;
+            m_onPostContext = NULL;
+            return;
+        }
+    }
+}
+
 bool FrameBuffer::setupSubWindow(FBNativeWindowType p_window,
                                   int p_x, int p_y,
                                   int p_width, int p_height, float zRot)
@@ -412,7 +427,7 @@
                 if (fb->m_eglSurface == EGL_NO_SURFACE) {
                     ERR("Failed to create surface\n");
                     destroySubWindow(fb->m_subWinDisplay, fb->m_subWin);
-                    fb->m_subWin = NULL;
+                    fb->m_subWin = (EGLNativeWindowType)0;
                 }
                 else if (fb->bindSubwin_locked()) {
                     // Subwin creation was successfull,
@@ -445,7 +460,7 @@
                              s_theFrameBuffer->m_subWin);
 
             s_theFrameBuffer->m_eglSurface = EGL_NO_SURFACE;
-            s_theFrameBuffer->m_subWin = NULL;
+            s_theFrameBuffer->m_subWin = (EGLNativeWindowType)0;
             removed = true;
         }
         s_theFrameBuffer->m_lock.unlock();
diff --git a/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.h b/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.h
index 343f384..de0b71c 100644
--- a/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.h
+++ b/emulator/opengl/host/libs/libOpenglRender/FrameBuffer.h
@@ -46,7 +46,7 @@
 class FrameBuffer
 {
 public:
-    static bool initialize(int width, int height, OnPostFn onPost, void* onPostContext);
+    static bool initialize(int width, int height);
     static bool setupSubWindow(FBNativeWindowType p_window,
                                 int x, int y,
                                 int width, int height, float zRot);
@@ -59,6 +59,14 @@
     int getWidth() const { return m_width; }
     int getHeight() const { return m_height; }
 
+    void setPostCallback(OnPostFn onPost, void* onPostContext);
+
+    void getGLStrings(const char** vendor, const char** renderer, const char** version) const {
+        *vendor = m_glVendor;
+        *renderer = m_glRenderer;
+        *version = m_glVersion;
+    }
+
     HandleType createRenderContext(int p_config, HandleType p_share, bool p_isGL2 = false);
     HandleType createWindowSurface(int p_config, int p_width, int p_height);
     HandleType createColorBuffer(int p_width, int p_height, GLenum p_internalFormat);
@@ -90,7 +98,7 @@
     }
 
 private:
-    FrameBuffer(int p_width, int p_height, OnPostFn onPost, void* onPostContext);
+    FrameBuffer(int p_width, int p_height);
     ~FrameBuffer();
     HandleType genHandle();
     bool bindSubwin_locked();
@@ -133,5 +141,9 @@
     OnPostFn m_onPost;
     void* m_onPostContext;
     unsigned char* m_fbImage;
+
+    const char* m_glVendor;
+    const char* m_glRenderer;
+    const char* m_glVersion;
 };
 #endif
diff --git a/emulator/opengl/host/libs/libOpenglRender/render_api.cpp b/emulator/opengl/host/libs/libOpenglRender/render_api.cpp
index c8d3e06..72cd9ba 100644
--- a/emulator/opengl/host/libs/libOpenglRender/render_api.cpp
+++ b/emulator/opengl/host/libs/libOpenglRender/render_api.cpp
@@ -50,7 +50,7 @@
 #define  RENDER_API_USE_THREAD
 //#endif
 
-bool initLibrary(void)
+int initLibrary(void)
 {
     //
     // Load EGL Plugin
@@ -76,8 +76,7 @@
     return true;
 }
 
-bool initOpenGLRenderer(int width, int height, int portNum,
-        OnPostFn onPost, void* onPostContext)
+int initOpenGLRenderer(int width, int height, int portNum)
 {
 
     //
@@ -94,7 +93,7 @@
     // initialize the renderer and listen to connections
     // on a thread in the current process.
     //
-    bool inited = FrameBuffer::initialize(width, height, onPost, onPostContext);
+    bool inited = FrameBuffer::initialize(width, height);
     if (!inited) {
         return false;
     }
@@ -191,7 +190,38 @@
     return true;
 }
 
-bool stopOpenGLRenderer()
+void setPostCallback(OnPostFn onPost, void* onPostContext)
+{
+#ifdef RENDER_API_USE_THREAD  // should be defined for mac
+    FrameBuffer* fb = FrameBuffer::getFB();
+    if (fb) {
+        fb->setPostCallback(onPost, onPostContext);
+    }
+#else
+    if (onPost) {
+        // onPost callback not supported with separate renderer process.
+        //
+        // If we ever revive separate process support, we could make the choice
+        // between thread and process at runtime instead of compile time, and
+        // choose the thread path if an onPost callback is requested. Or, the
+        // callback could be supported with a separate process using shmem or
+        // other IPC mechanism.
+        return false;
+    }
+#endif
+}
+
+void getHardwareStrings(const char** vendor, const char** renderer, const char** version)
+{
+    FrameBuffer* fb = FrameBuffer::getFB();
+    if (fb) {
+        fb->getGLStrings(vendor, renderer, version);
+    } else {
+        *vendor = *renderer = *version = NULL;
+    }
+}
+
+int stopOpenGLRenderer(void)
 {
     bool ret = false;
 
@@ -224,7 +254,7 @@
     return ret;
 }
 
-bool createOpenGLSubwindow(FBNativeWindowType window,
+int createOpenGLSubwindow(FBNativeWindowType window,
                            int x, int y, int width, int height, float zRot)
 {
     if (s_renderThread) {
@@ -240,7 +270,7 @@
     return false;
 }
 
-bool destroyOpenGLSubwindow()
+int destroyOpenGLSubwindow(void)
 {
     if (s_renderThread) {
         return FrameBuffer::removeSubWindow();
@@ -272,7 +302,7 @@
     }
 }
 
-void repaintOpenGLDisplay()
+void repaintOpenGLDisplay(void)
 {
     if (s_renderThread) {
         FrameBuffer *fb = FrameBuffer::getFB();
@@ -352,8 +382,8 @@
 #endif /* _WIN32 */
         default:
             // Invalid stream mode
-            return -1;
+            return false;
     }
     gRendererStreamMode = mode;
-    return 0;
+    return true;
 }
diff --git a/emulator/opengl/host/renderer/main.cpp b/emulator/opengl/host/renderer/main.cpp
index ae7ace3..4549c56 100644
--- a/emulator/opengl/host/renderer/main.cpp
+++ b/emulator/opengl/host/renderer/main.cpp
@@ -120,7 +120,7 @@
     //
     // initialize Framebuffer
     //
-    bool inited = FrameBuffer::initialize(winWidth, winHeight, NULL, NULL);
+    bool inited = FrameBuffer::initialize(winWidth, winHeight);
     if (!inited) {
         fprintf(stderr,"Failed to initialize Framebuffer\n");
         return -1;
diff --git a/emulator/opengl/shared/OpenglOsUtils/Android.mk b/emulator/opengl/shared/OpenglOsUtils/Android.mk
index 82391cd..2e5a8a1 100644
--- a/emulator/opengl/shared/OpenglOsUtils/Android.mk
+++ b/emulator/opengl/shared/OpenglOsUtils/Android.mk
@@ -38,7 +38,7 @@
 endif
 
 ifeq ($(HOST_OS),linux)
-    host_common_LDLIBS += -lpthread -lrt
+    host_common_LDLIBS += -lpthread -lrt -lX11
 endif
 
 ### 32-bit host library ####
diff --git a/emulator/opengl/tests/translator_tests/GLES_CM/Android.mk b/emulator/opengl/tests/translator_tests/GLES_CM/Android.mk
index 77cd272..d47d340 100644
--- a/emulator/opengl/tests/translator_tests/GLES_CM/Android.mk
+++ b/emulator/opengl/tests/translator_tests/GLES_CM/Android.mk
@@ -10,8 +10,8 @@
 
 ifeq ($(HOST_OS),darwin)
   DARWIN_VERSION := $(strip $(shell sw_vers -productVersion))
-  ifneq ($(filter 10.7 10.7.%,$(DARWIN_VERSION)),)
-    # Lion needs to be forced to link dylib to avoid problems
+  ifneq ($(filter 10.7 10.7.% 10.8 10.8.%,$(DARWIN_VERSION)),)
+    # OS X 10.7+ needs to be forced to link dylib to avoid problems
     # with the dynamic function lookups in SDL 1.2
     LOCAL_SDL_LDLIBS += /usr/lib/dylib1.o
   endif
diff --git a/emulator/opengl/tests/translator_tests/GLES_V2/Android.mk b/emulator/opengl/tests/translator_tests/GLES_V2/Android.mk
index 53f774c..2371da7 100644
--- a/emulator/opengl/tests/translator_tests/GLES_V2/Android.mk
+++ b/emulator/opengl/tests/translator_tests/GLES_V2/Android.mk
@@ -18,8 +18,8 @@
 
 ifeq ($(HOST_OS),darwin)
 DARWIN_VERSION := $(strip $(shell sw_vers -productVersion))
-ifneq ($(filter 10.7 10.7.%,$(DARWIN_VERSION)),)
-  # Lion needs to be forced to link dylib to avoid problems
+ifneq ($(filter 10.7 10.7.% 10.8 10.8.%,$(DARWIN_VERSION)),)
+  # OS X 10.7+ needs to be forced to link dylib to avoid problems
   # with the dynamic function lookups in SDL 1.2
   LOCAL_LDLIBS += /usr/lib/dylib1.o
 endif
diff --git a/ide_common/.classpath b/ide_common/.classpath
index 89ce2dd..79fe47a 100644
--- a/ide_common/.classpath
+++ b/ide_common/.classpath
@@ -3,7 +3,7 @@
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/layoutlib_api"/>
-	<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/common"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/layoutlib_api/.classpath b/layoutlib_api/.classpath
index 4095535..064639b 100644
--- a/layoutlib_api/.classpath
+++ b/layoutlib_api/.classpath
@@ -2,7 +2,7 @@
 <classpath>
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/common"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/layoutlib_api/sample/.classpath b/layoutlib_api/sample/.classpath
index 47c38c1..53c678b 100644
--- a/layoutlib_api/sample/.classpath
+++ b/layoutlib_api/sample/.classpath
@@ -2,7 +2,7 @@
 <classpath>
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_SRC/dalvik/libcore/xml/src/main/java"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/common"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/ide_common"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/layoutlib_api"/>
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java
index 7d9471d..74df385 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/IssueRegistry.java
@@ -124,7 +124,7 @@
                 }
 
                 // Determine if the scope matches
-                if (!scope.containsAll(issueScope)) {
+                if (!issue.isAdequate(scope)) {
                     continue;
                 }
 
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
index e221faa..0f985f0 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
@@ -21,6 +21,7 @@
 import static com.android.tools.lint.detector.api.LintConstants.DOT_CLASS;
 import static com.android.tools.lint.detector.api.LintConstants.DOT_JAR;
 import static com.android.tools.lint.detector.api.LintConstants.DOT_JAVA;
+import static com.android.tools.lint.detector.api.LintConstants.DOT_XML;
 import static com.android.tools.lint.detector.api.LintConstants.OLD_PROGUARD_FILE;
 import static com.android.tools.lint.detector.api.LintConstants.PROGUARD_FILE;
 import static com.android.tools.lint.detector.api.LintConstants.RES_FOLDER;
@@ -245,7 +246,7 @@
                         String name = file.getName();
                         if (name.equals(ANDROID_MANIFEST_XML)) {
                             mScope.add(Scope.MANIFEST);
-                        } else if (name.endsWith(".xml")) {
+                        } else if (name.endsWith(DOT_XML)) {
                             mScope.add(Scope.RESOURCE_FILE);
                         } else if (name.equals(PROGUARD_FILE) || name.equals(OLD_PROGUARD_FILE)) {
                             mScope.add(Scope.PROGUARD_FILE);
@@ -766,8 +767,13 @@
             List<Detector> checks = union(mScopeDetectors.get(Scope.JAVA_FILE),
                     mScopeDetectors.get(Scope.ALL_JAVA_FILES));
             if (checks != null && checks.size() > 0) {
-                List<File> sourceFolders = project.getJavaSourceFolders();
-                checkJava(project, main, sourceFolders, checks);
+                if (project.getSubset() != null) {
+                    checkIndividualJavaFiles(project, main, checks,
+                            project.getSubset());
+                } else {
+                    List<File> sourceFolders = project.getJavaSourceFolders();
+                    checkJava(project, main, sourceFolders, checks);
+                }
             }
         }
 
@@ -775,7 +781,9 @@
             return;
         }
 
-        checkClasses(project, main);
+        if (mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.JAVA_LIBRARIES)) {
+            checkClasses(project, main);
+        }
 
         if (mCanceled) {
             return;
@@ -785,7 +793,6 @@
             checkProGuard(project, main);
         }
     }
-
     private void checkProGuard(Project project, Project main) {
         List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD_FILE);
         if (detectors != null) {
@@ -884,7 +891,8 @@
 
     /** Check the classes in this project (and if applicable, in any library projects */
     private void checkClasses(Project project, Project main) {
-        if (!(mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.JAVA_LIBRARIES))) {
+        if (project.getSubset() != null) {
+            checkIndividualClassFiles(project, main, project.getSubset());
             return;
         }
 
@@ -935,6 +943,47 @@
         runClassDetectors(Scope.CLASS_FILE, classEntries, project, main);
     }
 
+    private void checkIndividualClassFiles(
+            @NonNull Project project,
+            @Nullable Project main,
+            @NonNull List<File> files) {
+        List<ClassEntry> entries = new ArrayList<ClassEntry>(files.size());
+
+        List<File> classFolders = project.getJavaClassFolders();
+        if (!classFolders.isEmpty()) {
+            for (File file : files) {
+                String path = file.getPath();
+                if (file.isFile() && path.endsWith(DOT_CLASS)) {
+                    try {
+                        byte[] bytes = Files.toByteArray(file);
+                        if (bytes != null) {
+                            for (File dir : classFolders) {
+                                if (path.startsWith(dir.getPath())) {
+                                    entries.add(new ClassEntry(file, null /* jarFile*/, dir,
+                                            bytes));
+                                    break;
+                                }
+                            }
+                        }
+                    } catch (IOException e) {
+                        mClient.log(e, null);
+                        continue;
+                    }
+
+                    if (mCanceled) {
+                        return;
+                    }
+                }
+            }
+
+            if (entries.size() > 0) {
+                // No superclass info available on individual lint runs
+                mSuperClassMap = Collections.emptyMap();
+                runClassDetectors(Scope.CLASS_FILE, entries, project, main);
+            }
+        }
+    }
+
     /**
      * Stack of {@link ClassNode} nodes for outer classes of the currently
      * processed class, including that class itself. Populated by
@@ -1186,6 +1235,32 @@
         }
     }
 
+    private void checkIndividualJavaFiles(
+            @NonNull Project project,
+            @Nullable Project main,
+            @NonNull List<Detector> checks,
+            @NonNull List<File> files) {
+
+        IJavaParser javaParser = mClient.getJavaParser();
+        if (javaParser == null) {
+            mClient.log(null, "No java parser provided to lint: not running Java checks");
+            return;
+        }
+
+        JavaVisitor visitor = new JavaVisitor(javaParser, checks);
+
+        for (File file : files) {
+            if (file.isFile() && file.getPath().endsWith(DOT_JAVA)) {
+                JavaContext context = new JavaContext(this, project, main, file);
+                fireEvent(EventType.SCANNING_FILE, context);
+                visitor.visitFile(context, file);
+                if (mCanceled) {
+                    return;
+                }
+            }
+        }
+    }
+
     private void gatherJavaFiles(@NonNull File dir, @NonNull List<File> result) {
         File[] files = dir.listFiles();
         if (files != null) {
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
index f4f4efa..44d5392 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
@@ -344,7 +344,7 @@
      * @param node the instruction node to get a line number for
      * @return the closest line number, or -1 if not known
      */
-    public static int findLineNumber(AbstractInsnNode node) {
+    public static int findLineNumber(@NonNull AbstractInsnNode node) {
         AbstractInsnNode curr = node;
 
         // First search backwards
@@ -373,7 +373,7 @@
      * @param node the method node to get a line number for
      * @return the closest line number, or -1 if not known
      */
-    public static int findLineNumber(MethodNode node) {
+    public static int findLineNumber(@NonNull MethodNode node) {
         if (node.instructions != null && node.instructions.size() > 0) {
             return findLineNumber(node.instructions.get(0));
         }
@@ -382,6 +382,79 @@
     }
 
     /**
+     * Finds the line number closest to the given class declaration
+     *
+     * @param node the method node to get a line number for
+     * @return the closest line number, or -1 if not known
+     */
+    public static int findLineNumber(@NonNull ClassNode node) {
+        if (node.methods != null && !node.methods.isEmpty()) {
+            MethodNode firstMethod = getFirstRealMethod(node);
+            if (firstMethod != null) {
+                return ClassContext.findLineNumber(firstMethod);
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Returns a location for the given {@link ClassNode}, where class node is
+     * either the top level class, or an inner class, in the current context.
+     *
+     * @param classNode the class in the current context
+     * @return a location pointing to the class declaration, or as close to it
+     *         as possible
+     */
+    @NonNull
+    public Location getLocation(@NonNull ClassNode classNode) {
+        // Attempt to find a proper location for this class. This is tricky
+        // since classes do not have line number entries in the class file; we need
+        // to find a method, look up the corresponding line number then search
+        // around it for a suitable tag, such as the class name.
+        String pattern;
+        if (isAnonymousClass(classNode.name)) {
+            pattern = classNode.superName.substring(classNode.superName.lastIndexOf('/') + 1);
+        } else {
+            pattern = classNode.name.substring(classNode.name.lastIndexOf('$') + 1);
+        }
+
+        return getLocationForLine(findLineNumber(classNode), pattern, null);
+    }
+
+    @Nullable
+    private static MethodNode getFirstRealMethod(@NonNull ClassNode classNode) {
+        // Return the first method in the class for line number purposes. Skip <init>,
+        // since it's typically not located near the real source of the method.
+        if (classNode.methods != null) {
+            @SuppressWarnings("rawtypes") // ASM API
+            List methods = classNode.methods;
+            for (Object m : methods) {
+                MethodNode method = (MethodNode) m;
+                if (method.name.charAt(0) != '<') {
+                    return method;
+                }
+            }
+
+            if (classNode.methods.size() > 0) {
+                return (MethodNode) classNode.methods.get(0);
+            }
+        }
+
+        return null;
+    }
+
+    private static boolean isAnonymousClass(@NonNull String fqcn) {
+        int lastIndex = fqcn.lastIndexOf('$');
+        if (lastIndex != -1 && lastIndex < fqcn.length() - 1) {
+            if (Character.isDigit(fqcn.charAt(lastIndex + 1))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Computes a user-readable type signature from the given class owner, name
      * and description. For example, for owner="foo/bar/Foo$Baz", name="foo",
      * description="(I)V", it returns "void foo.bar.Foo.Bar#foo(int)".
@@ -443,7 +516,8 @@
      * @param fqcn the fully qualified class name
      * @return the internal class name
      */
-    public static String getInternalName(String fqcn) {
+    @NonNull
+    public static String getInternalName(@NonNull String fqcn) {
         String[] parts = fqcn.split("\\."); //$NON-NLS-1$
         StringBuilder sb = new StringBuilder();
         String prev = null;
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java
index 3e49bc4..9f42fb4 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Issue.java
@@ -21,7 +21,10 @@
 import com.android.tools.lint.client.api.Configuration;
 import com.google.common.annotations.Beta;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.EnumSet;
+import java.util.List;
 
 
 /**
@@ -47,6 +50,7 @@
     private String mMoreInfoUrl;
     private boolean mEnabledByDefault = true;
     private final EnumSet<Scope> mScope;
+    private List<EnumSet<Scope>> mAnalysisScopes;
     private final Class<? extends Detector> mClass;
 
     // Use factory methods
@@ -244,6 +248,135 @@
     }
 
     /**
+     * Returns the sets of scopes required to analyze this issue, or null if all
+     * scopes named by {@link Issue#getScope()} are necessary. Note that only
+     * <b>one</b> match out of this collection is required, not all, and that
+     * the scope set returned by {@link #getScope()} does not have to be returned
+     * by this method, but is always implied to be included.
+     * <p>
+     * The scopes returned by {@link Issue#getScope()} list all the various
+     * scopes that are <b>affected</b> by this issue, meaning the detector
+     * should consider it. Frequently, the detector must analyze all these
+     * scopes in order to properly decide whether an issue is found. For
+     * example, the unused resource detector needs to consider both the XML
+     * resource files and the Java source files in order to decide if a resource
+     * is unused. If it analyzes just the Java files for example, it might
+     * incorrectly conclude that a resource is unused because it did not
+     * discover a resource reference in an XML file.
+     * <p>
+     * However, there are other issues where the issue can occur in a variety of
+     * files, but the detector can consider each in isolation. For example, the
+     * API checker is affected by both XML files and Java class files (detecting
+     * both layout constructor references in XML layout files as well as code
+     * references in .class files). It doesn't have to analyze both; it is
+     * capable of incrementally analyzing just an XML file, or just a class
+     * file, without considering the other.
+     * <p>
+     * The required scope list provides a list of scope sets that can be used to
+     * analyze this issue. For each scope set, all the scopes must be matched by
+     * the incremental analysis, but any one of the scope sets can be analyzed
+     * in isolation.
+     * <p>
+     * The required scope list is not required to include the full scope set
+     * returned by {@link #getScope()}; that set is always assumed to be
+     * included.
+     * <p>
+     * NOTE: You would normally call {@link #isAdequate(EnumSet)} rather
+     * than calling this method directly.
+     *
+     * @return a list of required scopes, or null.
+     */
+    @Nullable
+    public Collection<EnumSet<Scope>> getAnalysisScopes() {
+        return mAnalysisScopes;
+    }
+
+    /**
+     * Sets the collection of scopes that are allowed to be analyzed independently.
+     * See the {@link #getAnalysisScopes()} method for a full explanation.
+     * Note that you usually want to just call {@link #addAnalysisScope(EnumSet)}
+     * instead of constructing a list up front and passing it in here. This
+     * method exists primarily such that commonly used share sets of analysis
+     * scopes can be reused and set directly.
+     *
+     * @param required the collection of scopes
+     * @return this, for constructor chaining
+     */
+    public Issue setAnalysisScopes(@Nullable List<EnumSet<Scope>> required) {
+        mAnalysisScopes = required;
+
+        return this;
+    }
+
+    /**
+     * Returns true if the given scope is adequate for analyzing this issue.
+     * This looks through the analysis scopes (see
+     * {@link #addAnalysisScope(EnumSet)}) and if the scope passed in fully
+     * covers at least one of them, or if it covers the scope of the issue
+     * itself (see {@link #getScope()}, which should be a superset of all the
+     * analysis scopes) returns true.
+     * <p>
+     * The scope set returned by {@link Issue#getScope()} lists all the various
+     * scopes that are <b>affected</b> by this issue, meaning the detector
+     * should consider it. Frequently, the detector must analyze all these
+     * scopes in order to properly decide whether an issue is found. For
+     * example, the unused resource detector needs to consider both the XML
+     * resource files and the Java source files in order to decide if a resource
+     * is unused. If it analyzes just the Java files for example, it might
+     * incorrectly conclude that a resource is unused because it did not
+     * discover a resource reference in an XML file.
+     * <p>
+     * However, there are other issues where the issue can occur in a variety of
+     * files, but the detector can consider each in isolation. For example, the
+     * API checker is affected by both XML files and Java class files (detecting
+     * both layout constructor references in XML layout files as well as code
+     * references in .class files). It doesn't have to analyze both; it is
+     * capable of incrementally analyzing just an XML file, or just a class
+     * file, without considering the other.
+     * <p>
+     * An issue can register additional scope sets that can are adequate
+     * for analyzing the issue, by calling {@link #addAnalysisScope(EnumSet)}.
+     * This method returns true if the given scope matches one or more analysis
+     * scope, or the overall scope.
+     *
+     * @param scope the scope available for analysis
+     * @return true if this issue can be analyzed with the given available scope
+     */
+    public boolean isAdequate(@Nullable EnumSet<Scope> scope) {
+        if (scope.containsAll(mScope)) {
+            return true;
+        }
+
+        if (mAnalysisScopes != null) {
+            for (EnumSet<Scope> analysisScope : mAnalysisScopes) {
+                if (mScope.containsAll(analysisScope)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Adds a scope set that can be analyzed independently to uncover this issue.
+     * See the {@link #getAnalysisScopes()} method for a full explanation.
+     * Note that the {@link #getScope()} does not have to be added here; it is
+     * always considered an analysis scope.
+     *
+     * @param scope the additional scope which can analyze this issue independently
+     * @return this, for constructor chaining
+     */
+    public Issue addAnalysisScope(@Nullable EnumSet<Scope> scope) {
+        if (mAnalysisScopes == null) {
+            mAnalysisScopes = new ArrayList<EnumSet<Scope>>(2);
+        }
+        mAnalysisScopes.add(scope);
+
+        return this;
+    }
+
+    /**
      * Returns the class of the detector to use to find this issue
      *
      * @return the class of the detector to use to find this issue
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
index 4b9da60..a917c11 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java
@@ -90,12 +90,19 @@
      * @return true if the scope set references a single file
      */
     public static boolean checkSingleFile(@NonNull EnumSet<Scope> scopes) {
-        return scopes.size() == 1 &&
+        int size = scopes.size();
+        if (size == 2) {
+            // When single checking a Java source file, we check both its Java source
+            // and the associated class files
+            return scopes.contains(JAVA_FILE) && scopes.contains(CLASS_FILE);
+        } else {
+            return size == 1 &&
                 (scopes.contains(JAVA_FILE)
                         || scopes.contains(CLASS_FILE)
                         || scopes.contains(RESOURCE_FILE)
                         || scopes.contains(PROGUARD_FILE)
                         || scopes.contains(MANIFEST));
+        }
     }
 
     /**
@@ -123,4 +130,8 @@
     public static final EnumSet<Scope> ALL_RESOURCES_SCOPE = EnumSet.of(ALL_RESOURCE_FILES);
     /** Scope-set used for detectors which are affected by a single Java source file */
     public static final EnumSet<Scope> JAVA_FILE_SCOPE = EnumSet.of(JAVA_FILE);
+    /** Scope-set used for detectors which are affected by a single Java class file */
+    public static final EnumSet<Scope> CLASS_FILE_SCOPE = EnumSet.of(CLASS_FILE);
+    /** Scope-set used for detectors which are affected by the manifest only */
+    public static final EnumSet<Scope> MANIFEST_SCOPE = EnumSet.of(MANIFEST);
 }
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
index 43ea4ef..43bd7a8 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java
@@ -81,7 +81,9 @@
             6,
             Severity.ERROR,
             ApiDetector.class,
-            EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE));
+            EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE))
+            .addAnalysisScope(Scope.RESOURCE_FILE_SCOPE)
+            .addAnalysisScope(Scope.CLASS_FILE_SCOPE);
 
     private ApiLookup mApiDatabase;
     private int mMinApi = -1;
@@ -295,7 +297,7 @@
                             "Class requires API level %1$d (current min is %2$d): %3$s",
                             api, minSdk, fqcn);
                         AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null;
-                        report(context, message, first, method, null, null);
+                        report(context, message, first, method, method.name, null);
                     }
                 }
             }
@@ -458,6 +460,15 @@
     private void report(final ClassContext context, String message, AbstractInsnNode node,
             MethodNode method, String patternStart, String patternEnd) {
         int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
+
+        // If looking for a constructor, the string we'll see in the source is not the
+        // method name (<init>) but the class name
+        if (patternStart != null && patternStart.equals("<init>") //$NON-NLS-1$
+                && node instanceof MethodInsnNode) {
+            String owner = ((MethodInsnNode) node).owner;
+            patternStart = owner.substring(owner.lastIndexOf('/') + 1);
+        }
+
         Location location = context.getLocationForLine(lineNumber, patternStart, patternEnd);
         context.report(UNSUPPORTED, method, location, message, null);
     }
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index b62f34a..a367cd6 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -53,7 +53,7 @@
     private static final List<Issue> sIssues;
 
     static {
-        final int initialCapacity = 88;
+        final int initialCapacity = 90;
         List<Issue> issues = new ArrayList<Issue>(initialCapacity);
 
         issues.add(AccessibilityDetector.ISSUE);
@@ -85,6 +85,7 @@
         issues.add(GridLayoutDetector.ISSUE);
         issues.add(OnClickDetector.ISSUE);
         issues.add(RegistrationDetector.ISSUE);
+        issues.add(HandlerDetector.ISSUE);
         issues.add(TranslationDetector.EXTRA);
         issues.add(TranslationDetector.MISSING);
         issues.add(HardcodedValuesDetector.ISSUE);
@@ -144,6 +145,7 @@
         issues.add(JavaPerformanceDetector.USE_SPARSEARRAY);
         issues.add(SetJavaScriptEnabledDetector.ISSUE);
         issues.add(ToastDetector.ISSUE);
+        issues.add(SharedPrefsDetector.ISSUE);
 
         assert initialCapacity >= issues.size() : issues.size();
 
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java
index 8bbc749..656c226 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java
@@ -38,7 +38,6 @@
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -63,7 +62,7 @@
             4,
             Severity.WARNING,
             FieldGetterDetector.class,
-            EnumSet.of(Scope.CLASS_FILE)).
+            Scope.CLASS_FILE_SCOPE).
             // This is a micro-optimization: not enabled by default
             setEnabledByDefault(false).setMoreInfo(
            "http://developer.android.com/guide/practices/design/performance.html#internal_get_set"); //$NON-NLS-1$
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HandlerDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HandlerDetector.java
new file mode 100644
index 0000000..03a611f
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HandlerDetector.java
@@ -0,0 +1,121 @@
+/*
+ * 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.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+
+import java.io.File;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * Checks that Handler implementations are top level classes or static.
+ * See the corresponding check in the android.os.Handler source code.
+ */
+public class HandlerDetector extends Detector implements ClassScanner {
+
+    /** Potentially leaking handlers */
+    public static final Issue ISSUE = Issue.create(
+        "HandlerLeak", //$NON-NLS-1$
+        "Ensures that Handler classes do not hold on to a reference to an outer class",
+
+        "In Android, Handler classes should be static or leaks might occur. " +
+        "Messages enqueued on the application thread's MessageQueue also retain their " +
+        "target Handler. If the Handler is an inner class, its outer class will be " +
+        "retained as well. To avoid leaking the outer class, declare the Handler as a " +
+        "static nested class with a WeakReference to its outer class.",
+
+        Category.PERFORMANCE,
+        4,
+        Severity.WARNING,
+        HandlerDetector.class,
+        EnumSet.of(Scope.CLASS_FILE));
+
+    /** Constructs a new {@link HandlerDetector} */
+    public HandlerDetector() {
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    @Override
+    public boolean appliesTo(Context context, File file) {
+        return true;
+    }
+
+    // ---- Implements ClassScanner ----
+
+    @Override
+    public void checkClass(ClassContext context, ClassNode classNode) {
+        if (classNode.name.indexOf('$') == -1) {
+            return;
+        }
+
+        // Note: We can't just filter out static inner classes like this:
+        //     (classNode.access & Opcodes.ACC_STATIC) != 0
+        // because the static flag only appears on methods and fields in the class
+        // file. Instead, look for the synthetic this pointer.
+
+        LintDriver driver = context.getDriver();
+        String name = classNode.name;
+        while (name != null) {
+            if (name.equals("android/os/Handler")) { //$NON-NLS-1$
+                if (isStaticInnerClass(classNode)) {
+                    return;
+                }
+
+                Location location = context.getLocation(classNode);
+                context.report(ISSUE, location, String.format(
+                        "This Handler class should be static or leaks might occur (%1$s)",
+                            ClassContext.createSignature(classNode.name, null, null)),
+                        null);
+                return;
+            }
+            name = driver.getSuperClass(name);
+        }
+    }
+
+    private boolean isStaticInnerClass(@NonNull ClassNode classNode) {
+        @SuppressWarnings("rawtypes") // ASM API
+        List fieldList = classNode.fields;
+        for (Object f : fieldList) {
+            FieldNode field = (FieldNode) f;
+            if (field.name.startsWith("this$") && (field.access & Opcodes.ACC_SYNTHETIC) != 0) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
index 306f546..df8c728 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java
@@ -34,7 +34,6 @@
 import java.io.File;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.EnumSet;
 
 /**
  * Checks for hardcoded debug mode in manifest files
@@ -59,7 +58,7 @@
             5,
             Severity.WARNING,
             HardcodedDebugModeDetector.class,
-            EnumSet.of(Scope.MANIFEST));
+            Scope.MANIFEST_SCOPE);
 
     /** Constructs a new {@link HardcodedDebugModeDetector} check */
     public HardcodedDebugModeDetector() {
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
index 42ed1b8..c687d74 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java
@@ -648,6 +648,9 @@
                     float factor = getMdpiScalingFactor(file.getParentFile().getName());
                     if (factor > 0) {
                         Dimension size = pixelSizes.get(file);
+                        if (size == null) {
+                            continue;
+                        }
                         Dimension dip = new Dimension(
                                 Math.round(size.width / factor),
                                 Math.round(size.height / factor));
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java
index 04cb9b6..84216d1 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java
@@ -40,7 +40,7 @@
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -93,7 +93,7 @@
      * Map from element to whether that element has a non-zero linear layout
      * weight or has an ancestor which does
      */
-    private Map<Node, Boolean> mInsideWeight = new HashMap<Node, Boolean>();
+    private Map<Node, Boolean> mInsideWeight = new IdentityHashMap<Node, Boolean>();
 
     /** Constructs a new {@link InefficientWeightDetector} */
     public InefficientWeightDetector() {
@@ -116,7 +116,6 @@
         boolean multipleWeights = false;
         Element weightChild = null;
         boolean checkNesting = context.isEnabled(NESTED_WEIGHTS);
-        Node parent = element.getParentNode();
         for (Element child : children) {
             if (child.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)) {
                 if (weightChild != null) {
@@ -127,11 +126,11 @@
                 }
 
                 if (checkNesting) {
-                    mInsideWeight.put(element, Boolean.TRUE);
+                    mInsideWeight.put(child, Boolean.TRUE);
 
-                    Boolean inside = mInsideWeight.get(parent);
+                    Boolean inside = mInsideWeight.get(element);
                     if (inside == null) {
-                        mInsideWeight.put(parent, Boolean.FALSE);
+                        mInsideWeight.put(element, Boolean.FALSE);
                     } else if (inside) {
                         Attr sizeNode = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT);
                         context.report(NESTED_WEIGHTS, sizeNode,
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java
index 856bd26..ccf6e98 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java
@@ -68,7 +68,7 @@
             5,
             Severity.WARNING,
             ManifestOrderDetector.class,
-            EnumSet.of(Scope.MANIFEST));
+            Scope.MANIFEST_SCOPE);
 
     /** Missing a {@code <uses-sdk>} element */
     public static final Issue USES_SDK = Issue.create(
@@ -84,7 +84,7 @@
             2,
             Severity.WARNING,
             ManifestOrderDetector.class,
-            EnumSet.of(Scope.MANIFEST)).setMoreInfo(
+            Scope.MANIFEST_SCOPE).setMoreInfo(
             "http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$
 
     /** Missing a {@code <uses-sdk>} element */
@@ -101,7 +101,7 @@
             6,
             Severity.FATAL,
             ManifestOrderDetector.class,
-            EnumSet.of(Scope.MANIFEST)).setMoreInfo(
+            Scope.MANIFEST_SCOPE).setMoreInfo(
             "http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$
 
     /** Missing a {@code <uses-sdk>} element */
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java
index 91d2b5a..06258dd 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java
@@ -34,7 +34,6 @@
 import org.objectweb.asm.tree.MethodNode;
 
 import java.io.File;
-import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -60,7 +59,7 @@
             3,
             Severity.WARNING,
             MathDetector.class,
-            EnumSet.of(Scope.CLASS_FILE)).setMoreInfo(
+            Scope.CLASS_FILE_SCOPE).setMoreInfo(
                //"http://developer.android.com/reference/android/util/FloatMath.html"); //$NON-NLS-1$
                "http://developer.android.com/guide/practices/design/performance.html#avoidfloat"); //$NON-NLS-1$
 
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java
index 13b5c83..807d515 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java
@@ -52,7 +52,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.EnumSet;
 import java.util.Iterator;
 import java.util.List;
 
@@ -81,7 +80,7 @@
             5,
             Severity.WARNING,
             SecurityDetector.class,
-            EnumSet.of(Scope.MANIFEST));
+            Scope.MANIFEST_SCOPE);
 
     /** Exported content providers */
     public static final Issue EXPORTED_PROVIDER = Issue.create(
@@ -96,7 +95,7 @@
             5,
             Severity.WARNING,
             SecurityDetector.class,
-            EnumSet.of(Scope.MANIFEST));
+            Scope.MANIFEST_SCOPE);
 
     /** Content provides which grant all URIs access */
     public static final Issue OPEN_PROVIDER = Issue.create(
@@ -109,7 +108,7 @@
             7,
             Severity.WARNING,
             SecurityDetector.class,
-            EnumSet.of(Scope.MANIFEST));
+            Scope.MANIFEST_SCOPE);
 
     /** Using the world-writable flag */
     public static final Issue WORLD_WRITEABLE = Issue.create(
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SharedPrefsDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SharedPrefsDetector.java
new file mode 100644
index 0000000..525ab6d
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SharedPrefsDetector.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.lint.checks;
+
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.Return;
+import lombok.ast.VariableDefinition;
+import lombok.ast.VariableDefinitionEntry;
+
+/**
+ * Detector looking for SharedPreferences.edit() calls without a corresponding
+ * commit() or apply() call
+ */
+public class SharedPrefsDetector extends Detector implements Detector.JavaScanner {
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "CommitPrefEdits", //$NON-NLS-1$
+            "Looks for code editing a SharedPreference but forgetting to call commit() on it",
+
+            "After calling edit() on a SharedPreference, you must call commit() or apply() on " +
+            "the editor to save the results.",
+
+            Category.CORRECTNESS,
+            6,
+            Severity.WARNING,
+            SharedPrefsDetector.class,
+            Scope.JAVA_FILE_SCOPE);
+
+    /** Constructs a new {@link SharedPrefsDetector} check */
+    public SharedPrefsDetector() {
+    }
+
+    @Override
+    public boolean appliesTo(Context context, File file) {
+        return true;
+    }
+
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.NORMAL;
+    }
+
+    // ---- Implements JavaScanner ----
+
+    @Override
+    public List<String> getApplicableMethodNames() {
+        return Collections.singletonList("edit"); //$NON-NLS-1$
+    }
+
+    private MethodDeclaration findSurroundingMethod(Node scope) {
+        while (scope != null) {
+            Class<? extends Node> type = scope.getClass();
+            // The Lombok AST uses a flat hierarchy of node type implementation classes
+            // so no need to do instanceof stuff here.
+            if (type == MethodDeclaration.class) {
+                return (MethodDeclaration) scope;
+            }
+
+            scope = scope.getParent();
+        }
+
+        return null;
+    }
+
+    @Override
+    public void visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node) {
+        assert node.astName().astValue().equals("edit");
+        if (node.astOperand() == null) {
+            return;
+        }
+
+        // Looking for the specific pattern where you assign the edit() result
+        // to a local variable; this means we won't recognize some other usages
+        // of the API (e.g. assigning it to a previously declared variable) but
+        // is needed until we have type attribution in the AST itself.
+        if (!(node.getParent() instanceof VariableDefinitionEntry &&
+                node.getParent().getParent() instanceof VariableDefinition)) {
+            return;
+        }
+        VariableDefinition definition = (VariableDefinition) node.getParent().getParent();
+        String type = definition.astTypeReference().toString();
+        if (!type.endsWith("SharedPreferences.Editor")) { //$NON-NLS-1$
+            return;
+        }
+
+        MethodDeclaration method = findSurroundingMethod(node.getParent());
+        if (method == null) {
+            return;
+        }
+
+        CommitFinder finder = new CommitFinder(node);
+        method.accept(finder);
+        if (!finder.isCommitCalled()) {
+            context.report(ISSUE, method, context.getLocation(node),
+                    "SharedPreferences.edit() without a corresponding commit() or apply() call",
+                    null);
+        }
+    }
+
+    private class CommitFinder extends ForwardingAstVisitor {
+        /** Whether we've found one of the commit/cancel methods */
+        private boolean mFound;
+        /** The target edit call */
+        private MethodInvocation mTarget;
+        /** Whether we've seen the target edit node yet */
+        private boolean mSeenTarget;
+
+        private CommitFinder(MethodInvocation target) {
+            mTarget = target;
+        }
+
+        @Override
+        public boolean visitMethodInvocation(MethodInvocation node) {
+            if (node == mTarget) {
+                mSeenTarget = true;
+            } else if (mSeenTarget || node.astOperand() == mTarget) {
+                String name = node.astName().astValue();
+                if ("commit".equals(name) || "apply".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$
+                    // TODO: Do more flow analysis to see whether we're really calling commit/apply
+                    // on the right type of object?
+                    mFound = true;
+                }
+            }
+
+            return true;
+        }
+
+        @Override
+        public boolean visitReturn(Return node) {
+            if (node.astValue() == mTarget) {
+                // If you just do "return editor.commit() don't warn
+                mFound = true;
+            }
+            return super.visitReturn(node);
+        }
+
+        boolean isCommitCalled() {
+            return mFound;
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ToastDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ToastDetector.java
index c97027f..f199438 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ToastDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ToastDetector.java
@@ -54,7 +54,8 @@
             ToastDetector.class,
             Scope.JAVA_FILE_SCOPE);
 
-    /** Constructs a new {@link FieldGetterDetector} check */
+
+    /** Constructs a new {@link ToastDetector} check */
     public ToastDetector() {
     }
 
@@ -94,6 +95,10 @@
     @Override
     public void visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node) {
         assert node.astName().astValue().equals("makeText");
+        if (node.astOperand() == null) {
+            // "makeText()" in the code with no operand
+            return;
+        }
 
         String operand = node.astOperand().toString();
         if (!(operand.equals("Toast") || operand.endsWith(".Toast"))) {
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java
index e5d1e7e..6c3b42a 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java
@@ -31,7 +31,6 @@
 import org.objectweb.asm.tree.MethodNode;
 
 import java.io.File;
-import java.util.EnumSet;
 import java.util.List;
 
 /**
@@ -65,7 +64,7 @@
             3,
             Severity.WARNING,
             ViewConstructorDetector.class,
-            EnumSet.of(Scope.CLASS_FILE));
+            Scope.CLASS_FILE_SCOPE);
 
     /** Constructs a new {@link ViewConstructorDetector} check */
     public ViewConstructorDetector() {
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/HandlerDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/HandlerDetectorTest.java
new file mode 100644
index 0000000..1631a23
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/HandlerDetectorTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class HandlerDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new HandlerDetector();
+    }
+
+    public void testRegistered() throws Exception {
+        assertEquals(
+            "HandlerTest$1.class: Warning: This Handler class should be static or leaks might occur (test.pkg.HandlerTest.1)\n" +
+            "HandlerTest$Inner.class: Warning: This Handler class should be static or leaks might occur (test.pkg.HandlerTest.Inner)",
+
+            lintProject(
+                "bytecode/HandlerTest.java.txt=>src/test/pkg/HandlerTest.java",
+                "bytecode/HandlerTest.class.data=>bin/classes/test/pkg/HandlerTest.class",
+                "bytecode/HandlerTest$Inner.class.data=>bin/classes/test/pkg/HandlerTest$Inner.class",
+                "bytecode/HandlerTest$StaticInner.class.data=>bin/classes/test/pkg/HandlerTest$StaticInner.class",
+                "bytecode/HandlerTest$1.class.data=>bin/classes/test/pkg/HandlerTest$1.class"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
index ed820c6..c48658f 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/InefficientWeightDetectorTest.java
@@ -28,16 +28,13 @@
     public void testWeights() throws Exception {
         assertEquals(
             "inefficient_weight.xml:10: Warning: Use a layout_width of 0dip instead of match_parent for better performance\n" +
-            "inefficient_weight.xml:24: Warning: Use a layout_height of 0dip instead of wrap_content for better performance\n" +
-            "inefficient_weight.xml:25: Warning: Nested weights are bad for performance\n" +
-            "inefficient_weight.xml:40: Warning: Nested weights are bad for performance",
+            "inefficient_weight.xml:24: Warning: Use a layout_height of 0dip instead of wrap_content for better performance",
             lintFiles("res/layout/inefficient_weight.xml"));
     }
 
     public void testWeights2() throws Exception {
         assertEquals(
-            "nested_weights.xml:22: Warning: Nested weights are bad for performance\n" +
-            "nested_weights.xml:28: Warning: Use a layout_width of 0dip instead of match_parent for better performance",
+            "nested_weights.xml:23: Warning: Nested weights are bad for performance",
             lintFiles("res/layout/nested_weights.xml"));
     }
 
@@ -68,5 +65,12 @@
             lintFiles("res/layout/inefficient_weight2.xml"));
     }
 
+    public void testNestedWeights() throws Exception {
+        // Regression test for http://code.google.com/p/android/issues/detail?id=22889
+        // (Comment 8)
+        assertEquals(
+                "No warnings.",
 
+                lintFiles("res/layout/nested_weights2.xml"));
+    }
 }
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SharedPrefsDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
new file mode 100644
index 0000000..b13b344
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SharedPrefsDetectorTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class SharedPrefsDetectorTest  extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new SharedPrefsDetector();
+    }
+
+    public void test() throws Exception {
+        assertEquals(
+            "SharedPrefsTest.java:54: Warning: SharedPreferences.edit() without a " +
+            "corresponding commit() or apply() call",
+
+            lintProject("src/test/pkg/SharedPrefsTest.java.txt=>" +
+                    "src/test/pkg/SharedPrefsTest.java"));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data
new file mode 100644
index 0000000..ae65532
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$1.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data
new file mode 100644
index 0000000..3975896
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$Inner.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data
new file mode 100644
index 0000000..690ee89
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest$StaticInner.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data
new file mode 100644
index 0000000..93f9999
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
new file mode 100644
index 0000000..7622c98
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
@@ -0,0 +1,24 @@
+package test.pkg;
+
+import android.os.Handler;
+import android.os.Message;
+
+public class HandlerTest extends Handler { // OK
+    public static class StaticInner extends Handler { // OK
+        public void dispatchMessage(Message msg) {
+            super.dispatchMessage(msg);
+        };
+    }
+    public class Inner extends Handler { // ERROR
+        public void dispatchMessage(Message msg) {
+            super.dispatchMessage(msg);
+        };
+    }
+    void method() {
+        Handler anonymous = new Handler() { // ERROR
+            public void dispatchMessage(Message msg) {
+                super.dispatchMessage(msg);
+            };
+        };
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights.xml
index fda1e3e..a375e06 100644
--- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights.xml
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights.xml
@@ -12,6 +12,7 @@
 
     <LinearLayout
         android:id="@+id/linearLayout1"
+        android:layout_weight="1"
         android:layout_width="match_parent"
         android:layout_height="match_parent" >
 
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights2.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights2.xml
new file mode 100644
index 0000000..7002b89
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/nested_weights2.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+   android:layout_width="fill_parent"
+   android:layout_height="fill_parent" >
+
+   <LinearLayout
+       android:layout_width="fill_parent"
+       android:layout_height="fill_parent"
+       android:orientation="vertical" >
+
+       <LinearLayout
+           android:layout_width="fill_parent"
+           android:layout_height="wrap_content"
+           android:orientation="horizontal" >
+
+           <ImageView
+               android:layout_width="32dp"
+               android:layout_height="32dp"
+               android:layout_gravity="center_vertical"
+               android:src="@drawable/launcher_icon" />
+
+           <TextView
+               android:layout_width="0dp"
+               android:layout_height="fill_parent"
+               android:layout_gravity="center_vertical"
+               android:layout_weight="1"
+               android:text="test" />
+       </LinearLayout>
+
+       <LinearLayout
+           android:layout_width="fill_parent"
+           android:layout_weight="1"
+           android:layout_height="0dp"
+           android:orientation="vertical" >
+       </LinearLayout>
+   </LinearLayout>
+
+</FrameLayout>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest.java.txt
new file mode 100644
index 0000000..557fc51
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/SharedPrefsTest.java.txt
@@ -0,0 +1,59 @@
+package foo.bar;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.widget.Toast;
+
+public class SharedPrefsText {
+    // OK 1
+    public void onCreate1(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+        SharedPreferences.Editor editor = preferences.edit();
+        editor.putString("foo", "bar");
+        editor.putInt("bar", 42);
+        editor.commit();
+    }
+
+    // OK 2
+    public void onCreate2(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+        SharedPreferences.Editor editor = preferences.edit();
+        editor.putString("foo", "bar");
+        editor.putInt("bar", 42);
+        if (apply) {
+            editor.apply();
+        }
+    }
+
+    // OK 3
+    public boolean test1(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+        SharedPreferences.Editor editor = preferences.edit();
+        editor.putString("foo", "bar");
+        editor.putInt("bar", 42);
+        return editor.apply();
+    }
+
+    // Not a bug
+    public void test(Foo foo) {
+        Bar bar1 = foo.edit();
+        Bar bar2 = Foo.edit();
+        Bar bar3 = edit();
+        SharedPreferences.Editor editor = preferences.edit(42);
+        apply();
+    }
+
+    // Bug
+    public void bug1(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+        SharedPreferences.Editor editor = preferences.edit();
+        editor.putString("foo", "bar");
+        editor.putInt("bar", 42);
+    }
+ }
+
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/monitor/build.xml b/monitor/build.xml
index fabd3ba..d3c2d94 100644
--- a/monitor/build.xml
+++ b/monitor/build.xml
@@ -53,10 +53,11 @@
     <!-- create the build directory, copy plugins and features into it -->
     <target name="copy_srcs">
         <mkdir dir="${buildDir}" />
-        <copy todir="${buildDir}">
+        <copy todir="${buildDir}" preservelastmodified="true">
             <fileset dir="${srcDir}/">
                 <include name="plugins/**" />
                 <include name="features/**" />
+                <exclude name="plugins/*/bin/**" />
             </fileset>
         </copy>
     </target>
diff --git a/sdkmanager/libs/sdklib/.classpath b/sdkmanager/libs/sdklib/.classpath
index 3e6a435..ce6f3f4 100644
--- a/sdkmanager/libs/sdklib/.classpath
+++ b/sdkmanager/libs/sdklib/.classpath
@@ -12,6 +12,6 @@
 	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/httpclient-4.1.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/http-client/src/httpcomponents-client-4.1.1-src.zip"/>
 	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/httpcore-4.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/http-client/src/httpcomponents-core-4.1-src.zip"/>
 	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/http-client/httpmime-4.1.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/http-client/src/httpcomponents-client-4.1.1-src.zip"/>
-	<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/mkidentity/mkidentity-prebuilt.jar"/>
+	<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/mkidentity/mkidentity-prebuilt.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
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 8284054..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);
@@ -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/DownloadCache.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
index 039e165..591e447 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
@@ -71,7 +71,7 @@
      *     http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
      */
 
-    private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG_CACHE") != null;
+    private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG_CACHE") != null; //$NON-NLS-1$
 
     /** Key for the Status-Code in the info properties. */
     private static final String KEY_STATUS_CODE = "Status-Code";        //$NON-NLS-1$
@@ -134,6 +134,12 @@
 
     public enum Strategy {
         /**
+         * Exclusively serves data from the cache. If files are available in the
+         * cache, serve them as is (without trying to refresh them). If files are
+         * not available, they are <em>not</em> fetched at all.
+         */
+        ONLY_CACHE,
+        /**
          * If the files are available in the cache, serve them as-is, otherwise
          * download them and return the cached version. No expiration or refresh
          * is attempted if a file is in the cache.
@@ -156,6 +162,12 @@
     /** Creates a default instance of the URL cache */
     public DownloadCache(Strategy strategy) {
         mCacheRoot = initCacheRoot();
+
+        // If this is defined in the environment, never use the cache. Useful for testing.
+        if (System.getenv("SDKMAN_DISABLE_CACHE") != null) {                 //$NON-NLS-1$
+            strategy = Strategy.DIRECT;
+        }
+
         mStrategy = mCacheRoot == null ? Strategy.DIRECT : strategy;
     }
 
@@ -231,6 +243,7 @@
      * @param monitor {@link ITaskMonitor} which is related to this URL
      *            fetching.
      * @return Returns an {@link InputStream} holding the URL content.
+     *   Returns null if the document is not cached and strategy is {@link Strategy#ONLY_CACHE}.
      * @throws IOException Exception thrown when there are problems retrieving
      *             the URL or its content.
      * @throws CanceledByUserException Exception thrown if the user cancels the
@@ -408,6 +421,14 @@
             } catch (IOException ignore) {}
         }
 
+        if (!useCached && mStrategy == Strategy.ONLY_CACHE) {
+            // We don't have a document to serve from the cache.
+            if (DEBUG) {
+                System.out.println(String.format("%s : file not in cache", urlString)); //$NON-NLS-1$
+            }
+            return null;
+        }
+
         // If we're not using the cache, try to remove the cache and download again.
         try {
             cached.delete();
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java
index fb59b42..dfd197d 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskFactory.java
@@ -16,6 +16,7 @@
 

 package com.android.sdklib.internal.repository;

 

+

 /**

  * A factory that can start and run new {@link ITask}s.

  */

@@ -23,6 +24,12 @@
 

     /**

      * Starts a new task with a new {@link ITaskMonitor}.

+     * <p/>

+     * The task will execute in a thread and runs it own UI loop.

+     * This means the task can perform UI operations using

+     * {@code Display#asyncExec(Runnable)}.

+     * <p/>

+     * In either case, the method only returns when the task has finished.

      *

      * @param title The title of the task, displayed in the monitor if any.

      * @param task The task to run.

@@ -36,6 +43,13 @@
      * and give the sub-monitor to the new task with the number of work units you want

      * it to fill. The {@link #start} method will make sure to <em>fill</em> the progress

      * when the task is completed, in case the actual task did not.

+     * <p/>

+     * When a task is started from within a monitor, it reuses the thread

+     * from the parent. Otherwise it starts a new thread and runs it own

+     * UI loop. This means the task can perform UI operations using

+     * {@code Display#asyncExec(Runnable)}.

+     * <p/>

+     * In either case, the method only returns when the task has finished.

      *

      * @param title The title of the task, displayed in the monitor if any.

      * @param parentMonitor The parent monitor. Can be null.

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java
deleted file mode 100755
index e4f2419..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*

- * Copyright (C) 2009 The Android Open Source Project

- *

- * Licensed under the Apache License, Version 2.0 (the "License");

- * you may not use this file except in compliance with the License.

- * You may obtain a copy of the License at

- *

- *      http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing, software

- * distributed under the License is distributed on an "AS IS" BASIS,

- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

- * See the License for the specific language governing permissions and

- * limitations under the License.

- */

-

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

-

-import org.w3c.dom.Node;

-

-/**

- * Misc utilities to help extracting elements and attributes out of an XML document.

- */

-public class XmlParserUtils {

-

-    /**

-     * Returns the first child element with the given XML local name.

-     * If xmlLocalName is null, returns the very first child element.

-     */

-    public static Node getFirstChild(Node node, String xmlLocalName) {

-

-        String nsUri = node.getNamespaceURI();

-        for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {

-            if (child.getNodeType() == Node.ELEMENT_NODE &&

-                    nsUri.equals(child.getNamespaceURI())) {

-                if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {

-                    return child;

-                }

-            }

-        }

-

-        return null;

-    }

-

-    /**

-     * Retrieves the value of that XML element as a string.

-     * Returns an empty string whether the element is missing or empty,

-     * so you can't tell the difference.

-     * <p/>

-     * Note: use {@link #getOptionalXmlString(Node, String)} if you need to know when the

-     * element is missing versus empty.

-     *

-     * @param node The XML <em>parent</em> node to parse.

-     * @param xmlLocalName The XML local name to find in the parent node.

-     * @return The text content of the element. Returns an empty string whether the element

-     *         is missing or empty, so you can't tell the difference.

-     */

-    public static String getXmlString(Node node, String xmlLocalName) {

-        Node child = getFirstChild(node, xmlLocalName);

-

-        return child == null ? "" : child.getTextContent();  //$NON-NLS-1$

-    }

-

-    /**

-     * Retrieves the value of that XML element as a string.

-     * Returns null when the element is missing, so you can tell between a missing element

-     * and an empty one.

-     * <p/>

-     * Note: use {@link #getXmlString(Node, String)} if you don't need to know when the

-     * element is missing versus empty.

-     *

-     * @param node The XML <em>parent</em> node to parse.

-     * @param xmlLocalName The XML local name to find in the parent node.

-     * @return The text content of the element. Returns null when the element is missing.

-     *         Returns an empty string whether the element is present but empty.

-     */

-    public static String getOptionalXmlString(Node node, String xmlLocalName) {

-        Node child = getFirstChild(node, xmlLocalName);

-

-        return child == null ? null : child.getTextContent();  //$NON-NLS-1$

-    }

-

-    /**

-     * Retrieves the value of that XML element as an integer.

-     * Returns the default value when the element is missing or is not an integer.

-     */

-    public static int getXmlInt(Node node, String xmlLocalName, int defaultValue) {

-        String s = getXmlString(node, xmlLocalName);

-        try {

-            return Integer.parseInt(s);

-        } catch (NumberFormatException e) {

-            return defaultValue;

-        }

-    }

-

-    /**

-     * Retrieves the value of that XML element as a long.

-     * Returns the default value when the element is missing or is not an integer.

-     */

-    public static long getXmlLong(Node node, String xmlLocalName, long defaultValue) {

-        String s = getXmlString(node, xmlLocalName);

-        try {

-            return Long.parseLong(s);

-        } catch (NumberFormatException e) {

-            return defaultValue;

-        }

-    }

-

-    /**

-     * Retrieve an attribute which value must match one of the given enums using a

-     * case-insensitive name match.

-     *

-     * Returns defaultValue if the attribute does not exist or its value does not match

-     * the given enum values.

-     */

-    public static Object getEnumAttribute(

-            Node archiveNode,

-            String attrName,

-            Object[] values,

-            Object defaultValue) {

-

-        Node attr = archiveNode.getAttributes().getNamedItem(attrName);

-        if (attr != null) {

-            String found = attr.getNodeValue();

-            for (Object value : values) {

-                if (value.toString().equalsIgnoreCase(found)) {

-                    return value;

-                }

-            }

-        }

-

-        return defaultValue;

-    }

-

-}

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java
index cc35e54..fff45b9 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/AddonPackage.java
@@ -25,7 +25,6 @@
 import com.android.sdklib.SdkConstants;

 import com.android.sdklib.SdkManager;

 import com.android.sdklib.internal.repository.IDescription;

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

 import com.android.sdklib.internal.repository.sources.SdkSource;

@@ -46,8 +45,9 @@
 /**

  * Represents an add-on XML node in an SDK repository.

  */

-public class AddonPackage extends Package

-    implements IPackageVersion, IPlatformDependency, IExactApiLevelDependency, ILayoutlibVersion {

+public class AddonPackage extends MajorRevisionPackage

+        implements IAndroidVersionProvider, IPlatformDependency,

+                   IExactApiLevelDependency, ILayoutlibVersion {

 

     private final String mVendorId;

     private final String mVendorDisplay;

@@ -142,12 +142,12 @@
         // that only provide name and vendor. If the addon provides neither set of fields,

         // it will simply not work as expected.

 

-        String nameId   = XmlParserUtils.getXmlString(packageNode,

-                                                      SdkRepoConstants.NODE_NAME_ID);

-        String nameDisp = XmlParserUtils.getXmlString(packageNode,

-                                                      SdkRepoConstants.NODE_NAME_DISPLAY);

-        String name     = XmlParserUtils.getXmlString(packageNode,

-                                                      SdkRepoConstants.NODE_NAME);

+        String nameId   = PackageParserUtils.getXmlString(packageNode,

+                                                          SdkRepoConstants.NODE_NAME_ID);

+        String nameDisp = PackageParserUtils.getXmlString(packageNode,

+                                                          SdkRepoConstants.NODE_NAME_DISPLAY);

+        String name     = PackageParserUtils.getXmlString(packageNode,

+                                                          SdkRepoConstants.NODE_NAME);

 

         // The old <name> is equivalent to the new <name-display>

         if (nameDisp.length() == 0) {

@@ -168,12 +168,12 @@
         // --- vendor id/display ---

         // Same processing for vendor id vs display

 

-        String vendorId   = XmlParserUtils.getXmlString(packageNode,

-                                                        SdkAddonConstants.NODE_VENDOR_ID);

-        String vendorDisp = XmlParserUtils.getXmlString(packageNode,

-                                                        SdkAddonConstants.NODE_VENDOR_DISPLAY);

-        String vendor     = XmlParserUtils.getXmlString(packageNode,

-                                                        SdkAddonConstants.NODE_VENDOR);

+        String vendorId   = PackageParserUtils.getXmlString(packageNode,

+                                                            SdkAddonConstants.NODE_VENDOR_ID);

+        String vendorDisp = PackageParserUtils.getXmlString(packageNode,

+                                                            SdkAddonConstants.NODE_VENDOR_DISPLAY);

+        String vendor     = PackageParserUtils.getXmlString(packageNode,

+                                                            SdkAddonConstants.NODE_VENDOR);

 

         // The old <vendor> is equivalent to the new <vendor-display>

         if (vendorDisp.length() == 0) {

@@ -194,10 +194,12 @@
 

         // --- other attributes

 

-        int apiLevel = XmlParserUtils.getXmlInt(packageNode, SdkAddonConstants.NODE_API_LEVEL, 0);

+        int apiLevel =

+            PackageParserUtils.getXmlInt(packageNode, SdkAddonConstants.NODE_API_LEVEL, 0);

         mVersion = new AndroidVersion(apiLevel, null /*codeName*/);

 

-        mLibs = parseLibs(XmlParserUtils.getFirstChild(packageNode, SdkAddonConstants.NODE_LIBS));

+        mLibs = parseLibs(

+                PackageParserUtils.findChildElement(packageNode, SdkAddonConstants.NODE_LIBS));

 

         mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);

     }

@@ -399,8 +401,8 @@
      * Parses a <lib> element from a <libs> container.

      */

     private Lib parseLib(Node libNode) {

-        return new Lib(XmlParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_NAME),

-                       XmlParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_DESCRIPTION));

+        return new Lib(PackageParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_NAME),

+                       PackageParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_DESCRIPTION));

     }

 

     /** Returns the vendor id, a string, for add-on packages. */

@@ -428,8 +430,8 @@
      * <p/>

      * An add-on has the same {@link AndroidVersion} as the platform it depends on.

      */

-    @Override

-    public @NonNull AndroidVersion getVersion() {

+    @Override @NonNull

+    public AndroidVersion getAndroidVersion() {

         return mVersion;

     }

 

@@ -601,7 +603,7 @@
 

             // check they are the same add-on.

             if (getNameId().equals(newPkg.getNameId()) &&

-                    getVersion().equals(newPkg.getVersion())) {

+                    getAndroidVersion().equals(newPkg.getAndroidVersion())) {

                 // Check the vendor-id field.

                 if (getVendorId().equals(newPkg.getVendorId())) {

                     return true;

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java
index 6a7e9c6..c84c259 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/BrokenPackage.java
@@ -30,7 +30,7 @@
  * Represents an SDK repository package that is incomplete.

  * It has a distinct icon and a specific error that is supposed to help the user on how to fix it.

  */

-public class BrokenPackage extends Package

+public class BrokenPackage extends MajorRevisionPackage

         implements IExactApiLevelDependency, IMinApiLevelDependency {

 

     /**

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java
index 54dfc5e..196f351 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/DocPackage.java
@@ -16,11 +16,11 @@
 

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

 

+import com.android.annotations.NonNull;

 import com.android.sdklib.AndroidVersion;

 import com.android.sdklib.SdkConstants;

 import com.android.sdklib.SdkManager;

 import com.android.sdklib.internal.repository.IDescription;

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

 import com.android.sdklib.internal.repository.sources.SdkSource;

@@ -35,11 +35,11 @@
 /**

  * Represents a doc XML node in an SDK repository.

  * <p/>

- * Note that a doc package has a version and thus implements {@link IPackageVersion}.

+ * Note that a doc package has a version and thus implements {@link IAndroidVersionProvider}.

  * However there is no mandatory dependency that limits installation so this does not

  * implement {@link IPlatformDependency}.

  */

-public class DocPackage extends Package implements IPackageVersion {

+public class DocPackage extends MajorRevisionPackage implements IAndroidVersionProvider {

 

     private final AndroidVersion mVersion;

 

@@ -53,11 +53,16 @@
      *          parameters that vary according to the originating XML schema.

      * @param licenses The licenses loaded from the XML originating document.

      */

-    public DocPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {

+    public DocPackage(SdkSource source,

+            Node packageNode,

+            String nsUri,

+            Map<String,String> licenses) {

         super(source, packageNode, nsUri, licenses);

 

-        int apiLevel = XmlParserUtils.getXmlInt   (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);

-        String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);

+        int apiLevel =

+            PackageParserUtils.getXmlInt   (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);

+        String codeName =

+            PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);

         if (codeName.length() == 0) {

             codeName = null;

         }

@@ -124,8 +129,8 @@
      * Returns the version, for platform, add-on and doc packages.

      * Can be 0 if this is a local package of unknown api-level.

      */

-    @Override

-    public AndroidVersion getVersion() {

+    @Override @NonNull

+    public AndroidVersion getAndroidVersion() {

         return mVersion;

     }

 

@@ -166,12 +171,12 @@
         if (mVersion.isPreview()) {

             return String.format("Documentation for Android '%1$s' Preview SDK, revision %2$s%3$s",

                     mVersion.getCodename(),

-                    getRevision(),

+                    getRevision().toShortString(),

                     isObsolete() ? " (Obsolete)" : "");

         } else {

             return String.format("Documentation for Android SDK, API %1$d, revision %2$s%3$s",

                     mVersion.getApiLevel(),

-                    getRevision(),

+                    getRevision().toShortString(),

                     isObsolete() ? " (Obsolete)" : "");

         }

     }

@@ -190,8 +195,8 @@
         }

 

         if (s.indexOf("revision") == -1) {

-            s += String.format("\nRevision %1$d%2$s",

-                    getRevision(),

+            s += String.format("\nRevision %1$s%2$s",

+                    getRevision().toShortString(),

                     isObsolete() ? " (Obsolete)" : "");

         }

 

@@ -220,8 +225,8 @@
     @Override

     public boolean sameItemAs(Package pkg) {

         if (pkg instanceof DocPackage) {

-            AndroidVersion rev2 = ((DocPackage) pkg).getVersion();

-            return this.getVersion().equals(rev2);

+            AndroidVersion rev2 = ((DocPackage) pkg).getAndroidVersion();

+            return this.getAndroidVersion().equals(rev2);

         }

 

         return false;

@@ -248,12 +253,12 @@
 

         DocPackage replacementDoc = (DocPackage)replacementPackage;

 

-        AndroidVersion replacementVersion = replacementDoc.getVersion();

+        AndroidVersion replacementVersion = replacementDoc.getAndroidVersion();

 

         // Check if they're the same exact (api and codename)

         if (replacementVersion.equals(mVersion)) {

             // exact same version, so check the revision level

-            if (replacementPackage.getRevision() > this.getRevision()) {

+            if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {

                 return UpdateInfo.UPDATE;

             }

         } else {

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java
index cc27853..387ef98 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ExtraPackage.java
@@ -25,7 +25,6 @@
 import com.android.sdklib.internal.repository.IDescription;

 import com.android.sdklib.internal.repository.LocalSdkParser;

 import com.android.sdklib.internal.repository.NullTaskMonitor;

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.internal.repository.archives.Archive;

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

@@ -103,22 +102,25 @@
             Map<String,String> licenses) {

         super(source, packageNode, nsUri, licenses);

 

-        mPath   = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_PATH);

+        mPath   = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_PATH);

 

         // Read name-display, vendor-display and vendor-id, introduced in addon-4.xsd.

         // These are not optional, they are mandatory in addon-4 but we still treat them

         // as optional so that we can fallback on using <vendor> which was the only one

         // defined in addon-3.xsd.

-        String name  = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_NAME_DISPLAY);

-        String vname = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_DISPLAY);

-        String vid   = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_ID);

+        String name  =

+            PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_NAME_DISPLAY);

+        String vname =

+            PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_DISPLAY);

+        String vid   =

+            PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR_ID);

 

         if (vid.length() == 0) {

             // If vid is missing, use the old <vendor> attribute.

             // Note that in a valid XML, vendor-id cannot be an empty string.

             // The only reason vid can be empty is when <vendor-id> is missing, which

             // happens in an addon-3 schema, in which case the old <vendor> needs to be used.

-            String vendor = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR);

+            String vendor = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_VENDOR);

             vid = sanitizeLegacyVendor(vendor);

             if (vname.length() == 0) {

                 vname = vendor;

@@ -137,13 +139,13 @@
         }

         mDisplayName   = name.trim();

 

-        mMinApiLevel = XmlParserUtils.getXmlInt(

+        mMinApiLevel = PackageParserUtils.getXmlInt(

                 packageNode, RepoConstants.NODE_MIN_API_LEVEL, MIN_API_LEVEL_NOT_SPECIFIED);

 

         mProjectFiles = parseProjectFiles(

-                XmlParserUtils.getFirstChild(packageNode, RepoConstants.NODE_PROJECT_FILES));

+                PackageParserUtils.findChildElement(packageNode, RepoConstants.NODE_PROJECT_FILES));

 

-        mOldPaths = XmlParserUtils.getXmlString(packageNode, RepoConstants.NODE_OLD_PATHS);

+        mOldPaths = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_OLD_PATHS);

     }

 

     private String[] parseProjectFiles(Node projectFilesNode) {

@@ -254,10 +256,8 @@
 

         mOldPaths = getProperty(props, PkgProps.EXTRA_OLD_PATHS, null);

 

-        mMinApiLevel = Integer.parseInt(

-            getProperty(props,

-                    PkgProps.EXTRA_MIN_API_LEVEL,

-                    Integer.toString(MIN_API_LEVEL_NOT_SPECIFIED)));

+        mMinApiLevel = getPropertyInt(props, PkgProps.EXTRA_MIN_API_LEVEL,

+                MIN_API_LEVEL_NOT_SPECIFIED);

 

         String projectFiles = getProperty(props, PkgProps.EXTRA_PROJECT_FILES, null);

         ArrayList<String> filePaths = new ArrayList<String>();

@@ -490,9 +490,9 @@
      */

     @Override

     public String getShortDescription() {

-        String s = String.format("%1$s, revision %2$d%3$s",

+        String s = String.format("%1$s, revision %2$s%3$s",

                 getDisplayName(),

-                getRevision(),

+                getRevision().toShortString(),

                 isObsolete() ? " (Obsolete)" : "");  //$NON-NLS-2$

 

         return s;

@@ -506,9 +506,9 @@
      */

     @Override

     public String getLongDescription() {

-        String s = String.format("%1$s, revision %2$d%3$s\nBy %4$s",

+        String s = String.format("%1$s, revision %2$s%3$s\nBy %4$s",

                 getDisplayName(),

-                getRevision(),

+                getRevision().toShortString(),

                 isObsolete() ? " (Obsolete)" : "",  //$NON-NLS-2$

                 getVendorDisplay());

 

@@ -517,8 +517,9 @@
             s += '\n' + d;

         }

 

-        if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {

-            s += String.format("\nRequires tools revision %1$d", getMinToolsRevision());

+        if (!getMinToolsRevision().equals(MIN_TOOLS_REV_NOT_SPECIFIED)) {

+            s += String.format("\nRequires tools revision %1$s",

+                               getMinToolsRevision().toShortString());

         }

 

         if (getMinApiLevel() != MIN_API_LEVEL_NOT_SPECIFIED) {

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
new file mode 100755
index 0000000..40235c2
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevision.java
@@ -0,0 +1,243 @@
+/*

+ * 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 com.android.annotations.NonNull;

+

+import java.util.regex.Matcher;

+import java.util.regex.Pattern;

+

+

+/**

+ * Package multi-part revision number composed of a tuple

+ * (major.minor.micro) and an optional preview revision

+ * (the lack of a preview number indicates it's not a preview

+ *  but a final package.)

+ *

+ *  @see MajorRevision

+ */

+public class FullRevision implements Comparable<FullRevision> {

+

+    public static final int MISSING_MAJOR_REV  = 0;

+    public static final int IMPLICIT_MINOR_REV = 0;

+    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;

+    private final int mPreview;

+

+    public FullRevision(int major) {

+        this(major, 0, 0);

+    }

+

+    public FullRevision(int major, int minor, int micro) {

+        this(major, minor, micro, NOT_A_PREVIEW);

+    }

+

+    public FullRevision(int major, int minor, int micro, int preview) {

+        mMajor = major;

+        mMinor = minor;

+        mMicro = micro;

+        mPreview = preview;

+    }

+

+    public int getMajor() {

+        return mMajor;

+    }

+

+    public int getMinor() {

+        return mMinor;

+    }

+

+    public int getMicro() {

+        return mMicro;

+    }

+

+    public boolean isPreview() {

+        return mPreview > NOT_A_PREVIEW;

+    }

+

+    public int getPreview() {

+        return mPreview;

+    }

+

+    /**

+     * 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".

+     */

+    @Override

+    public String toString() {

+        StringBuilder sb = new StringBuilder();

+        sb.append(mMajor)

+          .append('.').append(mMinor)

+          .append('.').append(mMicro);

+

+        if (mPreview != NOT_A_PREVIEW) {

+            sb.append(" rc").append(mPreview);

+        }

+

+        return sb.toString();

+    }

+

+    /**

+     * Returns the version in a dynamic format "major.minor.micro rc#".

+     * This is similar to {@link #toString()} except it omits minor, micro

+     * or preview versions when they are zero.

+     * For example it would return "18 rc1" instead of "18.0.0 rc1",

+     * or "18.1 rc2" instead of "18.1.0 rc2".

+     */

+    public String toShortString() {

+        StringBuilder sb = new StringBuilder();

+        sb.append(mMajor);

+        if (mMinor > 0 || mMicro > 0) {

+            sb.append('.').append(mMinor);

+        }

+        if (mMicro > 0) {

+            sb.append('.').append(mMicro);

+        }

+        if (mPreview != NOT_A_PREVIEW) {

+            sb.append(" rc").append(mPreview);

+        }

+

+        return sb.toString();

+    }

+

+    @Override

+    public int hashCode() {

+        final int prime = 31;

+        int result = 1;

+        result = prime * result + mMajor;

+        result = prime * result + mMinor;

+        result = prime * result + mMicro;

+        result = prime * result + mPreview;

+        return result;

+    }

+

+    @Override

+    public boolean equals(Object rhs) {

+        if (this == rhs) {

+            return true;

+        }

+        if (rhs == null) {

+            return false;

+        }

+        if (!(rhs instanceof FullRevision)) {

+            return false;

+        }

+        FullRevision other = (FullRevision) rhs;

+        if (mMajor != other.mMajor) {

+            return false;

+        }

+        if (mMinor != other.mMinor) {

+            return false;

+        }

+        if (mMicro != other.mMicro) {

+            return false;

+        }

+        if (mPreview != other.mPreview) {

+            return false;

+        }

+        return true;

+    }

+

+    /**

+     * Trivial comparison of a version, e.g 17.1.2 < 18.0.0.

+     *

+     * Note that preview/release candidate are released before their final version,

+     * so "18.0.0 rc1" comes below "18.0.0". The best way to think of it as if the

+     * lack of preview number was "+inf":

+     * "18.1.2 rc5" => "18.1.2.5" so its less than "18.1.2.+INF" but more than "18.1.1.0"

+     * and more than "18.1.2.4"

+     */

+    @Override

+    public int compareTo(FullRevision rhs) {

+        int delta = mMajor - rhs.mMajor;

+        if (delta != 0) {

+            return delta;

+        }

+

+        delta = mMinor - rhs.mMinor;

+        if (delta != 0) {

+            return delta;

+        }

+

+        delta = mMicro - rhs.mMicro;

+        if (delta != 0) {

+            return delta;

+        }

+

+        int p1 = mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : mPreview;

+        int p2 = rhs.mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : rhs.mPreview;

+        delta = p1 - p2;

+        return delta;

+    }

+

+

+}

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
new file mode 100755
index 0000000..d7b5429
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
@@ -0,0 +1,170 @@
+/*

+ * 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 com.android.sdklib.internal.repository.archives.Archive.Arch;

+import com.android.sdklib.internal.repository.archives.Archive.Os;

+import com.android.sdklib.internal.repository.sources.SdkSource;

+import com.android.sdklib.repository.PkgProps;

+import com.android.sdklib.repository.SdkRepoConstants;

+

+import org.w3c.dom.Node;

+

+import java.util.Map;

+import java.util.Properties;

+

+/**

+ * Represents a package in an SDK repository that has a {@link FullRevision},

+ * which is a multi-part revision number (major.minor.micro) and an optional preview revision.

+ */

+public abstract class FullRevisionPackage extends Package

+        implements IFullRevisionProvider {

+

+    private final FullRevision mPreviewVersion;

+

+    /**

+     * Creates a new package from the attributes and elements of the given XML node.

+     * This constructor should throw an exception if the package cannot be created.

+     *

+     * @param source The {@link SdkSource} where this is loaded from.

+     * @param packageNode The XML element being parsed.

+     * @param nsUri The namespace URI of the originating XML document, to be able to deal with

+     *          parameters that vary according to the originating XML schema.

+     * @param licenses The licenses loaded from the XML originating document.

+     */

+    FullRevisionPackage(SdkSource source,

+            Node packageNode,

+            String nsUri,

+            Map<String,String> licenses) {

+        super(source, packageNode, nsUri, licenses);

+

+        mPreviewVersion = PackageParserUtils.parseFullRevisionElement(

+                PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_REVISION));

+    }

+

+    /**

+     * Manually create a new package with one archive and the given attributes.

+     * This is used to create packages from local directories in which case there must be

+     * one archive which URL is the actual target location.

+     * <p/>

+     * Properties from props are used first when possible, e.g. if props is non null.

+     * <p/>

+     * By design, this creates a package with one and only one archive.

+     */

+    public FullRevisionPackage(

+            SdkSource source,

+            Properties props,

+            int revision,

+            String license,

+            String description,

+            String descUrl,

+            Os archiveOs,

+            Arch archiveArch,

+            String archiveOsPath) {

+        super(source, props, revision, license, description, descUrl,

+                archiveOs, archiveArch, archiveOsPath);

+

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

+

+        FullRevision rev = null;

+        if (revStr != null) {

+            try {

+                rev = FullRevision.parseRevision(revStr);

+            } catch (NumberFormatException ignore) {}

+        }

+        if (rev == null) {

+            rev = new FullRevision(revision);

+        }

+

+        mPreviewVersion = rev;

+    }

+

+    @Override

+    public FullRevision getRevision() {

+        return mPreviewVersion;

+    }

+

+    @Override

+    public void saveProperties(Properties props) {

+        super.saveProperties(props);

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

+    }

+

+    @Override

+    public int hashCode() {

+        final int prime = 31;

+        int result = super.hashCode();

+        result = prime * result + ((mPreviewVersion == null) ? 0 : mPreviewVersion.hashCode());

+        return result;

+    }

+

+    @Override

+    public boolean equals(Object obj) {

+        if (this == obj) {

+            return true;

+        }

+        if (!super.equals(obj)) {

+            return false;

+        }

+        if (!(obj instanceof FullRevisionPackage)) {

+            return false;

+        }

+        FullRevisionPackage other = (FullRevisionPackage) obj;

+        if (mPreviewVersion == null) {

+            if (other.mPreviewVersion != null) {

+                return false;

+            }

+        } else if (!mPreviewVersion.equals(other.mPreviewVersion)) {

+            return false;

+        }

+        return true;

+    }

+

+    /**

+     * Computes whether the given package is a suitable update for the current package.

+     * <p/>

+     * A specific case here is that a release package can update a preview, whereas

+     * a preview can only update another preview.

+     * <p/>

+     * {@inheritDoc}

+     */

+    @Override

+    public UpdateInfo canBeUpdatedBy(Package replacementPackage) {

+        if (replacementPackage == null) {

+            return UpdateInfo.INCOMPATIBLE;

+        }

+

+        // check they are the same item, ignoring the preview bit.

+        if (!sameItemAs(replacementPackage, true /*ignorePreviews*/)) {

+            return UpdateInfo.INCOMPATIBLE;

+        }

+

+        // a preview cannot update a non-preview

+        if (!getRevision().isPreview() && replacementPackage.getRevision().isPreview()) {

+            return UpdateInfo.INCOMPATIBLE;

+        }

+

+        // check revision number

+        if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {

+            return UpdateInfo.UPDATE;

+        }

+

+        // not an upgrade but not incompatible either.

+        return UpdateInfo.NOT_UPDATE;

+    }

+

+}

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPackageVersion.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java
similarity index 84%
rename from sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPackageVersion.java
rename to sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java
index 77a6a1d..e3ab5bd 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPackageVersion.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IAndroidVersionProvider.java
@@ -16,6 +16,7 @@
 

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

 

+import com.android.annotations.NonNull;

 import com.android.sdklib.AndroidVersion;

 

 /**

@@ -26,11 +27,11 @@
  * requested platform is present, whereas this interface denotes that the given package simply

  * has a version, which is not necessarily a dependency.

  */

-public interface IPackageVersion {

+public interface IAndroidVersionProvider {

 

     /**

-     * Returns the version, for platform, add-on and doc packages.

+     * Returns the android version, for platform, add-on and doc packages.

      * Can be 0 if this is a local package of unknown api-level.

      */

-    public abstract AndroidVersion getVersion();

+    public abstract @NonNull AndroidVersion getAndroidVersion();

 }

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java
new file mode 100755
index 0000000..497b2f8
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IFullRevisionProvider.java
@@ -0,0 +1,43 @@
+/*

+ * 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;

+

+

+

+/**

+ * Interface for packages that provide a {@link FullRevision},

+ * which is a multi-part revision number (major.minor.micro) and an optional preview revision.

+ * <p/>

+ * This interface is a tag. It indicates that {@link Package#getRevision()} returns a

+ * {@link FullRevision} instead of a limited {@link MajorRevision}. <br/>

+ * The preview version number is available via {@link Package#getRevision()}.

+ */

+public interface IFullRevisionProvider {

+

+    /**

+     * Returns whether the give package represents the same item as the current package.

+     * <p/>

+     * Two packages are considered the same if they represent the same thing, except for the

+     * revision number.

+     * @param pkg the package to compare

+     * @param ignorePreviews true if 2 packages should be considered the same even if one

+     *    is a preview and the other one is not.

+     * @return true if the item are the same.

+     */

+    public abstract boolean sameItemAs(Package pkg, boolean ignorePreviews);

+

+}

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
index 32468a4..d17b800 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinPlatformToolsDependency.java
@@ -34,7 +34,8 @@
      * Since this is a required attribute in the XML schema, it can only happen when dealing
      * with an invalid repository XML.
      */
-    public static final int MIN_PLATFORM_TOOLS_REV_INVALID = 0;
+    public static final FullRevision MIN_PLATFORM_TOOLS_REV_INVALID =
+        new FullRevision(FullRevision.MISSING_MAJOR_REV);
 
     /**
      * The minimal revision of the tools package required by this package if > 0,
@@ -43,6 +44,6 @@
      * This attribute is mandatory and should not be normally missing.
      * It can only happen when dealing with an invalid repository XML.
      */
-    public abstract int getMinPlatformToolsRevision();
+    public abstract FullRevision getMinPlatformToolsRevision();
 
 }
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
index 76cdd66..064f1d3 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IMinToolsDependency.java
@@ -31,12 +31,12 @@
      * The value of {@link #getMinToolsRevision()} when the
      * {@link SdkRepoConstants#NODE_MIN_TOOLS_REV} was not specified in the XML source.
      */
-    public static final int MIN_TOOLS_REV_NOT_SPECIFIED = 0;
+    public static final FullRevision MIN_TOOLS_REV_NOT_SPECIFIED =
+        new FullRevision(FullRevision.MISSING_MAJOR_REV);
 
     /**
      * The minimal revision of the tools package required by this extra package if > 0,
      * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
      */
-    public abstract int getMinToolsRevision();
-
+    public abstract FullRevision getMinToolsRevision();
 }
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
index a61fbea..9665528 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/IPlatformDependency.java
@@ -25,13 +25,13 @@
  * A package that has this dependency can only be installed if a platform with at least the
  * requested API level is present or installed at the same time.
  * <p/>
- * Note that although this interface looks like {@link IPackageVersion}, it does not convey
- * the same semantic, that is {@link IPackageVersion} does <em>not</em> imply any dependency being
- * a limiting factor as far as installation is concerned.
+ * Note that although this interface looks like {@link IAndroidVersionProvider}, it does
+ * not convey the same semantic since {@link IAndroidVersionProvider} does <em>not</em>
+ * imply any dependency being a limiting factor as far as installation is concerned.
  */
 public interface IPlatformDependency {
 
     /** Returns the version of the platform dependency of this package. */
-    AndroidVersion getVersion();
+    AndroidVersion getAndroidVersion();
 
 }
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
index 5b582c1..2713fe7 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/LayoutlibVersionMixin.java
@@ -16,7 +16,6 @@
 

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

 

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.repository.PkgProps;

 import com.android.sdklib.repository.RepoConstants;

 import com.android.util.Pair;

@@ -51,11 +50,12 @@
         int api = LAYOUTLIB_API_NOT_SPECIFIED;

         int rev = LAYOUTLIB_REV_NOT_SPECIFIED;

 

-        Node layoutlibNode = XmlParserUtils.getFirstChild(pkgNode, RepoConstants.NODE_LAYOUT_LIB);

+        Node layoutlibNode =

+            PackageParserUtils.findChildElement(pkgNode, RepoConstants.NODE_LAYOUT_LIB);

 

         if (layoutlibNode != null) {

-            api = XmlParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_API, 0);

-            rev = XmlParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_REVISION, 0);

+            api = PackageParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_API, 0);

+            rev = PackageParserUtils.getXmlInt(layoutlibNode, RepoConstants.NODE_REVISION, 0);

         }

 

         mLayoutlibVersion = Pair.of(api, rev);

@@ -65,12 +65,10 @@
      * Parses the layoutlib version optionally available in the given {@link Properties}.

      */

     public LayoutlibVersionMixin(Properties props) {

-        int layoutlibApi = Integer.parseInt(

-            Package.getProperty(props, PkgProps.LAYOUTLIB_API,

-                                Integer.toString(LAYOUTLIB_API_NOT_SPECIFIED)));

-        int layoutlibRev = Integer.parseInt(

-                Package.getProperty(props, PkgProps.LAYOUTLIB_REV,

-                                    Integer.toString(LAYOUTLIB_REV_NOT_SPECIFIED)));

+        int layoutlibApi = Package.getPropertyInt(props, PkgProps.LAYOUTLIB_API,

+                                                         LAYOUTLIB_API_NOT_SPECIFIED);

+        int layoutlibRev = Package.getPropertyInt(props, PkgProps.LAYOUTLIB_REV,

+                                                         LAYOUTLIB_REV_NOT_SPECIFIED);

         mLayoutlibVersion = Pair.of(layoutlibApi, layoutlibRev);

     }

 

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
new file mode 100755
index 0000000..9ca9e22
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevision.java
@@ -0,0 +1,56 @@
+/*

+ * 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 com.android.annotations.NonNull;

+

+

+/**

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

+ * <p/>

+ * Contrary to a {@link FullRevision}, a {@link MajorRevision} does not

+ * provide minor, micro and preview revision numbers -- these are all

+ * set to zero.

+ */

+public class MajorRevision extends FullRevision {

+

+    public MajorRevision(int major) {

+        super(major, 0, 0);

+    }

+

+    @Override

+    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
new file mode 100755
index 0000000..1348bb9
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
@@ -0,0 +1,132 @@
+/*

+ * 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 com.android.sdklib.internal.repository.archives.Archive.Arch;

+import com.android.sdklib.internal.repository.archives.Archive.Os;

+import com.android.sdklib.internal.repository.sources.SdkSource;

+import com.android.sdklib.repository.PkgProps;

+import com.android.sdklib.repository.SdkRepoConstants;

+

+import org.w3c.dom.Node;

+

+import java.util.Map;

+import java.util.Properties;

+

+/**

+ * Represents a package in an SDK repository that has a {@link MajorRevision},

+ * which is a single major revision number (not minor, micro or previews).

+ */

+public abstract class MajorRevisionPackage extends Package {

+

+    private final MajorRevision mRevision;

+

+    /**

+     * Creates a new package from the attributes and elements of the given XML node.

+     * This constructor should throw an exception if the package cannot be created.

+     *

+     * @param source The {@link SdkSource} where this is loaded from.

+     * @param packageNode The XML element being parsed.

+     * @param nsUri The namespace URI of the originating XML document, to be able to deal with

+     *          parameters that vary according to the originating XML schema.

+     * @param licenses The licenses loaded from the XML originating document.

+     */

+    MajorRevisionPackage(SdkSource source,

+            Node packageNode,

+            String nsUri,

+            Map<String,String> licenses) {

+        super(source, packageNode, nsUri, licenses);

+

+        mRevision = new MajorRevision(

+                PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_REVISION, 0));

+    }

+

+    /**

+     * Manually create a new package with one archive and the given attributes.

+     * This is used to create packages from local directories in which case there must be

+     * one archive which URL is the actual target location.

+     * <p/>

+     * Properties from props are used first when possible, e.g. if props is non null.

+     * <p/>

+     * By design, this creates a package with one and only one archive.

+     */

+    public MajorRevisionPackage(

+            SdkSource source,

+            Properties props,

+            int revision,

+            String license,

+            String description,

+            String descUrl,

+            Os archiveOs,

+            Arch archiveArch,

+            String archiveOsPath) {

+        super(source, props, revision, license, description, descUrl,

+                archiveOs, archiveArch, archiveOsPath);

+

+        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;

+    }

+

+    /**

+     * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).

+     * Can be 0 if this is a local package of unknown revision.

+     */

+    @Override

+    public FullRevision getRevision() {

+        return mRevision;

+    }

+

+

+    @Override

+    public void saveProperties(Properties props) {

+        super.saveProperties(props);

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

+    }

+

+    @Override

+    public UpdateInfo canBeUpdatedBy(Package replacementPackage) {

+        if (replacementPackage == null) {

+            return UpdateInfo.INCOMPATIBLE;

+        }

+

+        // check they are the same item.

+        if (!sameItemAs(replacementPackage)) {

+            return UpdateInfo.INCOMPATIBLE;

+        }

+

+        // check revision number

+        if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {

+            return UpdateInfo.UPDATE;

+        }

+

+        // not an upgrade but not incompatible either.

+        return UpdateInfo.NOT_UPDATE;

+    }

+

+

+}

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
index 99602c8..12c423e 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
@@ -16,7 +16,6 @@
 

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

 

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

 import com.android.sdklib.internal.repository.sources.SdkSource;

@@ -31,13 +30,13 @@
 /**

  * Represents an XML node in an SDK repository that has a min-tools-rev requirement.

  */

-public abstract class MinToolsPackage extends Package implements IMinToolsDependency {

+public abstract class MinToolsPackage extends MajorRevisionPackage implements IMinToolsDependency {

 

     /**

      * The minimal revision of the tools package required by this extra package, if > 0,

      * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.

      */

-    private final int mMinToolsRevision;

+    private final FullRevision mMinToolsRevision;

 

     /**

      * Creates a new package from the attributes and elements of the given XML node.

@@ -52,9 +51,8 @@
     MinToolsPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {

         super(source, packageNode, nsUri, licenses);

 

-        mMinToolsRevision = XmlParserUtils.getXmlInt(packageNode,

-                SdkRepoConstants.NODE_MIN_TOOLS_REV,

-                MIN_TOOLS_REV_NOT_SPECIFIED);

+        mMinToolsRevision = PackageParserUtils.parseFullRevisionElement(

+            PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_MIN_TOOLS_REV));

     }

 

     /**

@@ -79,10 +77,16 @@
         super(source, props, revision, license, description, descUrl,

                 archiveOs, archiveArch, archiveOsPath);

 

-        mMinToolsRevision = Integer.parseInt(

-            getProperty(props,

-                    PkgProps.MIN_TOOLS_REV,

-                    Integer.toString(MIN_TOOLS_REV_NOT_SPECIFIED)));

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

+

+        FullRevision rev = MIN_TOOLS_REV_NOT_SPECIFIED;

+        if (revStr != null) {

+            try {

+                rev = FullRevision.parseRevision(revStr);

+            } catch (NumberFormatException ignore) {}

+        }

+

+        mMinToolsRevision = rev;

     }

 

     /**

@@ -90,7 +94,7 @@
      * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.

      */

     @Override

-    public int getMinToolsRevision() {

+    public FullRevision getMinToolsRevision() {

         return mMinToolsRevision;

     }

 

@@ -98,9 +102,8 @@
     public void saveProperties(Properties props) {

         super.saveProperties(props);

 

-        if (getMinToolsRevision() != MIN_TOOLS_REV_NOT_SPECIFIED) {

-            props.setProperty(PkgProps.MIN_TOOLS_REV,

-                    Integer.toString(getMinToolsRevision()));

+        if (!getMinToolsRevision().equals(MIN_TOOLS_REV_NOT_SPECIFIED)) {

+            props.setProperty(PkgProps.MIN_TOOLS_REV, getMinToolsRevision().toShortString());

         }

     }

 

@@ -108,7 +111,7 @@
     public int hashCode() {

         final int prime = 31;

         int result = super.hashCode();

-        result = prime * result + mMinToolsRevision;

+        result = prime * result + ((mMinToolsRevision == null) ? 0 : mMinToolsRevision.hashCode());

         return result;

     }

 

@@ -124,7 +127,11 @@
             return false;

         }

         MinToolsPackage other = (MinToolsPackage) obj;

-        if (mMinToolsRevision != other.mMinToolsRevision) {

+        if (mMinToolsRevision == null) {

+            if (other.mMinToolsRevision != null) {

+                return false;

+            }

+        } else if (!mMinToolsRevision.equals(other.mMinToolsRevision)) {

             return false;

         }

         return true;

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java
index 0e2b615..9c6bd44 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/Package.java
@@ -16,6 +16,8 @@
 

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

 

+import com.android.annotations.NonNull;

+import com.android.annotations.Nullable;

 import com.android.annotations.VisibleForTesting;

 import com.android.annotations.VisibleForTesting.Visibility;

 import com.android.sdklib.AndroidVersion;

@@ -23,7 +25,6 @@
 import com.android.sdklib.SdkManager;

 import com.android.sdklib.internal.repository.IDescription;

 import com.android.sdklib.internal.repository.ITaskMonitor;

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.internal.repository.archives.Archive;

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

@@ -58,7 +59,6 @@
  */

 public abstract class Package implements IDescription, Comparable<Package> {

 

-    private final int mRevision;

     private final String mObsolete;

     private final String mLicense;

     private final String mDescription;

@@ -104,17 +104,20 @@
      */

     Package(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {

         mSource = source;

-        mRevision    = XmlParserUtils.getXmlInt   (packageNode, SdkRepoConstants.NODE_REVISION, 0);

-        mDescription = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESCRIPTION);

-        mDescUrl     = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESC_URL);

-        mReleaseNote = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_NOTE);

-        mReleaseUrl  = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_URL);

-        mObsolete    = XmlParserUtils.getOptionalXmlString(

-                                                   packageNode, SdkRepoConstants.NODE_OBSOLETE);

+        mDescription =

+            PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESCRIPTION);

+        mDescUrl     =

+            PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESC_URL);

+        mReleaseNote =

+            PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_NOTE);

+        mReleaseUrl  =

+            PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_URL);

+        mObsolete    =

+            PackageParserUtils.getOptionalXmlString(packageNode, SdkRepoConstants.NODE_OBSOLETE);

 

         mLicense  = parseLicense(packageNode, licenses);

-        mArchives = parseArchives(XmlParserUtils.getFirstChild(

-                                  packageNode, SdkRepoConstants.NODE_ARCHIVES));

+        mArchives = parseArchives(

+                PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_ARCHIVES));

     }

 

     /**

@@ -144,8 +147,6 @@
             descUrl = "";

         }

 

-        mRevision = Integer.parseInt(

-                       getProperty(props, PkgProps.PKG_REVISION, Integer.toString(revision)));

         mLicense     = getProperty(props, PkgProps.PKG_LICENSE,      license);

         mDescription = getProperty(props, PkgProps.PKG_DESC,         description);

         mDescUrl     = getProperty(props, PkgProps.PKG_DESC_URL,     descUrl);

@@ -210,7 +211,11 @@
      * @return The string value of the given key in the properties, or null if the key

      *   isn't found or if {@code props} is null.

      */

-    static String getProperty(Properties props, String propKey, String defaultValue) {

+    @Nullable

+    static String getProperty(

+            @Nullable Properties props,

+            @NonNull String propKey,

+            @Nullable String defaultValue) {

         if (props == null) {

             return defaultValue;

         }

@@ -218,15 +223,38 @@
     }

 

     /**

+     * Utility method that returns an integer property from a {@link Properties} object.

+     * Returns the default value if props is null or if the property is not defined or

+     * cannot be parsed to an integer.

+     *

+     * @param props The {@link Properties} to search into.

+     *   If null, the default value is returned.

+     * @param propKey The name of the property. Must not be null.

+     * @param defaultValue The default value to return if {@code props} is null or if the

+     *   key is not found. Can be null.

+     * @return The integer value of the given key in the properties, or the {@code defaultValue}.

+     */

+    static int getPropertyInt(

+            @Nullable Properties props,

+            @NonNull String propKey,

+            int defaultValue) {

+        String s = props != null ? props.getProperty(propKey, null) : null;

+        if (s != null) {

+            try {

+                return Integer.parseInt(s);

+            } catch (Exception ignore) {}

+        }

+        return defaultValue;

+    }

+

+    /**

      * Save the properties of the current packages in the given {@link Properties} object.

      * These properties will later be give the constructor that takes a {@link Properties} object.

      */

     public void saveProperties(Properties props) {

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

         if (mLicense != null && mLicense.length() > 0) {

             props.setProperty(PkgProps.PKG_LICENSE, mLicense);

         }

-

         if (mDescription != null && mDescription.length() > 0) {

             props.setProperty(PkgProps.PKG_DESC, mDescription);

         }

@@ -243,7 +271,6 @@
         if (mObsolete != null) {

             props.setProperty(PkgProps.PKG_OBSOLETE, mObsolete);

         }

-

         if (mSource != null) {

             props.setProperty(PkgProps.PKG_SOURCE_URL,  mSource.getUrl());

         }

@@ -255,8 +282,8 @@
      * license of this name defined.

      */

     private String parseLicense(Node packageNode, Map<String, String> licenses) {

-        Node usesLicense = XmlParserUtils.getFirstChild(

-                                            packageNode, SdkRepoConstants.NODE_USES_LICENSE);

+        Node usesLicense =

+            PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_USES_LICENSE);

         if (usesLicense != null) {

             Node ref = usesLicense.getAttributes().getNamedItem(SdkRepoConstants.ATTR_REF);

             if (ref != null) {

@@ -297,13 +324,13 @@
     private Archive parseArchive(Node archiveNode) {

         Archive a = new Archive(

                     this,

-                    (Os)   XmlParserUtils.getEnumAttribute(archiveNode, SdkRepoConstants.ATTR_OS,

-                            Os.values(), null),

-                    (Arch) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepoConstants.ATTR_ARCH,

-                            Arch.values(), Arch.ANY),

-                    XmlParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_URL),

-                    XmlParserUtils.getXmlLong  (archiveNode, SdkRepoConstants.NODE_SIZE, 0),

-                    XmlParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_CHECKSUM)

+                    (Os)   PackageParserUtils.getEnumAttribute(

+                            archiveNode, SdkRepoConstants.ATTR_OS, Os.values(), null),

+                    (Arch) PackageParserUtils.getEnumAttribute(

+                            archiveNode, SdkRepoConstants.ATTR_ARCH, Arch.values(), Arch.ANY),

+                    PackageParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_URL),

+                    PackageParserUtils.getXmlLong  (archiveNode, SdkRepoConstants.NODE_SIZE, 0),

+                    PackageParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_CHECKSUM)

                 );

 

         return a;

@@ -328,9 +355,7 @@
      * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).

      * Can be 0 if this is a local package of unknown revision.

      */

-    public int getRevision() {

-        return mRevision;

-    }

+    public abstract FullRevision getRevision();

 

     /**

      * Returns the optional description for all packages (platform, add-on, tool, doc) or

@@ -484,8 +509,8 @@
             sb.append("\n");

         }

 

-        sb.append(String.format("Revision %1$d%2$s",

-                getRevision(),

+        sb.append(String.format("Revision %1$s%2$s",

+                getRevision().toShortString(),

                 isObsolete() ? " (Obsolete)" : ""));

 

         s = getDescUrl();

@@ -615,8 +640,8 @@
      * <p/>

      * Two packages are considered the same if they represent the same thing, except for the

      * revision number.

-     * @param pkg the package to compare

-     * @return true if the item

+     * @param pkg the package to compare.

+     * @return true if the item as equivalent.

      */

     public abstract boolean sameItemAs(Package pkg);

 

@@ -632,27 +657,10 @@
      *

      * @see #sameItemAs(Package)

      */

-    public UpdateInfo canBeUpdatedBy(Package replacementPackage) {

-        if (replacementPackage == null) {

-            return UpdateInfo.INCOMPATIBLE;

-        }

-

-        // check they are the same item.

-        if (sameItemAs(replacementPackage) == false) {

-            return UpdateInfo.INCOMPATIBLE;

-        }

-

-        // check revision number

-        if (replacementPackage.getRevision() > this.getRevision()) {

-            return UpdateInfo.UPDATE;

-        }

-

-        // not an upgrade but not incompatible either.

-        return UpdateInfo.NOT_UPDATE;

-    }

+    public abstract UpdateInfo canBeUpdatedBy(Package replacementPackage);

 

     /**

-     * Returns an ordering like this: <br/>

+     * Returns an ordering <b>suitable for display</b> like this: <br/>

      * - Tools <br/>

      * - Platform-Tools <br/>

      * - Docs. <br/>

@@ -668,6 +676,10 @@
      * Important: this must NOT be used to compare if two packages are the same thing.

      * This is achieved by {@link #sameItemAs(Package)} or {@link #canBeUpdatedBy(Package)}.

      * <p/>

+     * The order done here is suitable for display, and this may not be the appropriate

+     * order when comparing whether packages are equal or of greater revision -- if you need

+     * to compare revisions, then use {@link #getRevision()}{@code .compareTo(rev)} directly.

+     * <p/>

      * This {@link #compareTo(Package)} method is purely an implementation detail to

      * perform the right ordering of the packages in the list of available or installed packages.

      * <p/>

@@ -679,7 +691,8 @@
         String s1 = this.comparisonKey();

         String s2 = other.comparisonKey();

 

-        return s1.compareTo(s2);

+        int r = s1.compareTo(s2);

+        return r;

     }

 

     /**

@@ -728,12 +741,13 @@
 

 

         // We insert the package version here because it is more important

-        // than the revision number. We want package version to be sorted

+        // than the revision number.

+        // In the list display, we want package version to be sorted

         // top-down, so we'll use 10k-api as the sorting key. The day we

-        // get reach 10k APIs, we'll need to revisit this.

+        // reach 10k APIs, we'll need to revisit this.

         sb.append("|v:");                                                       //$NON-NLS-1$

-        if (this instanceof IPackageVersion) {

-            AndroidVersion v = ((IPackageVersion) this).getVersion();

+        if (this instanceof IAndroidVersionProvider) {

+            AndroidVersion v = ((IAndroidVersionProvider) this).getAndroidVersion();

 

             sb.append(String.format("%1$04d.%2$d",                              //$NON-NLS-1$

                     10000 - v.getApiLevel(),

@@ -743,7 +757,19 @@
 

         // Append revision number

         sb.append("|r:");                                                       //$NON-NLS-1$

-        sb.append(String.format("%1$04d", getRevision()));                      //$NON-NLS-1$

+        FullRevision rev = getRevision();

+        sb.append(rev.getMajor()).append('.')

+          .append(rev.getMinor()).append('.')

+          .append(rev.getMicro()).append('.');

+        // Hack: When comparing packages for installation purposes, we want to treat

+        // "final releases" packages as more important than rc/preview packages.

+        // However like for the API level above, when sorting for list display purposes

+        // we want the final release package listed before its rc/preview packages.

+        if (rev.isPreview()) {

+            sb.append(rev.getPreview());

+        } else {

+            sb.append('0'); // 0=Final (!preview), to make "18.0" < "18.1" (18 Final < 18 RC1)

+        }

 

         sb.append('|');

         return sb.toString();

@@ -755,7 +781,7 @@
         int result = 1;

         result = prime * result + Arrays.hashCode(mArchives);

         result = prime * result + ((mObsolete == null) ? 0 : mObsolete.hashCode());

-        result = prime * result + mRevision;

+        result = prime * result + getRevision().hashCode();

         result = prime * result + ((mSource == null) ? 0 : mSource.hashCode());

         return result;

     }

@@ -782,7 +808,7 @@
         } else if (!mObsolete.equals(other.mObsolete)) {

             return false;

         }

-        if (mRevision != other.mRevision) {

+        if (!getRevision().equals(other.getRevision())) {

             return false;

         }

         if (mSource == null) {

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PackageParserUtils.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
new file mode 100755
index 0000000..8bde3d3
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
@@ -0,0 +1,180 @@
+/*

+ * Copyright (C) 2009 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 com.android.sdklib.repository.SdkRepoConstants;

+

+import org.w3c.dom.Node;

+

+/**

+ * Misc utilities to help extracting elements and attributes out of an XML document.

+ */

+public class PackageParserUtils {

+

+    /**

+     * Parses a full revision element such as <revision> or <min-tools-rev>.

+     * This supports both the single-integer format as well as the full revision

+     * format with major/minor/micro/preview sub-elements.

+     *

+     * @param revisionNode The node to parse.

+     * @return A new {@link FullRevision}. If parsing failed, major is set to

+     *  {@link FullRevision#MISSING_MAJOR_REV}.

+     */

+    public static FullRevision parseFullRevisionElement(Node revisionNode) {

+        // This needs to support two modes:

+        // - For repository XSD >= 7, <revision> contains sub-elements such as <major> or <minor>.

+        // - Otherwise for repository XSD < 7, <revision> contains an integer.

+        // The <major> element is mandatory, so it's easy to distinguish between both cases.

+        int major = FullRevision.MISSING_MAJOR_REV,

+            minor = FullRevision.IMPLICIT_MINOR_REV,

+            micro = FullRevision.IMPLICIT_MICRO_REV,

+            preview = FullRevision.NOT_A_PREVIEW;

+

+        if (revisionNode != null) {

+            if (PackageParserUtils.findChildElement(revisionNode,

+                                                    SdkRepoConstants.NODE_MAJOR_REV) != null) {

+                // <revision> has a <major> sub-element, so it's a repository XSD >= 7.

+                major = PackageParserUtils.getXmlInt(revisionNode,

+                        SdkRepoConstants.NODE_MAJOR_REV, FullRevision.MISSING_MAJOR_REV);

+                minor = PackageParserUtils.getXmlInt(revisionNode,

+                        SdkRepoConstants.NODE_MINOR_REV, FullRevision.IMPLICIT_MINOR_REV);

+                micro = PackageParserUtils.getXmlInt(revisionNode,

+                        SdkRepoConstants.NODE_MICRO_REV, FullRevision.IMPLICIT_MICRO_REV);

+                preview = PackageParserUtils.getXmlInt(revisionNode,

+                        SdkRepoConstants.NODE_PREVIEW,   FullRevision.NOT_A_PREVIEW);

+            } else {

+                try {

+                    String majorStr = revisionNode.getTextContent().trim();

+                    major = Integer.parseInt(majorStr);

+                } catch (Exception e) {

+                }

+            }

+        }

+

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

+    }

+

+    /**

+     * Returns the first child element with the given XML local name.

+     * If xmlLocalName is null, returns the very first child element.

+     */

+    public static Node findChildElement(Node node, String xmlLocalName) {

+        if (node != null) {

+            String nsUri = node.getNamespaceURI();

+            for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {

+                if (child.getNodeType() == Node.ELEMENT_NODE &&

+                        nsUri.equals(child.getNamespaceURI())) {

+                    if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) {

+                        return child;

+                    }

+                }

+            }

+        }

+        return null;

+    }

+

+    /**

+     * Retrieves the value of that XML element as a string.

+     * Returns an empty string whether the element is missing or empty,

+     * so you can't tell the difference.

+     * <p/>

+     * Note: use {@link #getOptionalXmlString(Node, String)} if you need to know when the

+     * element is missing versus empty.

+     *

+     * @param node The XML <em>parent</em> node to parse.

+     * @param xmlLocalName The XML local name to find in the parent node.

+     * @return The text content of the element. Returns an empty string whether the element

+     *         is missing or empty, so you can't tell the difference.

+     */

+    public static String getXmlString(Node node, String xmlLocalName) {

+        Node child = findChildElement(node, xmlLocalName);

+

+        return child == null ? "" : child.getTextContent();  //$NON-NLS-1$

+    }

+

+    /**

+     * Retrieves the value of that XML element as a string.

+     * Returns null when the element is missing, so you can tell between a missing element

+     * and an empty one.

+     * <p/>

+     * Note: use {@link #getXmlString(Node, String)} if you don't need to know when the

+     * element is missing versus empty.

+     *

+     * @param node The XML <em>parent</em> node to parse.

+     * @param xmlLocalName The XML local name to find in the parent node.

+     * @return The text content of the element. Returns null when the element is missing.

+     *         Returns an empty string whether the element is present but empty.

+     */

+    public static String getOptionalXmlString(Node node, String xmlLocalName) {

+        Node child = findChildElement(node, xmlLocalName);

+

+        return child == null ? null : child.getTextContent();  //$NON-NLS-1$

+    }

+

+    /**

+     * Retrieves the value of that XML element as an integer.

+     * Returns the default value when the element is missing or is not an integer.

+     */

+    public static int getXmlInt(Node node, String xmlLocalName, int defaultValue) {

+        String s = getXmlString(node, xmlLocalName);

+        try {

+            return Integer.parseInt(s);

+        } catch (NumberFormatException e) {

+            return defaultValue;

+        }

+    }

+

+    /**

+     * Retrieves the value of that XML element as a long.

+     * Returns the default value when the element is missing or is not an integer.

+     */

+    public static long getXmlLong(Node node, String xmlLocalName, long defaultValue) {

+        String s = getXmlString(node, xmlLocalName);

+        try {

+            return Long.parseLong(s);

+        } catch (NumberFormatException e) {

+            return defaultValue;

+        }

+    }

+

+    /**

+     * Retrieve an attribute which value must match one of the given enums using a

+     * case-insensitive name match.

+     *

+     * Returns defaultValue if the attribute does not exist or its value does not match

+     * the given enum values.

+     */

+    public static Object getEnumAttribute(

+            Node archiveNode,

+            String attrName,

+            Object[] values,

+            Object defaultValue) {

+

+        Node attr = archiveNode.getAttributes().getNamedItem(attrName);

+        if (attr != null) {

+            String found = attr.getNodeValue();

+            for (Object value : values) {

+                if (value.toString().equalsIgnoreCase(found)) {

+                    return value;

+                }

+            }

+        }

+

+        return defaultValue;

+    }

+

+}

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java
index 391c32e..9aaee66 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformPackage.java
@@ -16,6 +16,7 @@
 

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

 

+import com.android.annotations.NonNull;

 import com.android.annotations.VisibleForTesting;

 import com.android.annotations.VisibleForTesting.Visibility;

 import com.android.sdklib.AndroidVersion;

@@ -23,7 +24,6 @@
 import com.android.sdklib.SdkConstants;

 import com.android.sdklib.SdkManager;

 import com.android.sdklib.internal.repository.IDescription;

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

 import com.android.sdklib.internal.repository.sources.SdkSource;

@@ -40,7 +40,8 @@
 /**

  * Represents a platform XML node in an SDK repository.

  */

-public class PlatformPackage extends MinToolsPackage implements IPackageVersion, ILayoutlibVersion {

+public class PlatformPackage extends MinToolsPackage

+        implements IAndroidVersionProvider, ILayoutlibVersion {

 

     /** The package version, for platform, add-on and doc packages. */

     private final AndroidVersion mVersion;

@@ -64,20 +65,27 @@
      *          parameters that vary according to the originating XML schema.

      * @param licenses The licenses loaded from the XML originating document.

      */

-    public PlatformPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {

+    public PlatformPackage(

+            SdkSource source,

+            Node packageNode,

+            String nsUri,

+            Map<String,String> licenses) {

         super(source, packageNode, nsUri, licenses);

 

-        mVersionName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_VERSION);

+        mVersionName =

+            PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_VERSION);

 

-        int apiLevel = XmlParserUtils.getXmlInt   (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);

-        String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);

+        int apiLevel =

+            PackageParserUtils.getXmlInt   (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);

+        String codeName =

+            PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);

         if (codeName.length() == 0) {

             codeName = null;

         }

         mVersion = new AndroidVersion(apiLevel, codeName);

 

-        mIncludedAbi = XmlParserUtils.getOptionalXmlString(

-                packageNode, SdkRepoConstants.NODE_ABI_INCLUDED);

+        mIncludedAbi = PackageParserUtils.getOptionalXmlString(packageNode,

+                                        SdkRepoConstants.NODE_ABI_INCLUDED);

 

         mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);

     }

@@ -145,8 +153,8 @@
     }

 

     /** Returns the package version, for platform, add-on and doc packages. */

-    @Override

-    public AndroidVersion getVersion() {

+    @Override @NonNull

+    public AndroidVersion getAndroidVersion() {

         return mVersion;

     }

 

@@ -220,13 +228,13 @@
         if (mVersion.isPreview()) {

             s = String.format("SDK Platform Android %1$s Preview, revision %2$s%3$s",

                     getVersionName(),

-                    getRevision(),

+                    getRevision().toShortString(),

                     isObsolete() ? " (Obsolete)" : "");  //$NON-NLS-2$

         } else {

             s = String.format("SDK Platform Android %1$s, API %2$d, revision %3$s%4$s",

                 getVersionName(),

                 mVersion.getApiLevel(),

-                getRevision(),

+                getRevision().toShortString(),

                 isObsolete() ? " (Obsolete)" : "");      //$NON-NLS-2$

         }

 

@@ -247,8 +255,8 @@
         }

 

         if (s.indexOf("revision") == -1) {

-            s += String.format("\nRevision %1$d%2$s",

-                    getRevision(),

+            s += String.format("\nRevision %1$s%2$s",

+                    getRevision().toShortString(),

                     isObsolete() ? " (Obsolete)" : "");

         }

 

@@ -279,7 +287,7 @@
 

         File platforms = new File(osSdkRoot, SdkConstants.FD_PLATFORMS);

         File folder = new File(platforms,

-                String.format("android-%s", getVersion().getApiString())); //$NON-NLS-1$

+                String.format("android-%s", getAndroidVersion().getApiString())); //$NON-NLS-1$

 

         return folder;

     }

@@ -290,7 +298,7 @@
             PlatformPackage newPkg = (PlatformPackage)pkg;

 

             // check they are the same version.

-            return newPkg.getVersion().equals(this.getVersion());

+            return newPkg.getAndroidVersion().equals(this.getAndroidVersion());

         }

 

         return false;

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
index 6070593..fdacfcc 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/PlatformToolPackage.java
@@ -39,10 +39,12 @@
 /**
  * Represents a platform-tool XML node in an SDK repository.
  */
-public class PlatformToolPackage extends Package {
+public class PlatformToolPackage extends FullRevisionPackage {
 
     /** The value returned by {@link PlatformToolPackage#installId()}. */
     public static final String INSTALL_ID = "platform-tools";                       //$NON-NLS-1$
+    /** The value returned by {@link PlatformToolPackage#installId()}. */
+    public static final String INSTALL_ID_PREVIEW = "platform-tools-preview";       //$NON-NLS-1$
 
     /**
      * Creates a new platform-tool package from the attributes and elements of the given XML node.
@@ -153,13 +155,18 @@
 
     /**
      * Returns a string identifier to install this package from the command line.
-     * For platform-tools, we use "platform-tools" since this package type is unique.
+     * For platform-tools, we use "platform-tools" or "platform-tools-preview" since
+     * this package type is unique.
      * <p/>
      * {@inheritDoc}
      */
     @Override
     public String installId() {
-        return INSTALL_ID;
+        if (getRevision().isPreview()) {
+            return INSTALL_ID_PREVIEW;
+        } else {
+            return INSTALL_ID;
+        }
     }
 
     /**
@@ -178,8 +185,8 @@
      */
     @Override
     public String getShortDescription() {
-        return String.format("Android SDK Platform-tools, revision %1$d%2$s",
-                getRevision(),
+        return String.format("Android SDK Platform-tools, revision %1$s%2$s",
+                getRevision().toShortString(),
                 isObsolete() ? " (Obsolete)" : "");
     }
 
@@ -192,8 +199,8 @@
         }
 
         if (s.indexOf("revision") == -1) {
-            s += String.format("\nRevision %1$d%2$s",
-                    getRevision(),
+            s += String.format("\nRevision %1$s%2$s",
+                    getRevision().toShortString(),
                     isObsolete() ? " (Obsolete)" : "");
         }
 
@@ -215,10 +222,28 @@
         return new File(osSdkRoot, SdkConstants.FD_PLATFORM_TOOLS);
     }
 
+    /**
+     * Check whether 2 platform-tool packages are the same <em>and</em> have the
+     * same preview bit.
+     */
     @Override
     public boolean sameItemAs(Package pkg) {
+        return sameItemAs(pkg, false /*ignorePreviews*/);
+    }
+
+    @Override
+    public boolean sameItemAs(Package pkg, boolean ignorePreviews) {
         // only one platform-tool package so any platform-tool package is the same item.
-        return pkg instanceof PlatformToolPackage;
+        if (pkg instanceof PlatformToolPackage) {
+            if (ignorePreviews) {
+                return true;
+            } else {
+                // however previews can only match previews by default, unless we ignore that check.
+                return ((PlatformToolPackage) pkg).getRevision().isPreview() ==
+                    getRevision().isPreview();
+            }
+        }
+        return false;
     }
 
     /**
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java
index ed5eda5..dcaccf1 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SamplePackage.java
@@ -16,6 +16,7 @@
 

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

 

+import com.android.annotations.NonNull;

 import com.android.sdklib.AndroidVersion;

 import com.android.sdklib.AndroidVersion.AndroidVersionException;

 import com.android.sdklib.IAndroidTarget;

@@ -23,7 +24,6 @@
 import com.android.sdklib.SdkManager;

 import com.android.sdklib.internal.repository.IDescription;

 import com.android.sdklib.internal.repository.ITaskMonitor;

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.internal.repository.archives.Archive;

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

@@ -49,7 +49,7 @@
  * Represents a sample XML node in an SDK repository.

  */

 public class SamplePackage extends MinToolsPackage

-    implements IPackageVersion, IMinApiLevelDependency {

+        implements IAndroidVersionProvider, IMinApiLevelDependency {

 

     /** The matching platform version. */

     private final AndroidVersion mVersion;

@@ -70,18 +70,24 @@
      *          parameters that vary according to the originating XML schema.

      * @param licenses The licenses loaded from the XML originating document.

      */

-    public SamplePackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {

+    public SamplePackage(SdkSource source,

+            Node packageNode,

+            String nsUri,

+            Map<String,String> licenses) {

         super(source, packageNode, nsUri, licenses);

 

-        int apiLevel = XmlParserUtils.getXmlInt   (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);

-        String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);

+        int apiLevel =

+            PackageParserUtils.getXmlInt   (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);

+        String codeName =

+            PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);

         if (codeName.length() == 0) {

             codeName = null;

         }

         mVersion = new AndroidVersion(apiLevel, codeName);

 

-        mMinApiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_MIN_API_LEVEL,

-                MIN_API_LEVEL_NOT_SPECIFIED);

+        mMinApiLevel = PackageParserUtils.getXmlInt(packageNode,

+                    SdkRepoConstants.NODE_MIN_API_LEVEL,

+                    MIN_API_LEVEL_NOT_SPECIFIED);

     }

 

     /**

@@ -115,10 +121,8 @@
 

         mVersion = target.getVersion();

 

-        mMinApiLevel = Integer.parseInt(

-            getProperty(props,

-                    PkgProps.SAMPLE_MIN_API_LEVEL,

-                    Integer.toString(MIN_API_LEVEL_NOT_SPECIFIED)));

+        mMinApiLevel = getPropertyInt(props, PkgProps.SAMPLE_MIN_API_LEVEL,

+                                             MIN_API_LEVEL_NOT_SPECIFIED);

     }

 

     /**

@@ -133,7 +137,8 @@
      * @throws AndroidVersionException if the {@link AndroidVersion} can't be restored

      *                                 from properties.

      */

-    public static Package create(String archiveOsPath, Properties props) throws AndroidVersionException {

+    public static Package create(String archiveOsPath, Properties props)

+            throws AndroidVersionException {

         return new SamplePackage(archiveOsPath, props);

     }

 

@@ -151,10 +156,8 @@
 

         mVersion = new AndroidVersion(props);

 

-        mMinApiLevel = Integer.parseInt(

-            getProperty(props,

-                    PkgProps.SAMPLE_MIN_API_LEVEL,

-                    Integer.toString(MIN_API_LEVEL_NOT_SPECIFIED)));

+        mMinApiLevel = getPropertyInt(props, PkgProps.SAMPLE_MIN_API_LEVEL,

+                                             MIN_API_LEVEL_NOT_SPECIFIED);

     }

 

     /**

@@ -182,8 +185,8 @@
     }

 

     /** Returns the matching platform version. */

-    @Override

-    public AndroidVersion getVersion() {

+    @Override @NonNull

+    public AndroidVersion getAndroidVersion() {

         return mVersion;

     }

 

@@ -217,10 +220,10 @@
      */

     @Override

     public String getShortDescription() {

-        String s = String.format("Samples for SDK API %1$s%2$s, revision %3$d%4$s",

+        String s = String.format("Samples for SDK API %1$s%2$s, revision %3$s%4$s",

                 mVersion.getApiString(),

                 mVersion.isPreview() ? " Preview" : "",

-                getRevision(),

+                getRevision().toShortString(),

                 isObsolete() ? " (Obsolete)" : "");

         return s;

     }

@@ -239,8 +242,8 @@
         }

 

         if (s.indexOf("revision") == -1) {

-            s += String.format("\nRevision %1$d%2$s",

-                    getRevision(),

+            s += String.format("\nRevision %1$s%2$s",

+                    getRevision().toShortString(),

                     isObsolete() ? " (Obsolete)" : "");

         }

 

@@ -284,12 +287,12 @@
 

         // Otherwise, get a suitable default

         File folder = new File(samplesRoot,

-                String.format("android-%s", getVersion().getApiString())); //$NON-NLS-1$

+                String.format("android-%s", getAndroidVersion().getApiString())); //$NON-NLS-1$

 

         for (int n = 1; folder.exists(); n++) {

             // Keep trying till we find an unused directory.

             folder = new File(samplesRoot,

-                    String.format("android-%s_%d", getVersion().getApiString(), n)); //$NON-NLS-1$

+                    String.format("android-%s_%d", getAndroidVersion().getApiString(), n)); //$NON-NLS-1$

         }

 

         return folder;

@@ -301,7 +304,7 @@
             SamplePackage newPkg = (SamplePackage)pkg;

 

             // check they are the same version.

-            return newPkg.getVersion().equals(this.getVersion());

+            return newPkg.getAndroidVersion().equals(this.getAndroidVersion());

         }

 

         return false;

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java
index 2c577e4..1ba2d13 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SourcePackage.java
@@ -16,6 +16,7 @@
 

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

 

+import com.android.annotations.NonNull;

 import com.android.annotations.VisibleForTesting;

 import com.android.annotations.VisibleForTesting.Visibility;

 import com.android.sdklib.AndroidVersion;

@@ -24,7 +25,6 @@
 import com.android.sdklib.SdkManager;

 import com.android.sdklib.internal.repository.IDescription;

 import com.android.sdklib.internal.repository.ITaskMonitor;

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.internal.repository.archives.Archive;

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

@@ -42,11 +42,11 @@
 /**

  * Represents a source XML node in an SDK repository.

  * <p/>

- * Note that a source package has a version and thus implements {@link IPackageVersion}.

+ * Note that a source package has a version and thus implements {@link IAndroidVersionProvider}.

  * However there is no mandatory dependency that limits installation so this does not

  * implement {@link IPlatformDependency}.

  */

-public class SourcePackage extends Package implements IPackageVersion {

+public class SourcePackage extends MajorRevisionPackage implements IAndroidVersionProvider {

 

     /** The package version, for platform, add-on and doc packages. */

     private final AndroidVersion mVersion;

@@ -67,8 +67,10 @@
             Map<String,String> licenses) {

         super(source, packageNode, nsUri, licenses);

 

-        int apiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);

-        String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);

+        int apiLevel =

+            PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);

+        String codeName =

+            PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);

         if (codeName.length() == 0) {

             codeName = null;

         }

@@ -184,8 +186,8 @@
     /**

      * Returns the android version of this package.

      */

-    @Override

-    public AndroidVersion getVersion() {

+    @Override @NonNull

+    public AndroidVersion getAndroidVersion() {

         return mVersion;

     }

 

@@ -226,12 +228,12 @@
         if (mVersion.isPreview()) {

             return String.format("Sources for Android '%1$s' Preview SDK, revision %2$s%3$s",

                 mVersion.getCodename(),

-                getRevision(),

+                getRevision().toShortString(),

                 isObsolete() ? " (Obsolete)" : "");

         } else {

             return String.format("Sources for Android SDK, API %1$d, revision %2$s%3$s",

                 mVersion.getApiLevel(),

-                getRevision(),

+                getRevision().toShortString(),

                 isObsolete() ? " (Obsolete)" : "");

         }

     }

@@ -250,8 +252,8 @@
         }

 

         if (s.indexOf("revision") == -1) {

-            s += String.format("\nRevision %1$d%2$s",

-                    getRevision(),

+            s += String.format("\nRevision %1$s%2$s",

+                    getRevision().toShortString(),

                     isObsolete() ? " (Obsolete)" : "");

         }

 

@@ -300,7 +302,7 @@
             SourcePackage newPkg = (SourcePackage)pkg;

 

             // check they are the same version.

-            return getVersion().equals(newPkg.getVersion());

+            return getAndroidVersion().equals(newPkg.getAndroidVersion());

         }

 

         return false;

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
index c862882..b6bf501 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/SystemImagePackage.java
@@ -16,6 +16,7 @@
 

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

 

+import com.android.annotations.NonNull;

 import com.android.annotations.VisibleForTesting;

 import com.android.annotations.VisibleForTesting.Visibility;

 import com.android.sdklib.AndroidVersion;

@@ -24,7 +25,6 @@
 import com.android.sdklib.SdkManager;

 import com.android.sdklib.SystemImage;

 import com.android.sdklib.internal.repository.IDescription;

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

 import com.android.sdklib.internal.repository.sources.SdkSource;

@@ -41,8 +41,8 @@
 /**

  * Represents a system-image XML node in an SDK repository.

  */

-public class SystemImagePackage extends Package

-        implements IPackageVersion, IPlatformDependency {

+public class SystemImagePackage extends MajorRevisionPackage

+        implements IAndroidVersionProvider, IPlatformDependency {

 

     /** The package version, for platform, add-on and doc packages. */

     private final AndroidVersion mVersion;

@@ -66,14 +66,16 @@
             Map<String,String> licenses) {

         super(source, packageNode, nsUri, licenses);

 

-        int apiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);

-        String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);

+        int apiLevel =

+            PackageParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);

+        String codeName =

+            PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);

         if (codeName.length() == 0) {

             codeName = null;

         }

         mVersion = new AndroidVersion(apiLevel, codeName);

 

-        mAbi = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_ABI);

+        mAbi = PackageParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_ABI);

     }

 

     @VisibleForTesting(visibility=Visibility.PRIVATE)

@@ -217,8 +219,8 @@
      * <p/>

      * A system-image has the same {@link AndroidVersion} as the platform it depends on.

      */

-    @Override

-    public AndroidVersion getVersion() {

+    @Override @NonNull

+    public AndroidVersion getAndroidVersion() {

         return mVersion;

     }

 

@@ -253,7 +255,7 @@
         return String.format("%1$s System Image, Android API %2$s, revision %3$s%4$s",

                 getAbiDisplayName(),

                 mVersion.getApiString(),

-                getRevision(),

+                getRevision().toShortString(),

                 isObsolete() ? " (Obsolete)" : "");

     }

 

@@ -271,8 +273,8 @@
         }

 

         if (s.indexOf("revision") == -1) {

-            s += String.format("\nRevision %1$d%2$s",

-                    getRevision(),

+            s += String.format("\nRevision %1$s%2$s",

+                    getRevision().toShortString(),

                     isObsolete() ? " (Obsolete)" : "");

         }

 

@@ -314,7 +316,7 @@
 

             // check they are the same abi and version.

             return getAbi().equals(newPkg.getAbi()) &&

-                    getVersion().equals(newPkg.getVersion());

+                    getAndroidVersion().equals(newPkg.getAndroidVersion());

         }

 

         return false;

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java
index e4a8fe6..8ae469e 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/packages/ToolPackage.java
@@ -22,11 +22,11 @@
 import com.android.sdklib.SdkManager;

 import com.android.sdklib.internal.repository.IDescription;

 import com.android.sdklib.internal.repository.ITaskMonitor;

-import com.android.sdklib.internal.repository.XmlParserUtils;

 import com.android.sdklib.internal.repository.archives.Archive;

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

 import com.android.sdklib.internal.repository.sources.SdkSource;

+import com.android.sdklib.repository.PkgProps;

 import com.android.sdklib.repository.SdkRepoConstants;

 import com.android.sdklib.util.GrabProcessOutput;

 import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;

@@ -43,19 +43,18 @@
 /**

  * Represents a tool XML node in an SDK repository.

  */

-public class ToolPackage extends Package implements IMinPlatformToolsDependency {

+public class ToolPackage extends FullRevisionPackage implements IMinPlatformToolsDependency {

 

     /** The value returned by {@link ToolPackage#installId()}. */

     public static final String INSTALL_ID = "tools";                             //$NON-NLS-1$

-

-    public static final String PROP_MIN_PLATFORM_TOOLS_REV =

-                                                "Platform.MinPlatformToolsRev";  //$NON-NLS-1$

+    /** The value returned by {@link ToolPackage#installId()}. */

+    private static final String INSTALL_ID_PREVIEW = "tools-preview";            //$NON-NLS-1$

 

     /**

      * The minimal revision of the platform-tools package required by this package

      * or {@link #MIN_PLATFORM_TOOLS_REV_INVALID} if the value was missing.

      */

-    private final int mMinPlatformToolsRevision;

+    private final FullRevision mMinPlatformToolsRevision;

 

     /**

      * Creates a new tool package from the attributes and elements of the given XML node.

@@ -67,14 +66,17 @@
      *          parameters that vary according to the originating XML schema.

      * @param licenses The licenses loaded from the XML originating document.

      */

-    public ToolPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {

+    public ToolPackage(SdkSource source,

+            Node packageNode,

+            String nsUri,

+            Map<String,String> licenses) {

         super(source, packageNode, nsUri, licenses);

 

-        mMinPlatformToolsRevision = XmlParserUtils.getXmlInt(

-                packageNode,

-                SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV,

-                MIN_PLATFORM_TOOLS_REV_INVALID);

-        if (mMinPlatformToolsRevision == MIN_PLATFORM_TOOLS_REV_INVALID) {

+        mMinPlatformToolsRevision = PackageParserUtils.parseFullRevisionElement(

+                PackageParserUtils.findChildElement(packageNode,

+                                                    SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV));

+

+        if (mMinPlatformToolsRevision.equals(MIN_PLATFORM_TOOLS_REV_INVALID)) {

             // This revision number is mandatory starting with sdk-repository-3.xsd

             // and did not exist before. Complain if the URI has level >= 3.

 

@@ -142,11 +144,16 @@
                 archiveArch,

                 archiveOsPath);

 

-        mMinPlatformToolsRevision = Integer.parseInt(

-                getProperty(

-                        props,

-                        PROP_MIN_PLATFORM_TOOLS_REV,

-                        Integer.toString(MIN_PLATFORM_TOOLS_REV_INVALID)));

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

+

+        FullRevision rev = MIN_PLATFORM_TOOLS_REV_INVALID;

+        if (revStr != null) {

+            try {

+                rev = FullRevision.parseRevision(revStr);

+            } catch (NumberFormatException ignore) {}

+        }

+

+        mMinPlatformToolsRevision = rev;

     }

 

     /**

@@ -156,19 +163,23 @@
     * This attribute is mandatory and should not be normally missing.

      */

     @Override

-    public int getMinPlatformToolsRevision() {

+    public FullRevision getMinPlatformToolsRevision() {

         return mMinPlatformToolsRevision;

     }

 

     /**

      * Returns a string identifier to install this package from the command line.

-     * For tools, we use "tools" since this package is unique.

+     * For tools, we use "tools" or "tools-preview" since this package is unique.

      * <p/>

      * {@inheritDoc}

      */

     @Override

     public String installId() {

-        return INSTALL_ID;

+        if (getRevision().isPreview()) {

+            return INSTALL_ID_PREVIEW;

+        } else {

+            return INSTALL_ID;

+        }

     }

 

     /**

@@ -187,8 +198,8 @@
      */

     @Override

     public String getShortDescription() {

-        return String.format("Android SDK Tools, revision %1$d%2$s",

-                getRevision(),

+        return String.format("Android SDK Tools, revision %1$s%2$s",

+                getRevision().toShortString(),

                 isObsolete() ? " (Obsolete)" : "");

     }

 

@@ -201,8 +212,8 @@
         }

 

         if (s.indexOf("revision") == -1) {

-            s += String.format("\nRevision %1$d%2$s",

-                    getRevision(),

+            s += String.format("\nRevision %1$s%2$s",

+                    getRevision().toShortString(),

                     isObsolete() ? " (Obsolete)" : "");

         }

 

@@ -224,19 +235,38 @@
         return new File(osSdkRoot, SdkConstants.FD_TOOLS);

     }

 

+    /**

+     * Check whether 2 tool packages are the same <em>and</em> have the

+     * same preview bit.

+     */

     @Override

     public boolean sameItemAs(Package pkg) {

+        // Only one tool package so any tool package is the same item

+        return sameItemAs(pkg, false /*ignorePreviews*/);

+    }

+

+    @Override

+    public boolean sameItemAs(Package pkg, boolean ignorePreviews) {

         // only one tool package so any tool package is the same item.

-        return pkg instanceof ToolPackage;

+        if (pkg instanceof ToolPackage) {

+            if (ignorePreviews) {

+                return true;

+            } else {

+                // however previews can only match previews by default, unless we ignore that check.

+                return ((ToolPackage) pkg).getRevision().isPreview() ==

+                    getRevision().isPreview();

+            }

+        }

+        return false;

     }

 

     @Override

     public void saveProperties(Properties props) {

         super.saveProperties(props);

 

-        if (getMinPlatformToolsRevision() != MIN_PLATFORM_TOOLS_REV_INVALID) {

-            props.setProperty(PROP_MIN_PLATFORM_TOOLS_REV,

-                              Integer.toString(getMinPlatformToolsRevision()));

+        if (!getMinPlatformToolsRevision().equals(MIN_PLATFORM_TOOLS_REV_INVALID)) {

+            props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,

+                    getMinPlatformToolsRevision().toShortString());

         }

     }

 

@@ -313,7 +343,8 @@
     public int hashCode() {

         final int prime = 31;

         int result = super.hashCode();

-        result = prime * result + mMinPlatformToolsRevision;

+        result = prime * result

+                 + ((mMinPlatformToolsRevision == null) ? 0 : mMinPlatformToolsRevision.hashCode());

         return result;

     }

 

@@ -329,7 +360,11 @@
             return false;

         }

         ToolPackage other = (ToolPackage) obj;

-        if (mMinPlatformToolsRevision != other.mMinPlatformToolsRevision) {

+        if (mMinPlatformToolsRevision == null) {

+            if (other.mMinPlatformToolsRevision != null) {

+                return false;

+            }

+        } else if (!mMinPlatformToolsRevision.equals(other.mMinPlatformToolsRevision)) {

             return false;

         }

         return true;

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
index 07c3a86..482655f 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
@@ -20,7 +20,7 @@
 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

 import com.android.sdklib.internal.repository.packages.Package;

-import com.android.sdklib.internal.repository.XmlParserUtils;

+import com.android.sdklib.internal.repository.packages.PackageParserUtils;

 import com.android.sdklib.repository.RepoConstants;

 import com.android.sdklib.repository.SdkRepoConstants;

 

@@ -340,14 +340,14 @@
                                                 prefix,

                                                 RepoConstants.NODE_ARCHIVE)) != null) {

                         try {

-                            Os os = (Os) XmlParserUtils.getEnumAttribute(archive,

-                                    RepoConstants.ATTR_OS,

-                                    Os.values(),

-                                    null /*default*/);

-                            Arch arch = (Arch) XmlParserUtils.getEnumAttribute(archive,

-                                    RepoConstants.ATTR_ARCH,

-                                    Arch.values(),

-                                    Arch.ANY);

+                            Os os = (Os) PackageParserUtils.getEnumAttribute(archive,

+                                            RepoConstants.ATTR_OS,

+                                            Os.values(),

+                                            null /*default*/);

+                            Arch arch = (Arch) PackageParserUtils.getEnumAttribute(archive,

+                                            RepoConstants.ATTR_ARCH,

+                                            Arch.values(),

+                                            Arch.ANY);

                             if (os == null || !os.isCompatible() ||

                                     arch == null || !arch.isCompatible()) {

                                 continue;

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-addon-5.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-addon-5.xsd
new file mode 100755
index 0000000..6250789
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-addon-5.xsd
@@ -0,0 +1,441 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+<xsd:schema
+    targetNamespace="http://schemas.android.com/sdk/android/addon/5"
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+    xmlns:sdk="http://schemas.android.com/sdk/android/addon/5"
+    elementFormDefault="qualified"
+    attributeFormDefault="unqualified"
+    version="1">
+
+    <!-- The repository contains a collection of downloadable items known as
+         "packages". Each package has a type and various attributes and contains
+         a list of file "archives" that can be downloaded for specific OSes.
+
+         An Android Addon repository is a web site that contains an "addon.xml"
+         file that conforms to this XML Schema.
+
+         History:
+         - v1 is used by the SDK Updater in Tools r8. It is split out of the
+           main SDK Repository XML Schema and can only contain <addon> and
+           <extra> packages.
+
+         - v2 is used by the SDK Updater in Tools r12.
+            - <extra> element now has a <project-files> element that contains 1 or
+              or more <path>, each indicating the relative path of a file that this package
+              can contribute to installed projects.
+            - <addon> element now has an optional <layoutlib> that indicates the API
+              and revision of the layout library for this particular add-on, if any.
+
+         - v3 is used by the SDK Manager in Tools r14:
+            - <extra> now has an <old-paths> element, a ;-separated list of old paths that
+              should be detected and migrated to the new <path> for that package.
+
+         - v4 is used by the SDK Manager in Tools r18:
+            - <extra> and <addon> are not in the Repository XSD v6 anymore.
+            - <extra> get a new field <name-display>, which is used by the SDK Manager to
+              customize the name of the extra in the list display. The single <vendor>
+              field becomes <vendor-id> and <vendor-display>, the id being used internally
+              and the display in the UI.
+            - <addon> does the same, where <name> is replaced by <name-id> and <name-display>
+              and <vendor> is replaced by <vendor-id> and <vendor-display>.
+
+         - v5 is used by the SDK Manager in Tools r20:
+            - The <beta-rc> element is no longer supported. It was never implemented anyway.
+            - For <tool> and <platform-tool> packages, the <revision> element becomes a
+              a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+            - <min-tools-rev> for <extra> becomes a full revision element.
+    -->
+
+    <xsd:element name="sdk-addon" type="sdk:repositoryType" />
+
+    <xsd:complexType name="repositoryType">
+        <xsd:annotation>
+            <xsd:documentation>
+                The repository contains a collection of downloadable packages.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="add-on"  type="sdk:addonType"        />
+            <xsd:element name="extra"   type="sdk:extraType"        />
+            <xsd:element name="license" type="sdk:licenseType"      />
+        </xsd:choice>
+    </xsd:complexType>
+
+    <!-- The definition of an SDK Add-on package. -->
+
+    <xsd:complexType name="addonType">
+        <xsd:annotation>
+            <xsd:documentation>An SDK add-on package.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The internal name id of the add-on. Must be unique per vendor. -->
+            <xsd:element name="name-id"         type="sdk:idType" />
+            <!-- The displayed name of the add-on. -->
+            <xsd:element name="name-display"    type="xsd:normalizedString" />
+
+            <!-- The internal vendor id of the add-on. Must be unique amongst vendors. -->
+            <xsd:element name="vendor-id"       type="sdk:idType" />
+            <!-- The displayed vendor name of the add-on. -->
+            <xsd:element name="vendor-display"  type="xsd:normalizedString" />
+
+            <!-- The Android API Level for the add-on. An int > 0. -->
+            <xsd:element name="api-level"    type="xsd:positiveInteger"  />
+            <!-- Note: Add-ons do not support 'codenames' (a.k.a. API previews). -->
+            <!-- The revision, an int > 0, incremented each time a new
+                 package is generated. -->
+            <xsd:element name="revision"     type="xsd:positiveInteger" />
+
+            <!-- An add-on can declare 0 or more libraries.
+                 This element is mandatory but it can be empty.
+            -->
+
+            <xsd:element name="libs">
+                <xsd:complexType>
+                    <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+                        <xsd:element name="lib">
+                            <xsd:complexType>
+                                <xsd:all>
+                                    <!-- The name of the library. -->
+                                    <xsd:element name="name" type="xsd:normalizedString" />
+                                    <!-- The optional description of this add-on library. -->
+                                    <xsd:element name="description" type="xsd:string" minOccurs="0" />
+                                </xsd:all>
+                            </xsd:complexType>
+                        </xsd:element>
+                    </xsd:sequence>
+                </xsd:complexType>
+            </xsd:element>
+
+            <!-- optional elements -->
+
+            <!-- The optional license of this package. If present, users will have
+                 to agree to it before downloading. -->
+            <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+            <!-- The optional description of this package. -->
+            <xsd:element name="description"  type="xsd:string"      minOccurs="0" />
+            <!-- The optional description URL of this package -->
+            <xsd:element name="desc-url"     type="xsd:token"       minOccurs="0" />
+            <!-- The optional release note for this package. -->
+            <xsd:element name="release-note" type="xsd:string"      minOccurs="0" />
+            <!-- The optional release note URL of this package -->
+            <xsd:element name="release-url"  type="xsd:token"       minOccurs="0" />
+            <!-- A list of file archives for this package. -->
+            <xsd:element name="archives"     type="sdk:archivesType" />
+
+            <!-- An optional element indicating the package is obsolete.
+                 The string content is however currently not defined and ignored. -->
+            <xsd:element name="obsolete"  type="xsd:string" minOccurs="0" />
+
+            <!-- Optional information on the layoutlib packaged in this platform. -->
+            <xsd:element name="layoutlib" type="sdk:layoutlibType"  minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <xsd:simpleType name="idType">
+        <xsd:annotation>
+            <xsd:documentation>
+                An ID string for an addon/extra name-id or vendor-id
+                can only be simple alphanumeric string.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:restriction base="xsd:token">
+            <xsd:pattern value="[a-zA-Z0-9_-]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+
+    <!-- The definition of a layout library used by an addon. -->
+
+    <xsd:complexType name="layoutlibType" >
+        <xsd:annotation>
+            <xsd:documentation>
+                Version information for a layoutlib included in an addon.
+            .</xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The layoutlib API level, an int > 0,
+                 incremented with each new incompatible lib. -->
+            <xsd:element name="api"          type="xsd:positiveInteger" />
+            <!-- The incremental minor revision for that API, e.g. in case of bug fixes.
+                 Optional. An int >= 0, assumed to be 0 if the element is missing. -->
+            <xsd:element name="revision"     type="xsd:nonNegativeInteger" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- The definition of an SDK extra package. This kind of package is for
+         "free" content. Such packages are installed in SDK/extras/vendor/path.
+    -->
+
+    <xsd:complexType name="extraType" >
+        <xsd:annotation>
+            <xsd:documentation>
+                An SDK extra package. This kind of package is for "free" content.
+                Such packages are installed in SDK/vendor/path.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The displayed name of the extra. -->
+            <xsd:element name="name-display"    type="xsd:normalizedString" />
+
+            <!-- The internal vendor id of the extra. Must be unique amongst vendors. -->
+            <xsd:element name="vendor-id"       type="sdk:idType" />
+            <!-- The displayed vendor name of the extra. -->
+            <xsd:element name="vendor-display"  type="xsd:normalizedString" />
+
+            <!-- The install path sub-folder name. It must not be empty. -->
+            <xsd:element name="path" type="sdk:segmentType" />
+
+            <!-- A semi-colon separated list of "obsolete" path names which are equivalent
+                 to the current 'path' name. When a package is seen using an old-paths' name,
+                 the package manager will try to upgrade it to the new path. -->
+            <xsd:element name="old-paths" type="sdk:segmentListType"  minOccurs="0" />
+
+            <!-- The revision, an int > 0, incremented each time a new
+                 package is generated. -->
+            <xsd:element name="revision"     type="xsd:positiveInteger" />
+
+            <!-- A list of file archives for this package. -->
+            <xsd:element name="archives"     type="sdk:archivesType" />
+
+            <!--  optional elements -->
+
+            <!-- The optional license of this package. If present, users will have
+                 to agree to it before downloading. -->
+            <xsd:element name="uses-license" type="sdk:usesLicenseType"  minOccurs="0" />
+            <!-- The optional description of this package. -->
+            <xsd:element name="description"  type="xsd:string"           minOccurs="0" />
+            <!-- The optional description URL of this package -->
+            <xsd:element name="desc-url"     type="xsd:token"            minOccurs="0" />
+            <!-- The optional release note for this package. -->
+            <xsd:element name="release-note" type="xsd:string"           minOccurs="0" />
+            <!-- The optional release note URL of this package -->
+            <xsd:element name="release-url"  type="xsd:token"            minOccurs="0" />
+            <!-- The minimal revision of tools required by this package.
+                 Optional. If present, must be a revision element. -->
+            <xsd:element name="min-tools-rev" type="sdk:revisionType"    minOccurs="0" />
+            <!-- The minimal API level required by this package.
+                 Optional. If present, must be an int > 0. -->
+            <xsd:element name="min-api-level" type="xsd:positiveInteger" minOccurs="0" />
+
+            <!-- An optional element indicating the package is obsolete.
+                 The string content is however currently not defined and ignored. -->
+            <xsd:element name="obsolete"      type="xsd:string" minOccurs="0" />
+
+            <!-- A list of project files contributed by this package. Optional. -->
+            <xsd:element name="project-files" type="sdk:projectFilesType" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- A full revision, with a major.minor.micro and an optional preview number.
+         The major number is mandatory, the other elements are optional.
+     -->
+
+    <xsd:complexType name="revisionType">
+        <xsd:annotation>
+            <xsd:documentation>
+                A full revision, with a major.minor.micro and an
+                optional preview number. The major number is mandatory.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The major revision, an int > 0, incremented each time a new
+                 package is generated. -->
+            <xsd:element name="major"      type="xsd:positiveInteger" />
+            <!-- The minor revision, an int >= 0, incremented each time a new
+                 minor package is generated. Assumed to be 0 if missing. -->
+            <xsd:element name="minor"     type="xsd:nonNegativeInteger" minOccurs="0" />
+            <!-- The micro revision, an int >= 0, incremented each time a new
+                 buf fix is generated. Assumed to be 0 if missing. -->
+            <xsd:element name="micro"     type="xsd:nonNegativeInteger" minOccurs="0" />
+            <!-- The preview/release candidate revision, an int > 0,
+                 incremented each time a new preview is generated.
+                 Not present for final releases. -->
+            <xsd:element name="preview"   type="xsd:positiveInteger" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- The definition of a path segment used by the extra element. -->
+
+    <xsd:simpleType name="segmentType">
+        <xsd:annotation>
+            <xsd:documentation>
+                One path segment for the install path of an extra element.
+                It must be a single-segment path. It must not be empty.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:restriction base="xsd:token">
+            <xsd:pattern value="[a-zA-Z0-9_]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:simpleType name="segmentListType">
+        <xsd:annotation>
+            <xsd:documentation>
+                A semi-colon separated list of a segmentTypes.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:restriction base="xsd:token">
+            <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+
+    <!-- The definition of a license to be referenced by the uses-license element. -->
+
+    <xsd:complexType name="licenseType">
+        <xsd:annotation>
+            <xsd:documentation>
+                A license definition. Such a license must be used later as a reference
+                using a uses-license element in one of the package elements.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:simpleContent>
+            <xsd:extension base="xsd:string">
+                <xsd:attribute name="id"   type="xsd:ID" />
+                <xsd:attribute name="type" type="xsd:token" fixed="text" />
+            </xsd:extension>
+        </xsd:simpleContent>
+    </xsd:complexType>
+
+
+    <!-- Type describing the license used by a package.
+         The license MUST be defined using a license node and referenced
+         using the ref attribute of the license element inside a package.
+     -->
+
+    <xsd:complexType name="usesLicenseType">
+        <xsd:annotation>
+            <xsd:documentation>
+                Describes the license used by a package. The license MUST be defined
+                using a license node and referenced using the ref attribute of the
+                license element inside a package.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:attribute name="ref" type="xsd:IDREF" />
+    </xsd:complexType>
+
+
+    <!-- A collection of files that can be downloaded for a given architecture.
+         The <archives> node is mandatory in the repository elements and the
+         collection must have at least one <archive> declared.
+         Each archive is a zip file that will be unzipped in a location that depends
+         on its package type.
+     -->
+
+    <xsd:complexType name="archivesType">
+        <xsd:annotation>
+            <xsd:documentation>
+                A collection of files that can be downloaded for a given architecture.
+                The &lt;archives&gt; node is mandatory in the repository packages and the
+                collection must have at least one &lt;archive&gt; declared.
+                Each archive is a zip file that will be unzipped in a location that depends
+                on its package type.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+            <!-- One archive file -->
+            <xsd:element name="archive">
+                <xsd:complexType>
+                    <!-- Properties of the archive file -->
+                    <xsd:all>
+                        <!-- The size in bytes of the archive to download. -->
+                        <xsd:element name="size"     type="xsd:positiveInteger" />
+                        <!-- The checksum of the archive file. -->
+                        <xsd:element name="checksum" type="sdk:checksumType" />
+                        <!-- The URL is an absolute URL if it starts with http://, https://
+                             or ftp://. Otherwise it is relative to the parent directory that
+                             contains this repository.xml -->
+                        <xsd:element name="url"      type="xsd:token" />
+                    </xsd:all>
+
+                    <!-- Attributes that identify the OS and architecture -->
+                    <xsd:attribute name="os" use="required">
+                        <xsd:simpleType>
+                            <xsd:restriction base="xsd:token">
+                                <xsd:enumeration value="any" />
+                                <xsd:enumeration value="linux" />
+                                <xsd:enumeration value="macosx" />
+                                <xsd:enumeration value="windows" />
+                            </xsd:restriction>
+                        </xsd:simpleType>
+                    </xsd:attribute>
+                    <xsd:attribute name="arch" use="optional">
+                        <xsd:simpleType>
+                            <xsd:restriction base="xsd:token">
+                                <xsd:enumeration value="any" />
+                                <xsd:enumeration value="ppc" />
+                                <xsd:enumeration value="x86" />
+                                <xsd:enumeration value="x86_64" />
+                            </xsd:restriction>
+                        </xsd:simpleType>
+                    </xsd:attribute>
+                </xsd:complexType>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+
+    <!-- A collection of file paths available in an &lt;extra&gt; package
+         that can be installed in an Android project.
+         If present, the &lt;project-files&gt; collection must contain at least one path.
+         Each path is relative to the root directory of the package.
+     -->
+
+    <xsd:complexType name="projectFilesType">
+        <xsd:annotation>
+            <xsd:documentation>
+                A collection of file paths available in an &lt;extra&gt; package
+                that can be installed in an Android project.
+                If present, the &lt;project-files&gt; collection must contain at least one path.
+                Each path is relative to the root directory of the package.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+            <!-- One JAR Path, relative to the root folder of the package. -->
+            <xsd:element name="path" type="xsd:string" />
+        </xsd:sequence>
+    </xsd:complexType>
+
+
+    <!-- The definition of a file checksum -->
+
+    <xsd:simpleType name="sha1Number">
+        <xsd:annotation>
+            <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="([0-9a-fA-F]){40}"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="checksumType">
+        <xsd:annotation>
+            <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:simpleContent>
+            <xsd:extension base="sdk:sha1Number">
+                <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+            </xsd:extension>
+        </xsd:simpleContent>
+    </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-repository-7.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-repository-7.xsd
new file mode 100755
index 0000000..ca41b36
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-repository-7.xsd
@@ -0,0 +1,611 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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.
+-->
+<xsd:schema
+    targetNamespace="http://schemas.android.com/sdk/android/repository/7"
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+    xmlns:sdk="http://schemas.android.com/sdk/android/repository/7"
+    elementFormDefault="qualified"
+    attributeFormDefault="unqualified"
+    version="1">
+
+    <!-- The repository contains a collection of downloadable items known as
+         "packages". Each package has a type and various attributes and contains
+         a list of file "archives" that can be downloaded for specific OSes.
+
+         An Android SDK repository is a web site that contains a "repository.xml"
+         file that conforms to this XML Schema.
+
+         History:
+         - v1 is used by the SDK Updater in Tools r3 and r4.
+
+         - v2 is used by the SDK Updater in Tools r5:
+            - It introduces a new <sample> repository type. Previously samples
+              were included in the <platform> packages. Instead this package is used
+              and and the samples are installed in $SDK/samples.
+            - All repository types have a new <obsolete> node. It works as a marker
+              to indicate the package is obsolete and should not be selected by default.
+              The UI also hides these out by default.
+
+         - v3 is used by the SDK Updater in Tools r8:
+            - It introduces a new <platform-tool> repository type. Previously platform-specific
+              tools were included in the <platform> packages. Instead this package is used
+              and platform-specific tools are installed in $SDK/platform-tools
+            - There's a new element <min-platform-tools-rev> in <tool>. The tool package now
+              requires that at least some minimal version of <platform-tool> be installed.
+            - It removes the <addon> repository type, which is now in its own XML Schema.
+
+         - v4 is used by the SDK Updater in Tools r12:
+            - <extra> element now has a <project-files> element that contains 1 or
+              or more <path>, each indicating the relative path of a file that this package
+              can contribute to installed projects.
+            - <platform> element now has a mandatory <layoutlib> that indicates the API
+              and revision of that layout library for this particular platform.
+
+         - v5 is used by the SDK Manager in Tools r14:
+            - <extra> now has an <old-paths> element, a ;-separated list of old paths that
+              should be detected and migrated to the new <path> for that package.
+            - <platform> has a new optional <abi-included> that describes the ABI of the
+              system image included in the platform, if any.
+            - New <system-image> package type, to store system images outside of <platform>s.
+            - New <source> package type.
+
+         - v6 is used by the SDK Manager in Tools r18:
+            - <extra> packages are removed. They are served only by the addon XML.
+            - <platform>, <system-image>, <source>, <tool>, <platform-tool>, <doc>
+              and <sample> get a new optional field <beta-rc> which can be used to indicate
+              the package is a Beta Release Candidate and not a final release.
+
+         - v7 is used by the SDK Manager in Tools r20:
+            - For <tool> and <platform-tool> packages, the <revision> element becomes a
+              a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+            - The <beta-rc> element is no longer supported, it is replaced by
+              <revision> -> <preview> and is only for <tool> and <platform-tool> packages.
+            - <min-tools-rev> and <min-platform-tools-rev> also become a full revision element.
+    -->
+
+    <xsd:element name="sdk-repository" type="sdk:repositoryType" />
+
+    <xsd:complexType name="repositoryType">
+        <xsd:annotation>
+            <xsd:documentation>
+                The repository contains a collection of downloadable packages.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:choice minOccurs="0" maxOccurs="unbounded">
+            <xsd:element name="platform"        type="sdk:platformType"     />
+            <xsd:element name="system-image"    type="sdk:systemImageType"  />
+            <xsd:element name="source"          type="sdk:sourceType"       />
+            <xsd:element name="tool"            type="sdk:toolType"         />
+            <xsd:element name="platform-tool"   type="sdk:platformToolType" />
+            <xsd:element name="doc"             type="sdk:docType"          />
+            <xsd:element name="sample"          type="sdk:sampleType"       />
+            <xsd:element name="license"         type="sdk:licenseType"      />
+        </xsd:choice>
+    </xsd:complexType>
+
+    <!-- The definition of an SDK platform package. -->
+
+    <xsd:complexType name="platformType">
+        <xsd:annotation>
+            <xsd:documentation>An SDK platform package.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The Android platform version. It is string such as "1.0". -->
+            <xsd:element name="version"   type="xsd:normalizedString" />
+            <!-- The Android API Level for the platform. An int > 0. -->
+            <xsd:element name="api-level" type="xsd:positiveInteger"  />
+            <!-- The optional codename for this platform, if it's a preview. -->
+            <xsd:element name="codename"  type="xsd:string" minOccurs="0" />
+            <!-- The revision, an int > 0, incremented each time a new
+                 package is generated. -->
+            <xsd:element name="revision"  type="xsd:positiveInteger" />
+
+            <!-- Information on the layoutlib packaged in this platform. -->
+            <xsd:element name="layoutlib" type="sdk:layoutlibType" />
+
+            <!-- optional elements -->
+
+            <!-- The optional license of this package. If present, users will have
+                 to agree to it before downloading. -->
+            <xsd:element name="uses-license"  type="sdk:usesLicenseType" minOccurs="0" />
+            <!-- The optional description of this package. -->
+            <xsd:element name="description"   type="xsd:string"      minOccurs="0" />
+            <!-- The optional description URL of this package -->
+            <xsd:element name="desc-url"      type="xsd:token"       minOccurs="0" />
+            <!-- The optional release note for this package. -->
+            <xsd:element name="release-note"  type="xsd:string"      minOccurs="0" />
+            <!-- The optional release note URL of this package -->
+            <xsd:element name="release-url"   type="xsd:token"       minOccurs="0" />
+            <!-- A list of file archives for this package. -->
+            <xsd:element name="archives"      type="sdk:archivesType" />
+            <!-- The minimal revision of tools required by this package.
+                 Optional. If present, must be a revision element. -->
+            <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+
+            <!-- The ABI of the system image *included* in this platform, if any.
+                 When the field is present, it means the platform already embeds one
+                 system image. A platform can also have any number of external
+                 &lt;system-image&gt; associated with it.  -->
+            <xsd:element name="included-abi"  type="sdk:abiType" minOccurs="0" />
+
+            <!-- An optional element indicating the package is obsolete.
+                 The string content is however currently not defined and ignored. -->
+            <xsd:element name="obsolete"      type="xsd:string" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- The definition of a layout library used by a platform. -->
+
+    <xsd:complexType name="layoutlibType" >
+        <xsd:annotation>
+            <xsd:documentation>
+                Version information for a layoutlib included in a platform.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The layoutlib API level, an int > 0,
+                 incremented with each new incompatible lib. -->
+            <xsd:element name="api"          type="xsd:positiveInteger" />
+            <!-- The incremental minor revision for that API, e.g. in case of bug fixes.
+                 Optional. An int >= 0, assumed to be 0 if the element is missing. -->
+            <xsd:element name="revision"     type="xsd:nonNegativeInteger" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- The definition of a system image used by a platform. -->
+
+    <xsd:complexType name="systemImageType" >
+        <xsd:annotation>
+            <xsd:documentation>
+                System Image for a platform.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- api-level + codename identifies the platform to which this system image belongs. -->
+
+            <!-- The Android API Level for the platform. An int > 0. -->
+            <xsd:element name="api-level" type="xsd:positiveInteger"  />
+            <!-- The optional codename for this platform, if it's a preview. -->
+            <xsd:element name="codename"  type="xsd:string" minOccurs="0" />
+
+            <!-- The revision, an int > 0, incremented each time a new
+                 package is generated. -->
+            <xsd:element name="revision"  type="xsd:positiveInteger" />
+
+            <!-- The ABI of the system emulated by this image. -->
+            <xsd:element name="abi"       type="sdk:abiType" />
+
+            <!-- The optional license of this package. If present, users will have
+                 to agree to it before downloading. -->
+            <xsd:element name="uses-license"  type="sdk:usesLicenseType" minOccurs="0" />
+            <!-- The optional description of this package. -->
+            <xsd:element name="description"   type="xsd:string"      minOccurs="0" />
+            <!-- The optional description URL of this package -->
+            <xsd:element name="desc-url"      type="xsd:token"       minOccurs="0" />
+            <!-- The optional release note for this package. -->
+            <xsd:element name="release-note"  type="xsd:string"      minOccurs="0" />
+            <!-- The optional release note URL of this package -->
+            <xsd:element name="release-url"   type="xsd:token"       minOccurs="0" />
+
+            <!-- A list of file archives for this package. -->
+            <xsd:element name="archives"  type="sdk:archivesType" />
+
+            <!-- An optional element indicating the package is obsolete.
+                 The string content is however currently not defined and ignored. -->
+            <xsd:element name="obsolete"  type="xsd:string" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+    <!-- The definition of the ABI supported by a platform's system image. -->
+
+    <xsd:simpleType name="abiType">
+        <xsd:annotation>
+            <xsd:documentation>The ABI of a platform's system image.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:restriction base="xsd:token">
+            <xsd:enumeration value="armeabi"     />
+            <xsd:enumeration value="armeabi-v7a" />
+            <xsd:enumeration value="x86"         />
+            <xsd:enumeration value="mips"        />
+        </xsd:restriction>
+    </xsd:simpleType>
+
+
+    <!-- The definition of a source package. -->
+
+    <xsd:complexType name="sourceType" >
+        <xsd:annotation>
+            <xsd:documentation>
+                Sources for a platform.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- api-level + codename identifies the platform to which this source belongs. -->
+
+            <!-- The Android API Level for the platform. An int > 0. -->
+            <xsd:element name="api-level" type="xsd:positiveInteger"  />
+            <!-- The optional codename for this platform, if it's a preview. -->
+            <xsd:element name="codename"  type="xsd:string" minOccurs="0" />
+
+            <!-- The revision, an int > 0, incremented each time a new
+                 package is generated. -->
+            <xsd:element name="revision"  type="xsd:positiveInteger" />
+
+            <!-- The optional license of this package. If present, users will have
+                 to agree to it before downloading. -->
+            <xsd:element name="uses-license"  type="sdk:usesLicenseType" minOccurs="0" />
+            <!-- The optional description of this package. -->
+            <xsd:element name="description"   type="xsd:string"      minOccurs="0" />
+            <!-- The optional description URL of this package -->
+            <xsd:element name="desc-url"      type="xsd:token"       minOccurs="0" />
+            <!-- The optional release note for this package. -->
+            <xsd:element name="release-note"  type="xsd:string"      minOccurs="0" />
+            <!-- The optional release note URL of this package -->
+            <xsd:element name="release-url"   type="xsd:token"       minOccurs="0" />
+
+            <!-- A list of file archives for this package. -->
+            <xsd:element name="archives"  type="sdk:archivesType" />
+
+            <!-- An optional element indicating the package is obsolete.
+                 The string content is however currently not defined and ignored. -->
+            <xsd:element name="obsolete"  type="xsd:string" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- The definition of an SDK tool package. -->
+
+    <xsd:complexType name="toolType" >
+        <xsd:annotation>
+            <xsd:documentation>An SDK tool package.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The full revision (major.minor.micro.preview), incremented each
+                 time a new package is generated. -->
+            <xsd:element name="revision"      type="sdk:revisionType" />
+
+            <!-- The optional license of this package. If present, users will have
+                 to agree to it before downloading. -->
+            <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+            <!-- The optional description of this package. -->
+            <xsd:element name="description"  type="xsd:string"      minOccurs="0" />
+            <!-- The optional description URL of this package -->
+            <xsd:element name="desc-url"     type="xsd:token"       minOccurs="0" />
+            <!-- The optional release note for this package. -->
+            <xsd:element name="release-note" type="xsd:string"      minOccurs="0" />
+            <!-- The optional release note URL of this package -->
+            <xsd:element name="release-url"  type="xsd:token"       minOccurs="0" />
+            <!-- A list of file archives for this package. -->
+            <xsd:element name="archives"     type="sdk:archivesType" />
+
+            <!-- The minimal revision of platform-tools required by this package.
+                 Mandatory. Must be a revision element. -->
+            <xsd:element name="min-platform-tools-rev" type="sdk:revisionType" />
+
+            <!-- An optional element indicating the package is obsolete.
+                 The string content is however currently not defined and ignored. -->
+            <xsd:element name="obsolete"  type="xsd:string" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- The definition of an SDK platform-tool package. -->
+
+    <xsd:complexType name="platformToolType" >
+        <xsd:annotation>
+            <xsd:documentation>An SDK platform-tool package.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The full revision (major.minor.micro.preview), incremented each
+                 time a new package is generated. -->
+            <xsd:element name="revision"      type="sdk:revisionType" />
+
+            <!-- The optional license of this package. If present, users will have
+                 to agree to it before downloading. -->
+            <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+            <!-- The optional description of this package. -->
+            <xsd:element name="description"  type="xsd:string"      minOccurs="0" />
+            <!-- The optional description URL of this package -->
+            <xsd:element name="desc-url"     type="xsd:token"       minOccurs="0" />
+            <!-- The optional release note for this package. -->
+            <xsd:element name="release-note" type="xsd:string"      minOccurs="0" />
+            <!-- The optional release note URL of this package -->
+            <xsd:element name="release-url"  type="xsd:token"       minOccurs="0" />
+            <!-- A list of file archives for this package. -->
+            <xsd:element name="archives"     type="sdk:archivesType" />
+
+            <!-- An optional element indicating the package is obsolete.
+                 The string content is however currently not defined and ignored. -->
+            <xsd:element name="obsolete"  type="xsd:string" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- The definition of an SDK doc package. -->
+
+    <xsd:complexType name="docType" >
+        <xsd:annotation>
+            <xsd:documentation>An SDK doc package.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The Android API Level for the documentation. An int > 0. -->
+            <xsd:element name="api-level" type="xsd:positiveInteger"  />
+            <!-- The optional codename for this doc, if it's a preview. -->
+            <xsd:element name="codename"  type="xsd:string" minOccurs="0" />
+
+            <!-- The revision, an int > 0, incremented each time a new
+                 package is generated. -->
+            <xsd:element name="revision"     type="xsd:positiveInteger" />
+            <!-- The optional license of this package. If present, users will have
+                 to agree to it before downloading. -->
+            <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+            <!-- The optional description of this package. -->
+            <xsd:element name="description"  type="xsd:string"      minOccurs="0" />
+            <!-- The optional description URL of this package -->
+            <xsd:element name="desc-url"     type="xsd:token"       minOccurs="0" />
+            <!-- The optional release note for this package. -->
+            <xsd:element name="release-note" type="xsd:string"      minOccurs="0" />
+            <!-- The optional release note URL of this package -->
+            <xsd:element name="release-url"  type="xsd:token"       minOccurs="0" />
+            <!-- A list of file archives for this package. -->
+            <xsd:element name="archives"     type="sdk:archivesType" />
+
+
+            <!-- An optional element indicating the package is obsolete.
+                 The string content is however currently not defined and ignored. -->
+            <xsd:element name="obsolete"  type="xsd:string" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- The definition of an SDK sample package. -->
+
+    <xsd:complexType name="sampleType" >
+        <xsd:annotation>
+            <xsd:documentation>An SDK sample package.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The Android API Level for the documentation. An int > 0. -->
+            <xsd:element name="api-level" type="xsd:positiveInteger"  />
+            <!-- The optional codename for this doc, if it's a preview. -->
+            <xsd:element name="codename"  type="xsd:string" minOccurs="0" />
+
+            <!-- The revision, an int > 0, incremented each time a new
+                 package is generated. -->
+            <xsd:element name="revision"     type="xsd:positiveInteger" />
+            <!-- The optional license of this package. If present, users will have
+                 to agree to it before downloading. -->
+            <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+            <!-- The optional description of this package. -->
+            <xsd:element name="description"  type="xsd:string"      minOccurs="0" />
+            <!-- The optional description URL of this package -->
+            <xsd:element name="desc-url"     type="xsd:token"       minOccurs="0" />
+            <!-- The optional release note for this package. -->
+            <xsd:element name="release-note" type="xsd:string"      minOccurs="0" />
+            <!-- The optional release note URL of this package -->
+            <xsd:element name="release-url"  type="xsd:token"       minOccurs="0" />
+            <!-- A list of file archives for this package. -->
+            <xsd:element name="archives"     type="sdk:archivesType" />
+            <!-- The minimal revision of tools required by this package.
+                 Optional. If present, must be a revision element. -->
+            <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+
+
+            <!-- An optional element indicating the package is obsolete.
+                 The string content is however currently not defined and ignored. -->
+            <xsd:element name="obsolete"  type="xsd:string" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- The definition of a path segment used by the extra element. -->
+
+    <xsd:simpleType name="segmentType">
+        <xsd:annotation>
+            <xsd:documentation>
+                One path segment for the install path of an extra element.
+                It must be a single-segment path.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:restriction base="xsd:token">
+            <xsd:pattern value="[a-zA-Z0-9_]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:simpleType name="segmentListType">
+        <xsd:annotation>
+            <xsd:documentation>
+                A semi-colon separated list of a segmentTypes.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:restriction base="xsd:token">
+            <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+
+    <!-- The definition of a license to be referenced by the uses-license element. -->
+
+    <xsd:complexType name="licenseType">
+        <xsd:annotation>
+            <xsd:documentation>
+                A license definition. Such a license must be used later as a reference
+                using a uses-license element in one of the package elements.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:simpleContent>
+            <xsd:extension base="xsd:string">
+                <xsd:attribute name="id"   type="xsd:ID" />
+                <xsd:attribute name="type" type="xsd:token" fixed="text" />
+            </xsd:extension>
+        </xsd:simpleContent>
+    </xsd:complexType>
+
+
+    <!-- Type describing the license used by a package.
+         The license MUST be defined using a license node and referenced
+         using the ref attribute of the license element inside a package.
+     -->
+
+    <xsd:complexType name="usesLicenseType">
+        <xsd:annotation>
+            <xsd:documentation>
+                Describes the license used by a package. The license MUST be defined
+                using a license node and referenced using the ref attribute of the
+                license element inside a package.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:attribute name="ref" type="xsd:IDREF" />
+    </xsd:complexType>
+
+
+    <!-- A collection of files that can be downloaded for a given architecture.
+         The <archives> node is mandatory in the repository elements and the
+         collection must have at least one <archive> declared.
+         Each archive is a zip file that will be unzipped in a location that depends
+         on its package type.
+     -->
+
+    <xsd:complexType name="archivesType">
+        <xsd:annotation>
+            <xsd:documentation>
+                A collection of files that can be downloaded for a given architecture.
+                The &lt;archives&gt; node is mandatory in the repository packages and the
+                collection must have at least one &lt;archive&gt; declared.
+                Each archive is a zip file that will be unzipped in a location that depends
+                on its package type.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+            <!-- One archive file -->
+            <xsd:element name="archive">
+                <xsd:complexType>
+                    <!-- Properties of the archive file -->
+                    <xsd:all>
+                        <!-- The size in bytes of the archive to download. -->
+                        <xsd:element name="size"     type="xsd:positiveInteger" />
+                        <!-- The checksum of the archive file. -->
+                        <xsd:element name="checksum" type="sdk:checksumType" />
+                        <!-- The URL is an absolute URL if it starts with http://, https://
+                             or ftp://. Otherwise it is relative to the parent directory that
+                             contains this repository.xml -->
+                        <xsd:element name="url"      type="xsd:token" />
+                    </xsd:all>
+
+                    <!-- Attributes that identify the OS and architecture -->
+                    <xsd:attribute name="os" use="required">
+                        <xsd:simpleType>
+                            <xsd:restriction base="xsd:token">
+                                <xsd:enumeration value="any" />
+                                <xsd:enumeration value="linux" />
+                                <xsd:enumeration value="macosx" />
+                                <xsd:enumeration value="windows" />
+                            </xsd:restriction>
+                        </xsd:simpleType>
+                    </xsd:attribute>
+                    <xsd:attribute name="arch" use="optional">
+                        <xsd:simpleType>
+                            <xsd:restriction base="xsd:token">
+                                <xsd:enumeration value="any" />
+                                <xsd:enumeration value="ppc" />
+                                <xsd:enumeration value="x86" />
+                                <xsd:enumeration value="x86_64" />
+                            </xsd:restriction>
+                        </xsd:simpleType>
+                    </xsd:attribute>
+                </xsd:complexType>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+
+    <!-- A full revision, with a major.minor.micro and an optional preview number.
+         The major number is mandatory, the other elements are optional.
+     -->
+
+    <xsd:complexType name="revisionType">
+        <xsd:annotation>
+            <xsd:documentation>
+                A full revision, with a major.minor.micro and an
+                optional preview number. The major number is mandatory.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:all>
+            <!-- The major revision, an int > 0, incremented each time a new
+                 package is generated. -->
+            <xsd:element name="major"      type="xsd:positiveInteger" />
+            <!-- The minor revision, an int >= 0, incremented each time a new
+                 minor package is generated. Assumed to be 0 if missing. -->
+            <xsd:element name="minor"     type="xsd:nonNegativeInteger" minOccurs="0" />
+            <!-- The micro revision, an int >= 0, incremented each time a new
+                 buf fix is generated. Assumed to be 0 if missing. -->
+            <xsd:element name="micro"     type="xsd:nonNegativeInteger" minOccurs="0" />
+            <!-- The preview/release candidate revision, an int > 0,
+                 incremented each time a new preview is generated.
+                 Not present for final releases. -->
+            <xsd:element name="preview"   type="xsd:positiveInteger" minOccurs="0" />
+        </xsd:all>
+    </xsd:complexType>
+
+
+    <!-- A collection of file paths available in an &lt;extra&gt; package
+         that can be installed in an Android project.
+         If present, the &lt;project-files&gt; collection must contain at least one path.
+         Each path is relative to the root directory of the package.
+     -->
+
+    <xsd:complexType name="projectFilesType">
+        <xsd:annotation>
+            <xsd:documentation>
+                A collection of file paths available in an &lt;extra&gt; package
+                that can be installed in an Android project.
+                If present, the &lt;project-files&gt; collection must contain at least one path.
+                Each path is relative to the root directory of the package.
+            </xsd:documentation>
+        </xsd:annotation>
+        <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+            <!-- One JAR Path, relative to the root folder of the package. -->
+            <xsd:element name="path" type="xsd:string" />
+        </xsd:sequence>
+    </xsd:complexType>
+
+
+    <!-- The definition of a file checksum -->
+
+    <xsd:simpleType name="sha1Number">
+        <xsd:annotation>
+            <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:restriction base="xsd:string">
+            <xsd:pattern value="([0-9a-fA-F]){40}"/>
+        </xsd:restriction>
+    </xsd:simpleType>
+
+    <xsd:complexType name="checksumType">
+        <xsd:annotation>
+            <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+        </xsd:annotation>
+        <xsd:simpleContent>
+            <xsd:extension base="sdk:sha1Number">
+                <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+            </xsd:extension>
+        </xsd:simpleContent>
+    </xsd:complexType>
+
+</xsd:schema>
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 579656a..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,7 +29,6 @@
 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$

@@ -44,6 +43,7 @@
     public static final String VERSION_API_LEVEL        = "AndroidVersion.ApiLevel";//$NON-NLS-1$

     public static final String VERSION_CODENAME         = "AndroidVersion.CodeName";//$NON-NLS-1$

 

+

     // AddonPackage

 

     public static final String ADDON_NAME               = "Addon.Name";             //$NON-NLS-1$

@@ -81,7 +81,10 @@
     public static final String PLATFORM_VERSION         = "Platform.Version";       //$NON-NLS-1$

     public static final String PLATFORM_INCLUDED_ABI    = "Platform.Included.Abi";  //$NON-NLS-1$

 

-    // PlatformToolPackage

+    // ToolPackage

+

+    public static final String MIN_PLATFORM_TOOLS_REV   = "Platform.MinPlatformToolsRev";//$NON-NLS-1$

+

 

     // SamplePackage

 

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java
index 52d3a14..703bf1a 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkAddonConstants.java
@@ -46,7 +46,7 @@
      * The latest version of the sdk-addon XML Schema.

      * Valid version numbers are between 1 and this number, included.

      */

-    public static final int NS_LATEST_VERSION = 4;

+    public static final int NS_LATEST_VERSION = 5;

 

     /** The XML namespace of the latest sdk-addon XML. */

     public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java
index 258ea26..5ad6285 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepoConstants.java
@@ -30,7 +30,7 @@
      * The latest version of the sdk-repository XML Schema.

      * Valid version numbers are between 1 and this number, included.

      */

-    public static final int NS_LATEST_VERSION = 6;

+    public static final int NS_LATEST_VERSION = 7;

 

     /**

      * The min version of the sdk-repository XML Schema we'll try to load.

@@ -80,6 +80,23 @@
     /** The root sdk-repository element */

     public static final String NODE_SDK_REPOSITORY = "sdk-repository";        //$NON-NLS-1$

 

+    /* The major revision for tool and platform-tool package

+     * (the full revision number is revision.minor.micro + preview#.)

+     * Mandatory int > 0. 0 when missing, which should not happen in

+     * a valid document. */

+    public static final String NODE_MAJOR_REV       = "major";                //$NON-NLS-1$

+    /* The minor revision for tool and platform-tool package

+     * (the full revision number is revision.minor.micro + preview#.)

+     * Optional int >= 0. Implied to be 0 when missing. */

+    public static final String NODE_MINOR_REV       = "minor";                //$NON-NLS-1$

+    /* The micro revision for tool and platform-tool package

+     * (the full revision number is revision.minor.micro + preview#.)

+     * Optional int >= 0. Implied to be 0 when missing. */

+    public static final String NODE_MICRO_REV       = "micro";                //$NON-NLS-1$

+    /* The preview revision for tool and platform-tool package.

+     * Int > 0, only present for "preview / release candidate" packages. */

+    public static final String NODE_PREVIEW         = "preview";              //$NON-NLS-1$

+

     /** A platform package. */

     public static final String NODE_PLATFORM        = "platform";             //$NON-NLS-1$

     /** A tool package. */

diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd
index 564aaeb..c31efbf 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-4.xsd
@@ -137,7 +137,8 @@
 
             <!-- An optional element indicating the package is a beta/preview.
                  When present, it indicates the release-candidate number.
-                 When the element is absent, it indicates this is a released package. -->
+                 When the element is absent, it indicates this is a released package.
+                 DEPRECATED. TODO remove in sdk-addon-5. -->
             <xsd:element name="beta-rc"   type="xsd:positiveInteger" minOccurs="0" />
 
             <!-- Optional information on the layoutlib packaged in this platform. -->
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd
index 87e183a..bccce69 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-6.xsd
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
- * Copyright (C) 2011 The Android Open Source Project
+ * 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.
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/archives/ArchiveInstallerTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
index 3f70d79..609481b 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
@@ -20,15 +20,11 @@
 import com.android.sdklib.internal.repository.ITaskMonitor;
 import com.android.sdklib.internal.repository.MockEmptySdkManager;
 import com.android.sdklib.internal.repository.MockMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.archives.ArchiveInstaller;
-import com.android.sdklib.internal.repository.archives.ArchiveReplacement;
 import com.android.sdklib.internal.repository.archives.Archive.Arch;
 import com.android.sdklib.internal.repository.archives.Archive.Os;
 import com.android.sdklib.internal.repository.packages.ExtraPackage;
 import com.android.sdklib.internal.repository.packages.MockEmptyPackage;
 import com.android.sdklib.internal.repository.packages.MockExtraPackage;
-import com.android.sdklib.internal.repository.packages.ToolPackage;
 import com.android.sdklib.internal.repository.sources.SdkRepoSource;
 import com.android.sdklib.internal.repository.sources.SdkSource;
 import com.android.sdklib.io.IFileOp;
@@ -366,7 +362,7 @@
             int min_platform_tools_rev) {
         Properties props = new Properties();
         props.setProperty(PkgProps.EXTRA_OLD_PATHS, oldPaths);
-        props.setProperty(ToolPackage.PROP_MIN_PLATFORM_TOOLS_REV,
+        props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,
                 Integer.toString((min_platform_tools_rev)));
         return new MockExtraPackage(source, props, vendor, newPath, revision) {
             @Override
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
new file mode 100755
index 0000000..9bf2703
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionPackageTest.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 com.android.sdklib.repository.PkgProps;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+public class FullRevisionPackageTest extends TestCase {
+
+    /**
+     * Helper that creates the {@link Properties} from a {@link FullRevision}
+     * as expected by {@link FullRevisionPackage}.
+     */
+    public static Properties createProps(FullRevision revision) {
+        Properties props = new Properties();
+        if (revision != null) {
+            props.setProperty(PkgProps.PKG_REVISION, revision.toString());
+        }
+        return props;
+    }
+
+    public void testCompareTo() throws Exception {
+        // Test order of full revision packages.
+        //
+        // Note that Package.compareTo() is designed to return the desired
+        // ordering for a list display and as such a final/release package
+        // needs to be listed before its rc/preview package.
+        //
+        // This differs from the order used by FullRevision.compareTo().
+
+        ArrayList<Package> list = new ArrayList<Package>();
+
+        list.add(new MockToolPackage(null, new FullRevision(1, 0, 0, 0), 8));
+        list.add(new MockToolPackage(null, new FullRevision(1, 0, 0, 1), 8));
+        list.add(new MockToolPackage(null, new FullRevision(1, 0, 1, 0), 8));
+        list.add(new MockToolPackage(null, new FullRevision(1, 0, 1, 1), 8));
+        list.add(new MockToolPackage(null, new FullRevision(1, 1, 0, 0), 8));
+        list.add(new MockToolPackage(null, new FullRevision(1, 1, 0, 1), 8));
+        list.add(new MockToolPackage(null, new FullRevision(2, 1, 1, 0), 8));
+        list.add(new MockToolPackage(null, new FullRevision(2, 1, 1, 1), 8));
+
+        Collections.sort(list);
+
+        assertEquals(
+                 "[Android SDK Tools, revision 1, " +
+                  "Android SDK Tools, revision 1 rc1, " +
+                  "Android SDK Tools, revision 1.0.1, " +
+                  "Android SDK Tools, revision 1.0.1 rc1, " +
+                  "Android SDK Tools, revision 1.1, " +
+                  "Android SDK Tools, revision 1.1 rc1, " +
+                  "Android SDK Tools, revision 2.1.1, " +
+                  "Android SDK Tools, revision 2.1.1 rc1]",
+                Arrays.toString(list.toArray()));
+    }
+}
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
new file mode 100755
index 0000000..d072d05
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/FullRevisionTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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 FullRevisionTest extends TestCase {
+
+    public final void testFullRevision() {
+        FullRevision p = new FullRevision(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, 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());
+        assertEquals(FullRevision.IMPLICIT_MINOR_REV, p.getMinor());
+        assertEquals(FullRevision.IMPLICIT_MICRO_REV, p.getMicro());
+        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());
+        assertEquals(7, p.getMinor());
+        assertEquals(0, p.getMicro());
+        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());
+        assertEquals(11, p.getMinor());
+        assertEquals(12, p.getMicro());
+        assertEquals(0, p.getPreview());
+        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());
+        assertEquals(11, p.getMinor());
+        assertEquals(12, p.getMicro());
+        assertEquals(13, p.getPreview());
+        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() {
+        FullRevision s4 = new FullRevision(4);
+        FullRevision i4 = new FullRevision(4);
+        FullRevision g5 = new FullRevision(5, 1, 0, 6);
+        FullRevision y5 = new FullRevision(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/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/MinToolsPackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
index 00986a4..adffe2e 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MinToolsPackageTest.java
@@ -124,7 +124,7 @@
         Properties props = super.createProps();
 
         // MinToolsPackage properties
-        props.setProperty(PkgProps.MIN_TOOLS_REV, "3");
+        props.setProperty(PkgProps.MIN_TOOLS_REV, "3.0.1");
 
         return props;
     }
@@ -133,6 +133,6 @@
         super.testCreatedPackage(p);
 
         // MinToolsPackage properties
-        assertEquals(3, p.getMinToolsRevision());
+        assertEquals("3.0.1", p.getMinToolsRevision().toShortString());
     }
 }
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
index f1e4344..70f57b4 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockEmptyPackage.java
@@ -30,7 +30,7 @@
  * A mock empty package, of no particular subpackage type.
  * {@link #sameItemAs(Package)} will return true if these packages have the same handle.
  */
-public class MockEmptyPackage extends Package {
+public class MockEmptyPackage extends MajorRevisionPackage {
     private final String mTestHandle;
 
     /**
@@ -134,7 +134,7 @@
     public String getShortDescription() {
         StringBuilder sb = new StringBuilder(this.getClass().getSimpleName());
         sb.append(" '").append(mTestHandle).append('\'');
-        if (getRevision() > 0) {
+        if (getRevision().getMajor() > 0) {
             sb.append(" rev=").append(getRevision());
         }
         return sb.toString();
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockExtraPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
index 6225935..d6a98a0 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
@@ -18,9 +18,8 @@
 

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

-import com.android.sdklib.internal.repository.packages.ExtraPackage;

-import com.android.sdklib.internal.repository.packages.ToolPackage;

 import com.android.sdklib.internal.repository.sources.SdkSource;

+import com.android.sdklib.repository.PkgProps;

 

 import java.util.Properties;

 

@@ -73,7 +72,7 @@
 

     private static Properties createProps(int min_platform_tools_rev) {

         Properties props = new Properties();

-        props.setProperty(ToolPackage.PROP_MIN_PLATFORM_TOOLS_REV,

+        props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,

                           Integer.toString((min_platform_tools_rev)));

         return props;

     }

diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
index fc01b3c..e896de6 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockPlatformToolPackage.java
@@ -18,7 +18,6 @@
 

 import com.android.sdklib.internal.repository.archives.Archive.Arch;

 import com.android.sdklib.internal.repository.archives.Archive.Os;

-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;

 import com.android.sdklib.internal.repository.sources.SdkSource;

 

 /**

@@ -57,5 +56,25 @@
             "foo" // archiveOsPath

             );

     }

+

+    /**

+     * Creates a {@link MockPlatformToolPackage} with the given revision and hardcoded defaults

+     * for everything else.

+     * <p/>

+     * By design, this creates a package with one and only one archive.

+     */

+    public MockPlatformToolPackage(SdkSource source, FullRevision revision) {

+        super(

+                source, // source,

+                FullRevisionPackageTest.createProps(revision), // props,

+                revision.getMajor(),

+                null, // license,

+                "desc", // description,

+                "url", // descUrl,

+                Os.getCurrentOs(), // archiveOs,

+                Arch.getCurrentArch(), // archiveArch,

+                "foo" // archiveOsPath

+                );

+    }

 }

 

diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java
index 534dad8..1726bbd 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockSystemImagePackage.java
@@ -33,12 +33,12 @@
      * By design, this package contains one and only one archive.

      */

     public MockSystemImagePackage(MockPlatformPackage basePlatform, int revision, String abi) {

-        super(basePlatform.getVersion(),

+        super(basePlatform.getAndroidVersion(),

                 revision,

                 abi,

                 null /*props*/,

                 String.format("/sdk/system-images/android-%s/%s",

-                        basePlatform.getVersion().getApiString(), abi));

+                        basePlatform.getAndroidVersion().getApiString(), abi));

     }

 

     /**

@@ -53,11 +53,11 @@
             int revision,

             String abi) {

         super(source,

-                basePlatform.getVersion(),

+                basePlatform.getAndroidVersion(),

                 revision,

                 abi,

                 null /*props*/,

                 String.format("/sdk/system-images/android-%s/%s",

-                        basePlatform.getVersion().getApiString(), abi));

+                        basePlatform.getAndroidVersion().getApiString(), abi));

     }

 }

diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockToolPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockToolPackage.java
index e1da422..155946c 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockToolPackage.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/MockToolPackage.java
@@ -20,6 +20,7 @@
 import com.android.sdklib.internal.repository.archives.Archive.Os;

 import com.android.sdklib.internal.repository.packages.ToolPackage;

 import com.android.sdklib.internal.repository.sources.SdkSource;

+import com.android.sdklib.repository.PkgProps;

 

 import java.util.Properties;

 

@@ -46,10 +47,10 @@
      * <p/>

      * By design, this creates a package with one and only one archive.

      */

-    public MockToolPackage(SdkSource source, int revision, int min_platform_tools_rev) {

+    public MockToolPackage(SdkSource source, int revision, int minPlatformToolsRev) {

         super(

             source, // source,

-            createProps(min_platform_tools_rev), // props,

+            createProps(null /*version*/, minPlatformToolsRev), // props,

             revision,

             null, // license,

             "desc", // description,

@@ -60,10 +61,33 @@
             );

     }

 

-    private static Properties createProps(int min_platform_tools_rev) {

-        Properties props = new Properties();

-        props.setProperty(ToolPackage.PROP_MIN_PLATFORM_TOOLS_REV,

-                          Integer.toString((min_platform_tools_rev)));

+    /**

+     * Creates a {@link MockToolPackage} with the given revision and hardcoded defaults

+     * for everything else.

+     * <p/>

+     * By design, this creates a package with one and only one archive.

+     */

+    public MockToolPackage(

+            SdkSource source,

+            FullRevision revision,

+            int minPlatformToolsRev) {

+        super(

+                source, // source,

+                createProps(revision, minPlatformToolsRev), // props,

+                revision.getMajor(),

+                null, // license,

+                "desc", // description,

+                "url", // descUrl,

+                Os.getCurrentOs(), // archiveOs,

+                Arch.getCurrentArch(), // archiveArch,

+                "foo" // archiveOsPath

+                );

+    }

+

+    private static Properties createProps(FullRevision revision, int minPlatformToolsRev) {

+        Properties props = FullRevisionPackageTest.createProps(revision);

+        props.setProperty(PkgProps.MIN_PLATFORM_TOOLS_REV,

+                          Integer.toString((minPlatformToolsRev)));

         return props;

     }

 }

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 ce41a4d..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
@@ -39,7 +39,7 @@
     protected static final String LOCAL_ARCHIVE_PATH = "/local/archive/path";
 
     /** Local class used to test the abstract Package class */
-    protected static class MockPackage extends Package {
+    protected static class MockPackage extends MajorRevisionPackage {
         public MockPackage(
                 SdkSource source,
                 Properties props,
@@ -155,7 +155,7 @@
      */
     protected void testCreatedPackage(Package p) {
         // Package properties
-        assertEquals(42, p.getRevision());
+        assertEquals("42", p.getRevision().toShortString());
         assertEquals("The License", p.getLicense());
         assertEquals("Some description.", p.getDescription());
         assertEquals("http://description/url", p.getDescUrl());
@@ -197,4 +197,20 @@
                  "Vendor Path, revision 5]",
                 Arrays.toString(list.toArray()));
     }
+
+    public void testGetPropertyInt() {
+        Properties p = new Properties();
+
+        assertEquals(42, Package.getPropertyInt(p, "key", 42));
+
+        p.setProperty("key", "");
+        assertEquals(43, Package.getPropertyInt(p, "key", 43));
+
+        p.setProperty("key", "I am not a number");
+        assertEquals(44, Package.getPropertyInt(p, "key", 44));
+
+        p.setProperty("key", "6");
+        assertEquals(6, Package.getPropertyInt(p, "key", 45));
+    }
+
 }
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
index a0b4ab7..acb57ee 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/PlatformPackageTest.java
@@ -75,7 +75,7 @@
         super.testCreatedPackage(p);
 
         // Package properties
-        assertEquals("API 5", p.getVersion().toString());
+        assertEquals("API 5", p.getAndroidVersion().toString());
         assertEquals("armeabi", p.getIncludedAbi());
     }
 
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SourcePackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
index 66f37dc..d87eab3 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SourcePackageTest.java
@@ -79,7 +79,7 @@
         super.testCreatedPackage(p);
 
         // SourcePackageTest properties
-        assertEquals("API 5", p.getVersion().toString());
+        assertEquals("API 5", p.getAndroidVersion().toString());
     }
 
     // ----
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java
index 14e1c3e..c32b81f 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/packages/SystemImagePackageTest.java
@@ -85,7 +85,7 @@
         super.testCreatedPackage(p);
 
         // SystemImagePackage properties
-        assertEquals("API 5", p.getVersion().toString());
+        assertEquals("API 5", p.getAndroidVersion().toString());
         assertEquals("armeabi-v7a", p.getAbi());
     }
 
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
index b9a3436..d866018 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
@@ -22,6 +22,7 @@
 import com.android.sdklib.internal.repository.MockMonitor;

 import com.android.sdklib.internal.repository.packages.AddonPackage;

 import com.android.sdklib.internal.repository.packages.ExtraPackage;

+import com.android.sdklib.internal.repository.packages.IMinToolsDependency;

 import com.android.sdklib.internal.repository.packages.Package;

 import com.android.sdklib.internal.repository.sources.SdkAddonSource;

 import com.android.sdklib.repository.SdkAddonConstants;

@@ -564,6 +565,154 @@
     }

 

     /**

+     * Validate we can load a valid add-on schema version 5

+     */

+    public void testLoadAddonXml_5() throws Exception {

+        InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_5.xml");

+

+        // guess the version from the XML document

+        int version = mSource._getXmlSchemaVersion(xmlStream);

+        assertEquals(5, version);

+

+        Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };

+        String[] validationError = new String[] { null };

+        String url = "not-a-valid-url://" + SdkAddonConstants.URL_DEFAULT_FILENAME;

+

+        String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);

+        assertEquals(Boolean.TRUE, validatorFound[0]);

+        assertEquals(null, validationError[0]);

+        assertEquals(SdkAddonConstants.getSchemaUri(5), uri);

+

+        // Validation was successful, load the document

+        MockMonitor monitor = new MockMonitor();

+        Document doc = mSource._getDocument(xmlStream, monitor);

+        assertNotNull(doc);

+

+        // Get the packages

+        assertTrue(mSource._parsePackages(doc, uri, monitor));

+

+        assertEquals("Found My First add-on, Android API 1, revision 1\n" +

+                     "Found My Second add-on, Android API 2, revision 42\n" +

+                     "Found This add-on has no libraries, Android API 4, revision 3\n" +

+                     "Found Random name, not an id!, revision 43 (Obsolete)\n" +

+                     "Found Yet another extra, by Android, revision 2\n" +

+                     "Found . -..- - .-. .-, revision 2 (Obsolete)\n",

+                monitor.getCapturedVerboseLog());

+        assertEquals("", monitor.getCapturedLog());

+        assertEquals("", monitor.getCapturedErrorLog());

+

+        // check the packages we found... we expected to find 6 packages with each at least

+        // one archive.

+        // Note the order doesn't necessary match the one from the

+        // assertEquald(getCapturedVerboseLog) because packages are sorted using the

+        // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.

+        Package[] pkgs = mSource.getPackages();

+

+        assertEquals(6, pkgs.length);

+        for (Package p : pkgs) {

+            assertTrue(p.getArchives().length >= 1);

+        }

+

+        // Check the addon packages: vendor/name id vs display

+        ArrayList<String> addonNames   = new ArrayList<String>();

+        ArrayList<String> addonVendors = new ArrayList<String>();

+        for (Package p : pkgs) {

+            if (p instanceof AddonPackage) {

+                AddonPackage ap = (AddonPackage) p;

+                addonNames.add(ap.getNameId() + "/" + ap.getDisplayName());

+                addonVendors.add(ap.getVendorId() + "/" + ap.getDisplayVendor());

+            }

+        }

+        // Addons are sorted by addon/vendor id and thus their order differs from the

+        // XML or the parsed package list.

+        assertEquals(

+                "[no_libs/This add-on has no libraries, " +

+                 "My_Second_add-on/My Second add-on, " +

+                 "My_First_add-on/My First add-on]",

+                Arrays.toString(addonNames.toArray()));

+        assertEquals(

+                "[Joe_Bar/Joe Bar, " +

+                 "John_Deer/John Deer, " +

+                 "John_Doe/John Doe]",

+                Arrays.toString(addonVendors.toArray()));

+

+        // Check the layoutlib of the platform packages.

+        ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();

+        for (Package p : pkgs) {

+            if (p instanceof AddonPackage) {

+                layoutlibVers.add(((AddonPackage) p).getLayoutlibVersion());

+            }

+        }

+        assertEquals(

+                 "[Pair [first=3, second=42], " +         // for #3 "This add-on has no libraries"

+                  "Pair [first=0, second=0], " +          // for #2 "My Second add-on"

+                  "Pair [first=5, second=0]]",            // for #1 "My First add-on"

+                Arrays.toString(layoutlibVers.toArray()));

+

+

+        // Check the extra packages: path, vendor, install folder, old-paths

+        final String osSdkPath = "SDK";

+        final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);

+

+        ArrayList<String> extraPaths   = new ArrayList<String>();

+        ArrayList<String> extraVendors = new ArrayList<String>();

+        ArrayList<File>   extraInstall = new ArrayList<File>();

+        ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();

+        for (Package p : pkgs) {

+            if (p instanceof ExtraPackage) {

+                ExtraPackage ep = (ExtraPackage) p;

+                // combine path and old-paths in the form "path [old_path1, old_path2]"

+                extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));

+                extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());

+                extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));

+

+                ArrayList<String> filePaths = new ArrayList<String>();

+                for (String filePath : ep.getProjectFiles()) {

+                    filePaths.add(filePath);

+                }

+                extraFilePaths.add(filePaths);

+            }

+        }

+        // Extras are sorted by vendor-id/path and thus their order differs from the

+        // XML or the parsed package list.

+        assertEquals(

+                "[extra0000005f [], " +                             // for extra #3

+                 "extra_api_dep [path1, old_path2, oldPath3], " +   // for extra #2

+                 "usb_driver []]",                                  // for extra #1

+                Arrays.toString(extraPaths.toArray()));

+        assertEquals(

+                "[____/____, " +

+                 "android_vendor/Android Vendor, " +

+                 "cyclop/The big bus]",

+                Arrays.toString(extraVendors.toArray()));

+        assertEquals(

+                ("[SDK/extras/____/extra0000005f, " +

+                  "SDK/extras/android_vendor/extra_api_dep, " +

+                  "SDK/extras/cyclop/usb_driver]").replace('/', File.separatorChar),

+                Arrays.toString(extraInstall.toArray()));

+        assertEquals(

+                "[[], " +

+                 "[v8/veggies_8.jar, root.jar, dir1/dir 2 with space/mylib.jar], " +

+                 "[]]",

+                Arrays.toString(extraFilePaths.toArray()));

+

+

+        // Check the min-tools-rev

+        ArrayList<String> minToolsRevs = new ArrayList<String>();

+        for (Package p : pkgs) {

+            if (p instanceof IMinToolsDependency) {

+                minToolsRevs.add(p.getListDescription() + ": " +

+                        ((IMinToolsDependency) p).getMinToolsRevision().toShortString());

+            }

+        }

+        assertEquals(

+                "[. -..- - .-. .- (Obsolete): 3.0.1, " +

+                 "Yet another extra, by Android: 3, " +

+                 "Random name, not an id! (Obsolete): 3.2.1 rc42]",

+                Arrays.toString(minToolsRevs.toArray()));

+    }

+

+    /**

      * Returns an SdkLib file resource as a {@link ByteArrayInputStream},

      * which has the advantage that we can use {@link InputStream#reset()} on it

      * at any time to read it multiple times.

diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
index 1bc9639..413902a 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/sources/SdkRepoSourceTest.java
@@ -21,6 +21,8 @@
 import com.android.sdklib.internal.repository.MockEmptySdkManager;

 import com.android.sdklib.internal.repository.MockMonitor;

 import com.android.sdklib.internal.repository.packages.ExtraPackage;

+import com.android.sdklib.internal.repository.packages.IMinPlatformToolsDependency;

+import com.android.sdklib.internal.repository.packages.IMinToolsDependency;

 import com.android.sdklib.internal.repository.packages.Package;

 import com.android.sdklib.internal.repository.packages.PlatformPackage;

 import com.android.sdklib.internal.repository.packages.PlatformToolPackage;

@@ -209,7 +211,7 @@
     /**

      * Validate we can still load an old repository in schema version 1

      */

-    public void testLoadOldXml_1() throws Exception {

+    public void testLoadXml_1() throws Exception {

         InputStream xmlStream = getTestResource(

                     "/com/android/sdklib/testdata/repository_sample_1.xml");

 

@@ -287,7 +289,7 @@
     /**

      * Validate we can still load an old repository in schema version 2

      */

-    public void testLoadOldXml_2() throws Exception {

+    public void testLoadXml_2() throws Exception {

         InputStream xmlStream = getTestResource(

                     "/com/android/sdklib/testdata/repository_sample_2.xml");

 

@@ -678,7 +680,7 @@
         for (Package p : pkgs) {

             if (p instanceof SystemImagePackage) {

                 SystemImagePackage sip = (SystemImagePackage) p;

-                String v = sip.getVersion().getApiString();

+                String v = sip.getAndroidVersion().getApiString();

                 String a = sip.getAbi();

                 sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$

             }

@@ -694,7 +696,7 @@
         for (Package p : pkgs) {

             if (p instanceof SourcePackage) {

                 SourcePackage sp = (SourcePackage) p;

-                String v = sp.getVersion().getApiString();

+                String v = sp.getAndroidVersion().getApiString();

                 sourceVersion.add(v);

             }

         }

@@ -820,7 +822,7 @@
         for (Package p : pkgs) {

             if (p instanceof SystemImagePackage) {

                 SystemImagePackage sip = (SystemImagePackage) p;

-                String v = sip.getVersion().getApiString();

+                String v = sip.getAndroidVersion().getApiString();

                 String a = sip.getAbi();

                 sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$

             }

@@ -837,7 +839,7 @@
         for (Package p : pkgs) {

             if (p instanceof SourcePackage) {

                 SourcePackage sp = (SourcePackage) p;

-                String v = sp.getVersion().getApiString();

+                String v = sp.getAndroidVersion().getApiString();

                 sourceVersion.add(v);

             }

         }

@@ -847,6 +849,183 @@
     }

 

     /**

+     * Validate what we can load from repository in schema version 6

+     */

+    public void testLoadXml_7() throws Exception {

+        InputStream xmlStream = getTestResource(

+                    "/com/android/sdklib/testdata/repository_sample_7.xml");

+

+        // guess the version from the XML document

+        int version = mSource._getXmlSchemaVersion(xmlStream);

+        assertEquals(7, version);

+

+        Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };

+        String[] validationError = new String[] { null };

+        String url = "not-a-valid-url://" + SdkRepoConstants.URL_DEFAULT_FILENAME;

+

+        String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);

+        assertEquals(Boolean.TRUE, validatorFound[0]);

+        assertEquals(null, validationError[0]);

+        assertEquals(SdkRepoConstants.getSchemaUri(7), uri);

+

+        // Validation was successful, load the document

+        MockMonitor monitor = new MockMonitor();

+        Document doc = mSource._getDocument(xmlStream, monitor);

+        assertNotNull(doc);

+

+        // Get the packages

+        assertTrue(mSource._parsePackages(doc, uri, monitor));

+

+        assertEquals("Found SDK Platform Android 1.0, API 1, revision 3\n" +

+                     "Found Documentation for Android SDK, API 1, revision 1\n" +

+                     "Found Sources for Android SDK, API 1, revision 1\n" +

+                     "Found SDK Platform Android 1.1, API 2, revision 12\n" +

+                     "Found Intel x86 Atom System Image, Android API 2, revision 1\n" +

+                     "Found ARM EABI v7a System Image, Android API 2, revision 2\n" +

+                     "Found Sources for Android SDK, API 2, revision 2\n" +

+                     "Found SDK Platform Android Pastry Preview, revision 3\n" +

+                     "Found Android SDK Tools, revision 1.2.3 rc4\n" +

+                     "Found Documentation for Android SDK, API 2, revision 42\n" +

+                     "Found Android SDK Tools, revision 42\n" +

+                     "Found Android SDK Platform-tools, revision 3 rc5\n" +

+                     "Found Samples for SDK API 14, revision 24 (Obsolete)\n" +

+                     "Found Samples for SDK API 14, revision 25 (Obsolete)\n" +

+                     "Found ARM EABI System Image, Android API 42, revision 12\n" +

+                     "Found Mips System Image, Android API 42, revision 12\n" +

+                     "Found Sources for Android SDK, API 42, revision 12\n",

+                monitor.getCapturedVerboseLog());

+        assertEquals("", monitor.getCapturedLog());

+        assertEquals("", monitor.getCapturedErrorLog());

+

+        // check the packages we found... we expected to find 13 packages with each at least

+        // one archive.

+        // Note the order doesn't necessary match the one from the

+        // assertEquald(getCapturedVerboseLog) because packages are sorted using the

+        // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.

+        Package[] pkgs = mSource.getPackages();

+

+        assertEquals(17, pkgs.length);

+        for (Package p : pkgs) {

+            assertTrue(p.getArchives().length >= 1);

+        }

+

+        // Check the layoutlib & included-abi of the platform packages.

+        ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();

+        ArrayList<String> includedAbi = new ArrayList<String>();

+        for (Package p : pkgs) {

+            if (p instanceof PlatformPackage) {

+                layoutlibVers.add(((PlatformPackage) p).getLayoutlibVersion());

+                String abi = ((PlatformPackage) p).getIncludedAbi();

+                includedAbi.add(abi == null ? "(null)" : abi);

+            }

+        }

+        assertEquals(

+                "[Pair [first=1, second=0], " +         // platform API 5 preview

+                 "Pair [first=5, second=31415], " +     // platform API 2

+                 "Pair [first=5, second=0]]",           // platform API 1

+                Arrays.toString(layoutlibVers.toArray()));

+        assertEquals(

+                "[(null), " +                           // platform API 5 preview

+                 "x86, " +                              // platform API 2

+                 "armeabi]",                            // platform API 1

+                Arrays.toString(includedAbi.toArray()));

+

+        // Check the extra packages path, vendor, install folder, project-files, old-paths

+

+        final String osSdkPath = "SDK";

+        final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);

+

+        ArrayList<String> extraPaths    = new ArrayList<String>();

+        ArrayList<String> extraVendors  = new ArrayList<String>();

+        ArrayList<File>   extraInstall  = new ArrayList<File>();

+        ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();

+        for (Package p : pkgs) {

+            if (p instanceof ExtraPackage) {

+                ExtraPackage ep = (ExtraPackage) p;

+                // combine path and old-paths in the form "path [old_path1, old_path2]"

+                extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));

+                extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());

+                extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));

+

+                ArrayList<String> filePaths = new ArrayList<String>();

+                for (String filePath : ep.getProjectFiles()) {

+                    filePaths.add(filePath);

+                }

+                extraFilePaths.add(filePaths);

+            }

+        }

+

+        // There are no extra packages anymore in repository-6

+        assertEquals("[]", Arrays.toString(extraPaths.toArray()));

+        assertEquals("[]", Arrays.toString(extraVendors.toArray()));

+        assertEquals("[]", Arrays.toString(extraInstall.toArray()));

+        assertEquals("[]", Arrays.toString(extraFilePaths.toArray()));

+

+

+        // Check the system-image packages

+        ArrayList<String> sysImgVersionAbi = new ArrayList<String>();

+        for (Package p : pkgs) {

+            if (p instanceof SystemImagePackage) {

+                SystemImagePackage sip = (SystemImagePackage) p;

+                String v = sip.getAndroidVersion().getApiString();

+                String a = sip.getAbi();

+                sysImgVersionAbi.add(String.format("%1$s %2$s", v, a)); //$NON-NLS-1$

+            }

+        }

+        assertEquals(

+                "[42 armeabi, " +

+                 "42 mips, " +

+                 "2 armeabi-v7a, " +

+                 "2 x86]",

+                Arrays.toString(sysImgVersionAbi.toArray()));

+

+

+        // Check the source packages

+        ArrayList<String> sourceVersion = new ArrayList<String>();

+        for (Package p : pkgs) {

+            if (p instanceof SourcePackage) {

+                SourcePackage sp = (SourcePackage) p;

+                String v = sp.getAndroidVersion().getApiString();

+                sourceVersion.add(v);

+            }

+        }

+        assertEquals(

+                "[42, 2, 1]",

+                Arrays.toString(sourceVersion.toArray()));

+

+

+        // Check the min-tools-rev

+        ArrayList<String> minToolsRevs = new ArrayList<String>();

+        for (Package p : pkgs) {

+            if (p instanceof IMinToolsDependency) {

+                minToolsRevs.add(p.getListDescription() + ": " +

+                        ((IMinToolsDependency) p).getMinToolsRevision().toShortString());

+            }

+        }

+        assertEquals(

+                "[SDK Platform Android Pastry Preview: 0, " +

+                 "SDK Platform Android 1.1: 0, " +

+                 "SDK Platform Android 1.0: 2.0.1, " +

+                 "Samples for SDK API 14 (Obsolete): 5, " +

+                 "Samples for SDK API 14 (Obsolete): 5.1.2 rc3]",

+                Arrays.toString(minToolsRevs.toArray()));

+

+

+        // Check the min-platform-tools-rev

+        ArrayList<String> minPlatToolsRevs = new ArrayList<String>();

+        for (Package p : pkgs) {

+            if (p instanceof IMinPlatformToolsDependency) {

+                minPlatToolsRevs.add(p.getListDescription() + ": " +

+                  ((IMinPlatformToolsDependency) p).getMinPlatformToolsRevision().toShortString());

+            }

+        }

+        assertEquals(

+                "[Android SDK Tools: 4, " +

+                 "Android SDK Tools: 4 rc5]",

+                Arrays.toString(minPlatToolsRevs.toArray()));

+    }

+

+    /**

      * Returns an SdkLib file resource as a {@link ByteArrayInputStream},

      * which has the advantage that we can use {@link InputStream#reset()} on it

      * at any time to read it multiple times.

diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/repository/ValidateRepositoryXmlTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
index ce21650..648ff05 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/repository/ValidateRepositoryXmlTest.java
@@ -162,6 +162,32 @@
 

     }

 

+    /** Validate a valid sample using namespace version 5 using an InputStream */

+    public void testValidateLocalRepositoryFile6() throws Exception {

+        InputStream xmlStream = this.getClass().getResourceAsStream(

+                    "/com/android/sdklib/testdata/repository_sample_6.xml");

+        Source source = new StreamSource(xmlStream);

+

+        CaptureErrorHandler handler = new CaptureErrorHandler();

+        Validator validator = getRepoValidator(6, handler);

+        validator.validate(source);

+        handler.verify();

+

+    }

+

+    /** Validate a valid sample using namespace version 5 using an InputStream */

+    public void testValidateLocalRepositoryFile7() throws Exception {

+        InputStream xmlStream = this.getClass().getResourceAsStream(

+                    "/com/android/sdklib/testdata/repository_sample_7.xml");

+        Source source = new StreamSource(xmlStream);

+

+        CaptureErrorHandler handler = new CaptureErrorHandler();

+        Validator validator = getRepoValidator(7, handler);

+        validator.validate(source);

+        handler.verify();

+

+    }

+

     // IMPORTANT: each time you add a test here, you should add a corresponding

     // test in SdkRepoSourceTest to validate the XML content is parsed correctly.

 

@@ -276,8 +302,8 @@
         String document = "<?xml version=\"1.0\"?>" +

             OPEN_TAG_REPO +

             "<r:license id=\"lic1\"> some license </r:license> " +

-            "<r:tool> <r:uses-license ref=\"lic2\" /> <r:revision>1</r:revision> " +

-            "<r:min-platform-tools-rev>1</r:min-platform-tools-rev> " +

+            "<r:tool> <r:uses-license ref=\"lic2\" /> <r:revision> <r:major>1</r:major> </r:revision> " +

+            "<r:min-platform-tools-rev> <r:major>1</r:major> </r:min-platform-tools-rev> " +

             "<r:archives> <r:archive os=\"any\"> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +

             "<r:url>url</r:url> </r:archive> </r:archives> </r:tool>" +

             CLOSE_TAG_REPO;

diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/addon_sample_5.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/addon_sample_5.xml
new file mode 100755
index 0000000..47b37be
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/addon_sample_5.xml
@@ -0,0 +1,214 @@
+<?xml version="1.0"?>
+<!--
+ * 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.
+-->
+<sdk:sdk-addon
+    xmlns:sdk="http://schemas.android.com/sdk/android/addon/5">
+
+    <!-- Define a couple of licenses. These will be referenced by uses-license later. -->
+
+    <sdk:license type="text" id="license1">
+        This is the license
+        for this platform.
+    </sdk:license>
+
+    <sdk:license id="license2">
+        Licenses are only of type 'text' right now, so this is implied.
+    </sdk:license>
+
+    <!-- Inner elements must be either platform, add-on, doc or tool.
+         There can be 0 or more of each, in any order. -->
+
+    <sdk:add-on>
+        <sdk:name-id>My_First_add-on</sdk:name-id>
+        <sdk:name-display>My First add-on</sdk:name-display>
+
+        <sdk:vendor-id>John_Doe</sdk:vendor-id>
+        <sdk:vendor-display>John Doe</sdk:vendor-display>
+
+        <sdk:api-level>1</sdk:api-level>
+        <sdk:revision>1</sdk:revision>
+        <sdk:uses-license ref="license2" />
+        <sdk:description>Some optional description</sdk:description>
+        <sdk:desc-url>http://www.example.com/myfirstaddon</sdk:desc-url>
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>http://www.example.com/add-ons/first.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <!-- The libs node is mandatory, however it can be empty. -->
+        <sdk:libs>
+            <sdk:lib>
+                <sdk:name>android.blah.somelib</sdk:name>
+                <sdk:description>The description for this library.</sdk:description>
+            </sdk:lib>
+            <sdk:lib>
+                <!-- sdk:description is optional, name is not -->
+                <sdk:name>com.android.mymaps</sdk:name>
+            </sdk:lib>
+        </sdk:libs>
+        <sdk:layoutlib>
+            <sdk:api>5</sdk:api>
+            <sdk:revision>0</sdk:revision>
+        </sdk:layoutlib>
+    </sdk:add-on>
+
+    <sdk:add-on>
+        <sdk:name-id>My_Second_add-on</sdk:name-id>
+        <sdk:name-display>My Second add-on</sdk:name-display>
+
+        <sdk:vendor-id>John_Deer</sdk:vendor-id>
+        <sdk:vendor-display>John Deer</sdk:vendor-display>
+
+        <sdk:api-level>2</sdk:api-level>
+        <sdk:revision>42</sdk:revision>
+        <sdk:archives>
+            <sdk:archive os="windows">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/second-42-win.zip</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="linux">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/second-42-linux.tar.bz2</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <sdk:libs>
+            <sdk:lib>
+                <sdk:name>android.blah.somelib</sdk:name>
+                <sdk:description>The description for this library.</sdk:description>
+            </sdk:lib>
+            <sdk:lib>
+                <sdk:name>com.android.mymaps</sdk:name>
+            </sdk:lib>
+        </sdk:libs>
+        <sdk:uses-license ref="license2" />
+        <!-- No layoutlib element in this package. It's optional. -->
+    </sdk:add-on>
+
+    <sdk:add-on>
+        <sdk:name-id>no_libs</sdk:name-id>
+        <sdk:name-display>This add-on has no libraries</sdk:name-display>
+
+        <sdk:vendor-id>Joe_Bar</sdk:vendor-id>
+        <sdk:vendor-display>Joe Bar</sdk:vendor-display>
+
+        <sdk:uses-license ref="license2" />
+        <sdk:api-level>4</sdk:api-level>
+        <sdk:revision>3</sdk:revision>
+        <sdk:archives>
+            <sdk:archive os="any" arch="any">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/imnotanarchiveimadoctorjim.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <!-- The libs node is mandatory, however it can be empty. -->
+        <sdk:libs />
+        <sdk:layoutlib>
+            <sdk:api>3</sdk:api>
+            <sdk:revision>42</sdk:revision>
+        </sdk:layoutlib>
+    </sdk:add-on>
+
+    <sdk:extra>
+        <sdk:name-display>Random name, not an id!</sdk:name-display>
+
+        <sdk:vendor-id>cyclop</sdk:vendor-id>
+        <sdk:vendor-display>The big bus</sdk:vendor-display>
+
+        <sdk:path>usb_driver</sdk:path>
+        <sdk:uses-license ref="license2" />
+        <sdk:revision>43</sdk:revision>
+        <sdk:archives>
+            <sdk:archive os="any" arch="any">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/extraduff.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <sdk:description>An Extra package for the USB driver, it will install in $SDK/usb_driver</sdk:description>
+        <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+        <sdk:min-tools-rev>
+            <sdk:major>3</sdk:major>
+            <sdk:minor>2</sdk:minor>
+            <sdk:micro>1</sdk:micro>
+            <sdk:preview>42</sdk:preview>
+        </sdk:min-tools-rev>
+        <sdk:obsolete/>
+    </sdk:extra>
+
+    <sdk:extra>
+        <sdk:name-display>Yet another extra, by Android</sdk:name-display>
+
+        <sdk:vendor-id>android_vendor</sdk:vendor-id>
+        <sdk:vendor-display>Android Vendor</sdk:vendor-display>
+
+        <sdk:path>extra_api_dep</sdk:path>
+        <sdk:uses-license ref="license2" />
+        <sdk:revision>2</sdk:revision>
+        <sdk:archives>
+            <sdk:archive os="any" arch="any">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/extra_mega_duff.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <sdk:description>Some extra package that has a min-api-level of 42</sdk:description>
+        <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+        <sdk:min-tools-rev>
+            <sdk:major>3</sdk:major>
+        </sdk:min-tools-rev>
+        <sdk:min-api-level>42</sdk:min-api-level>
+        <sdk:project-files>
+            <sdk:path>v8/veggies_8.jar</sdk:path>
+            <sdk:path>root.jar</sdk:path>
+            <sdk:path>dir1/dir 2 with space/mylib.jar</sdk:path>
+        </sdk:project-files>
+        <sdk:old-paths>path1;old_path2;oldPath3</sdk:old-paths>
+    </sdk:extra>
+
+    <sdk:extra>
+        <sdk:name-display>. -..- - .-. .-</sdk:name-display>
+
+        <sdk:vendor-id>____</sdk:vendor-id>
+        <sdk:vendor-display>____</sdk:vendor-display>
+
+        <sdk:path>____</sdk:path>
+        <sdk:uses-license ref="license2" />
+        <sdk:revision>2</sdk:revision>
+        <sdk:archives>
+            <sdk:archive os="any" arch="any">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/extra_mega_duff.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <sdk:description>Some extra package that has a min-api-level of 42</sdk:description>
+        <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+        <sdk:min-tools-rev>
+            <sdk:major>3</sdk:major>
+            <!-- no minor, assumed to be 0 -->
+            <sdk:micro>1</sdk:micro>
+        </sdk:min-tools-rev>
+        <sdk:min-api-level>42</sdk:min-api-level>
+        <sdk:obsolete></sdk:obsolete>
+        <!-- No project-files element in this package. -->
+    </sdk:extra>
+
+</sdk:sdk-addon>
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_4.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_4.xml
index a201a63..f68b033 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_4.xml
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_4.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0"?>
 <!--
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_5.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_5.xml
index 4db8b33..471550e 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_5.xml
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_5.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0"?>
 <!--
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_6.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_6.xml
index e57b831..12be591 100755
--- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_6.xml
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_6.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0"?>
 <!--
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_7.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_7.xml
new file mode 100755
index 0000000..401521b
--- /dev/null
+++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_7.xml
@@ -0,0 +1,371 @@
+<?xml version="1.0"?>
+<!--
+ * 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.
+-->
+<sdk:sdk-repository
+    xmlns:sdk="http://schemas.android.com/sdk/android/repository/7">
+
+    <!-- Define a couple of licenses. These will be referenced by uses-license later. -->
+
+    <sdk:license type="text" id="license1">
+        This is the license
+        for this platform.
+    </sdk:license>
+
+    <sdk:license id="license2">
+        Licenses are only of type 'text' right now, so this is implied.
+    </sdk:license>
+
+    <!-- Inner elements must be either platform, add-on, doc or tool.
+         There can be 0 or more of each, in any order. -->
+
+    <sdk:platform>
+        <sdk:version>1.0</sdk:version>
+        <sdk:api-level>1</sdk:api-level>
+        <sdk:revision>3</sdk:revision>
+        <sdk:uses-license ref="license1" />
+        <sdk:description>Some optional description</sdk:description>
+        <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>
+        <sdk:release-note>This is an optional release note
+            for this package. It's a free multi-line text.
+        </sdk:release-note>
+        <sdk:release-url>http://some/url/for/the/release/note.html</sdk:release-url>
+        <sdk:min-tools-rev>
+            <sdk:major>2</sdk:major>
+            <!-- minor is missing and equivalent to 0 -->
+            <sdk:micro>1</sdk:micro>
+        </sdk:min-tools-rev>
+        <!-- The archives node is mandatory and it cannot be empty. -->
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>http://www.example.com/files/plat1.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <sdk:layoutlib>
+            <sdk:api>5</sdk:api>
+            <sdk:revision>0</sdk:revision>
+        </sdk:layoutlib>
+        <sdk:included-abi>armeabi</sdk:included-abi>
+    </sdk:platform>
+
+    <sdk:doc>
+        <sdk:api-level>1</sdk:api-level>
+        <sdk:revision>1</sdk:revision>
+        <!-- the license element is not mandatory. -->
+        <sdk:description>Some optional description</sdk:description>
+        <sdk:desc-url>http://www.example.com/docs.html</sdk:desc-url>
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>http://www.example.com/docs/docs1.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:doc>
+
+    <sdk:source>
+        <sdk:api-level>1</sdk:api-level>
+        <sdk:revision>1</sdk:revision>
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>65535</sdk:size>
+                <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+                <sdk:url>http://www.example.com/plat1/sources1.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:source>
+
+    <sdk:platform>
+        <sdk:version>1.1</sdk:version>
+        <sdk:api-level>2</sdk:api-level>
+        <sdk:revision>12</sdk:revision>
+        <sdk:uses-license ref="license1" />
+        <!-- sdk:description and sdk:desc-url are optional -->
+        <sdk:archives>
+            <sdk:archive os="windows">
+                <!--  arch attribute is optional -->
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/platform-2-12-win.zip</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="macosx" arch="any">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/platform-2-12-mac.zip</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="macosx" arch="ppc">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/platform-2-12-mac.zip</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="linux" arch="x86">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/platform-2-12-linux.tar.bz2</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="linux" arch="x86_64">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/platform-2-12-linux.tar.bz2</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <sdk:layoutlib>
+            <sdk:api>5</sdk:api>
+            <sdk:revision>31415</sdk:revision>
+        </sdk:layoutlib>
+        <sdk:included-abi>x86</sdk:included-abi>
+    </sdk:platform>
+
+    <sdk:system-image>
+        <sdk:api-level>2</sdk:api-level>
+        <sdk:revision>1</sdk:revision>
+        <sdk:abi>x86</sdk:abi>
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>65535</sdk:size>
+                <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+                <sdk:url>http://www.example.com/plat1/x86/image1.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:system-image>
+
+    <sdk:system-image>
+        <sdk:api-level>2</sdk:api-level>
+        <sdk:revision>2</sdk:revision>
+        <sdk:abi>armeabi-v7a</sdk:abi>
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>65534</sdk:size>
+                <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+                <sdk:url>http://www.example.com/plat1/armv7/image2.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:system-image>
+
+    <sdk:source>
+        <sdk:api-level>2</sdk:api-level>
+        <sdk:revision>2</sdk:revision>
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>65534</sdk:size>
+                <sdk:checksum type="sha1">1234ae37115ebf13412bbef91339ee0d94541234</sdk:checksum>
+                <sdk:url>http://www.example.com/plat1/sources2.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:source>
+
+   <sdk:platform>
+        <sdk:version>Pastry</sdk:version>
+        <sdk:api-level>5</sdk:api-level>
+        <sdk:codename>Pastry</sdk:codename>
+        <sdk:revision>3</sdk:revision>
+        <sdk:uses-license ref="license1" />
+        <sdk:description>Preview version for Pastry</sdk:description>
+        <sdk:desc-url>http://www.example.com/platform1.html</sdk:desc-url>
+        <!-- The archives node is mandatory and it cannot be empty. -->
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>http://www.example.com/files/plat1.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <sdk:layoutlib>
+            <sdk:api>1</sdk:api>
+        </sdk:layoutlib>
+    </sdk:platform>
+
+    <sdk:tool>
+        <sdk:revision>
+            <sdk:major>1</sdk:major>
+            <sdk:minor>2</sdk:minor>
+            <sdk:micro>3</sdk:micro>
+            <sdk:preview>4</sdk:preview>
+        </sdk:revision>
+        <sdk:description>Some optional description</sdk:description>
+        <sdk:desc-url>http://www.example.com/tools.html</sdk:desc-url>
+        <sdk:uses-license ref="license1" />
+        <sdk:min-platform-tools-rev>
+            <sdk:major>4</sdk:major>
+        </sdk:min-platform-tools-rev>
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>http://www.example.com/files/tools1.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:tool>
+
+    <sdk:doc>
+        <sdk:api-level>2</sdk:api-level>
+        <sdk:revision>42</sdk:revision>
+        <sdk:uses-license ref="license2" />
+        <sdk:archives>
+            <sdk:archive os="windows">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/docs/2.zip</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="linux">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/docs2-linux.tar.bz2</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="macosx">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/docs2-mac.tar.bz2</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:doc>
+
+    <sdk:tool>
+        <sdk:revision>
+            <sdk:major>42</sdk:major>
+        </sdk:revision>
+        <sdk:uses-license ref="license1" />
+        <sdk:min-platform-tools-rev>
+            <sdk:major>4</sdk:major>
+            <sdk:minor>0</sdk:minor>
+            <sdk:micro>0</sdk:micro>
+            <sdk:preview>5</sdk:preview>
+        </sdk:min-platform-tools-rev>
+        <sdk:archives>
+            <sdk:archive os="windows">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/tools/2.zip</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="linux">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/tools2-linux.tar.bz2</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="macosx">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/tools2-mac.tar.bz2</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:tool>
+
+    <sdk:platform-tool>
+        <sdk:revision>
+            <sdk:major>3</sdk:major>
+            <sdk:minor>0</sdk:minor>
+            <sdk:micro>0</sdk:micro>
+            <sdk:preview>5</sdk:preview>
+        </sdk:revision>
+        <sdk:uses-license ref="license1" />
+        <sdk:archives>
+            <sdk:archive os="windows">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/platform-tools/2.zip</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="linux">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/platform-tools2-linux.tar.bz2</sdk:url>
+            </sdk:archive>
+            <sdk:archive os="macosx">
+                <sdk:size>65536</sdk:size>
+                <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/platform-tools2-mac.tar.bz2</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:platform-tool>
+
+    <sdk:sample>
+        <sdk:api-level>14</sdk:api-level>
+        <sdk:revision>24</sdk:revision>
+        <sdk:archives>
+            <sdk:archive os="any" arch="any">
+                <sdk:size>65537</sdk:size>
+                <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/sample_duff.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <sdk:description>Some sample package</sdk:description>
+        <sdk:desc-url>http://www.example.com/sample.html</sdk:desc-url>
+        <sdk:min-tools-rev>
+            <sdk:major>5</sdk:major>
+        </sdk:min-tools-rev>
+        <sdk:obsolete>This is obsolete</sdk:obsolete>
+    </sdk:sample>
+
+    <sdk:sample>
+        <sdk:api-level>14</sdk:api-level>
+        <sdk:revision>25</sdk:revision>
+        <sdk:archives>
+            <sdk:archive os="any" arch="any">
+                <sdk:size>65537</sdk:size>
+                <sdk:checksum type="sha1">3822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+                <sdk:url>distrib/sample_duff.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+        <sdk:description>Some sample package</sdk:description>
+        <sdk:desc-url>http://www.example.com/sample.html</sdk:desc-url>
+        <sdk:min-tools-rev>
+            <sdk:major>5</sdk:major>
+            <sdk:minor>1</sdk:minor>
+            <sdk:micro>2</sdk:micro>
+            <sdk:preview>3</sdk:preview>
+        </sdk:min-tools-rev>
+        <sdk:obsolete>This is obsolete</sdk:obsolete>
+    </sdk:sample>
+
+    <sdk:system-image>
+        <sdk:api-level>42</sdk:api-level>
+        <sdk:revision>12</sdk:revision>
+        <sdk:abi>armeabi</sdk:abi>
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>1234</sdk:size>
+                <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+                <sdk:url>http://www.example.com/plat42/86/image12.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:system-image>
+
+    <sdk:system-image>
+        <sdk:api-level>42</sdk:api-level>
+        <sdk:revision>12</sdk:revision>
+        <sdk:abi>mips</sdk:abi>
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>12345</sdk:size>
+                <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+                <sdk:url>http://www.example.com/plat42/mips/image12.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:system-image>
+
+    <sdk:source>
+        <sdk:api-level>42</sdk:api-level>
+        <sdk:revision>12</sdk:revision>
+        <sdk:archives>
+            <sdk:archive os="any">
+                <sdk:size>1234</sdk:size>
+                <sdk:checksum type="sha1">12345637115ebf13412bbef91339ee0d94541234</sdk:checksum>
+                <sdk:url>http://www.example.com/plat42/source12.zip</sdk:url>
+            </sdk:archive>
+        </sdk:archives>
+    </sdk:source>
+
+</sdk:sdk-repository>
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterChooserDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterChooserDialog.java
index b5ab90b..a41a952 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterChooserDialog.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterChooserDialog.java
@@ -19,8 +19,9 @@
 import com.android.sdklib.AndroidVersion;

 import com.android.sdklib.SdkConstants;

 import com.android.sdklib.internal.repository.archives.Archive;

-import com.android.sdklib.internal.repository.packages.IPackageVersion;

+import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;

 import com.android.sdklib.internal.repository.packages.Package;

+import com.android.sdklib.internal.repository.packages.FullRevision;

 import com.android.sdklib.internal.repository.sources.SdkSource;

 import com.android.sdkuilib.internal.repository.icons.ImageFactory;

 import com.android.sdkuilib.ui.GridDialog;

@@ -423,28 +424,29 @@
         if (aOld != null) {

             Package pOld = aOld.getParentPackage();

 

-            int rOld = pOld.getRevision();

-            int rNew = pNew.getRevision();

+            FullRevision rOld = pOld.getRevision();

+            FullRevision rNew = pNew.getRevision();

 

             boolean showRev = true;

 

-            if (pNew instanceof IPackageVersion && pOld instanceof IPackageVersion) {

-                AndroidVersion vOld = ((IPackageVersion) pOld).getVersion();

-                AndroidVersion vNew = ((IPackageVersion) pNew).getVersion();

+            if (pNew instanceof IAndroidVersionProvider &&

+                    pOld instanceof IAndroidVersionProvider) {

+                AndroidVersion vOld = ((IAndroidVersionProvider) pOld).getAndroidVersion();

+                AndroidVersion vNew = ((IAndroidVersionProvider) pNew).getAndroidVersion();

 

                 if (!vOld.equals(vNew)) {

                     // Versions are different, so indicate more than just the revision.

-                    addText(String.format("This update will replace API %1$s revision %2$d with API %3$s revision %4$d.\n\n",

-                            vOld.getApiString(), rOld,

-                            vNew.getApiString(), rNew));

+                    addText(String.format("This update will replace API %1$s revision %2$s with API %3$s revision %4$s.\n\n",

+                            vOld.getApiString(), rOld.toShortString(),

+                            vNew.getApiString(), rNew.toShortString()));

                     showRev = false;

                 }

             }

 

             if (showRev) {

-                addText(String.format("This update will replace revision %1$d with revision %2$d.\n\n",

-                        rOld,

-                        rNew));

+                addText(String.format("This update will replace revision %1$s with revision %2$s.\n\n",

+                        rOld.toShortString(),

+                        rNew.toShortString()));

             }

         }

 

diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterLogic.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterLogic.java
index 0b9f396..ba1cd2d 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterLogic.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/SdkUpdaterLogic.java
@@ -23,20 +23,21 @@
 import com.android.sdklib.internal.repository.packages.AddonPackage;

 import com.android.sdklib.internal.repository.packages.DocPackage;

 import com.android.sdklib.internal.repository.packages.ExtraPackage;

+import com.android.sdklib.internal.repository.packages.FullRevision;

+import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;

 import com.android.sdklib.internal.repository.packages.IExactApiLevelDependency;

 import com.android.sdklib.internal.repository.packages.IMinApiLevelDependency;

 import com.android.sdklib.internal.repository.packages.IMinPlatformToolsDependency;

 import com.android.sdklib.internal.repository.packages.IMinToolsDependency;

-import com.android.sdklib.internal.repository.packages.IPackageVersion;

 import com.android.sdklib.internal.repository.packages.IPlatformDependency;

 import com.android.sdklib.internal.repository.packages.MinToolsPackage;

 import com.android.sdklib.internal.repository.packages.Package;

+import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;

 import com.android.sdklib.internal.repository.packages.PlatformPackage;

 import com.android.sdklib.internal.repository.packages.PlatformToolPackage;

 import com.android.sdklib.internal.repository.packages.SamplePackage;

 import com.android.sdklib.internal.repository.packages.SystemImagePackage;

 import com.android.sdklib.internal.repository.packages.ToolPackage;

-import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;

 import com.android.sdklib.internal.repository.sources.SdkSource;

 import com.android.sdklib.internal.repository.sources.SdkSources;

 

@@ -184,6 +185,14 @@
         return archives;

     }

 

+    private double getRevisionRank(FullRevision rev) {

+        int p = rev.isPreview() ? 999 : 999 - rev.getPreview();

+        return  rev.getMajor() +

+                rev.getMinor() / 1000.d +

+                rev.getMicro() / 1000000.d +

+                p              / 1000000000.d;

+    }

+

     /**

      * Finds new packages that the user does not have in his/her local SDK

      * and adds them to the list of archives to install.

@@ -212,27 +221,27 @@
         ArchiveInfo[] localArchives = createLocalArchives(localPkgs);

 

         // Find the highest platform installed

-        float currentPlatformScore = 0;

-        float currentSampleScore = 0;

-        float currentAddonScore = 0;

-        float currentDocScore = 0;

-        HashMap<String, Float> currentExtraScore = new HashMap<String, Float>();

+        double currentPlatformScore = 0;

+        double currentSampleScore = 0;

+        double currentAddonScore = 0;

+        double currentDocScore = 0;

+        HashMap<String, Double> currentExtraScore = new HashMap<String, Double>();

         if (!includeAll) {

             if (localPkgs != null) {

                 for (Package p : localPkgs) {

-                    int rev = p.getRevision();

+                    double rev = getRevisionRank(p.getRevision());

                     int api = 0;

                     boolean isPreview = false;

-                    if (p instanceof IPackageVersion) {

-                        AndroidVersion vers = ((IPackageVersion) p).getVersion();

+                    if (p instanceof IAndroidVersionProvider) {

+                        AndroidVersion vers = ((IAndroidVersionProvider) p).getAndroidVersion();

                         api = vers.getApiLevel();

                         isPreview = vers.isPreview();

                     }

 

-                    // The score is 10*api + (1 if preview) + rev/100

+                    // The score is 1000*api + (999 if preview) + rev

                     // This allows previews to rank above a non-preview and

                     // allows revisions to rank appropriately.

-                    float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;

+                    double score = api * 1000 + (isPreview ? 999 : 0) + rev;

 

                     if (p instanceof PlatformPackage) {

                         currentPlatformScore = Math.max(currentPlatformScore, score);

@@ -261,16 +270,16 @@
                 continue;

             }

 

-            int rev = p.getRevision();

+            double rev = getRevisionRank(p.getRevision());

             int api = 0;

             boolean isPreview = false;

-            if (p instanceof  IPackageVersion) {

-                AndroidVersion vers = ((IPackageVersion) p).getVersion();

+            if (p instanceof IAndroidVersionProvider) {

+                AndroidVersion vers = ((IAndroidVersionProvider) p).getAndroidVersion();

                 api = vers.getApiLevel();

                 isPreview = vers.isPreview();

             }

 

-            float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;

+            double score = api * 1000 + (isPreview ? 999 : 0) + rev;

 

             boolean shouldAdd = false;

             if (p instanceof PlatformPackage) {

@@ -282,7 +291,7 @@
             } else if (p instanceof ExtraPackage) {

                 String key = ((ExtraPackage) p).getPath();

                 shouldAdd = !currentExtraScore.containsKey(key) ||

-                    score > currentExtraScore.get(key).floatValue();

+                    score > currentExtraScore.get(key).doubleValue();

             } else if (p instanceof DocPackage) {

                 // We don't want all the doc, only the most recent one

                 if (score > currentDocScore) {

@@ -318,7 +327,7 @@
                             continue;

                         }

                         SystemImagePackage sip = (SystemImagePackage) p2;

-                        if (sip.getVersion().equals(pp.getVersion())) {

+                        if (sip.getAndroidVersion().equals(pp.getAndroidVersion())) {

                             for (Archive a : sip.getArchives()) {

                                 if (a.isCompatible()) {

                                     insertArchive(a,

@@ -673,9 +682,9 @@
             SdkSource[] remoteSources,

             ArchiveInfo[] localArchives) {

         // This is the requirement to match.

-        int rev = pkg.getMinToolsRevision();

+        FullRevision rev = pkg.getMinToolsRevision();

 

-        if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {

+        if (rev.equals(MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED)) {

             // Well actually there's no requirement.

             return null;

         }

@@ -686,7 +695,7 @@
             if (a != null) {

                 Package p = a.getParentPackage();

                 if (p instanceof ToolPackage) {

-                    if (((ToolPackage) p).getRevision() >= rev) {

+                    if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {

                         // We found one already installed.

                         return null;

                     }

@@ -700,7 +709,7 @@
             if (a != null) {

                 Package p = a.getParentPackage();

                 if (p instanceof ToolPackage) {

-                    if (((ToolPackage) p).getRevision() >= rev) {

+                    if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {

                         // The dependency is already scheduled for install, nothing else to do.

                         return ai;

                     }

@@ -713,7 +722,7 @@
             for (Archive a : selectedArchives) {

                 Package p = a.getParentPackage();

                 if (p instanceof ToolPackage) {

-                    if (((ToolPackage) p).getRevision() >= rev) {

+                    if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {

                         // It's not already in the list of things to install, so add it now

                         return insertArchive(a,

                                 outArchives,

@@ -731,7 +740,7 @@
         fetchRemotePackages(remotePkgs, remoteSources);

         for (Package p : remotePkgs) {

             if (p instanceof ToolPackage) {

-                if (((ToolPackage) p).getRevision() >= rev) {

+                if (((ToolPackage) p).getRevision().compareTo(rev) >= 0) {

                     // It's not already in the list of things to install, so add the

                     // first compatible archive we can find.

                     for (Archive a : p.getArchives()) {

@@ -771,12 +780,12 @@
             SdkSource[] remoteSources,

             ArchiveInfo[] localArchives) {

         // This is the requirement to match.

-        int rev = pkg.getMinPlatformToolsRevision();

+        FullRevision rev = pkg.getMinPlatformToolsRevision();

         boolean findMax = false;

         ArchiveInfo aiMax = null;

         Archive aMax = null;

 

-        if (rev == IMinPlatformToolsDependency.MIN_PLATFORM_TOOLS_REV_INVALID) {

+        if (rev.equals(IMinPlatformToolsDependency.MIN_PLATFORM_TOOLS_REV_INVALID)) {

             // The requirement is invalid, which is not supposed to happen since this

             // property is mandatory. However in a typical upgrade scenario we can end

             // up with the previous updater managing a new package and not dealing

@@ -792,11 +801,11 @@
             if (a != null) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformToolPackage) {

-                    int r = ((PlatformToolPackage) p).getRevision();

-                    if (findMax && r > rev) {

+                    FullRevision r = ((PlatformToolPackage) p).getRevision();

+                    if (findMax && r.compareTo(rev) > 0) {

                         rev = r;

                         aiMax = ai;

-                    } else if (!findMax && r >= rev) {

+                    } else if (!findMax && r.compareTo(rev) >= 0) {

                         // We found one already installed.

                         return null;

                     }

@@ -810,11 +819,11 @@
             if (a != null) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformToolPackage) {

-                    int r = ((PlatformToolPackage) p).getRevision();

-                    if (findMax && r > rev) {

+                    FullRevision r = ((PlatformToolPackage) p).getRevision();

+                    if (findMax && r.compareTo(rev) > 0) {

                         rev = r;

                         aiMax = ai;

-                    } else if (!findMax && r >= rev) {

+                    } else if (!findMax && r.compareTo(rev) >= 0) {

                         // The dependency is already scheduled for install, nothing else to do.

                         return ai;

                     }

@@ -827,12 +836,12 @@
             for (Archive a : selectedArchives) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformToolPackage) {

-                    int r = ((PlatformToolPackage) p).getRevision();

-                    if (findMax && r > rev) {

+                    FullRevision r = ((PlatformToolPackage) p).getRevision();

+                    if (findMax && r.compareTo(rev) > 0) {

                         rev = r;

                         aiMax = null;

                         aMax = a;

-                    } else if (!findMax && r >= rev) {

+                    } else if (!findMax && r.compareTo(rev) >= 0) {

                         // It's not already in the list of things to install, so add it now

                         return insertArchive(a,

                                 outArchives,

@@ -850,16 +859,16 @@
         fetchRemotePackages(remotePkgs, remoteSources);

         for (Package p : remotePkgs) {

             if (p instanceof PlatformToolPackage) {

-                int r = ((PlatformToolPackage) p).getRevision();

-                if (r >= rev) {

+                FullRevision r = ((PlatformToolPackage) p).getRevision();

+                if (r.compareTo(rev) >= 0) {

                     // Make sure there's at least one valid archive here

                     for (Archive a : p.getArchives()) {

                         if (a.isCompatible()) {

-                            if (findMax && r > rev) {

+                            if (findMax && r.compareTo(rev) > 0) {

                                 rev = r;

                                 aiMax = null;

                                 aMax = a;

-                            } else if (!findMax && r >= rev) {

+                            } else if (!findMax && r.compareTo(rev) >= 0) {

                                 // It's not already in the list of things to install, so add the

                                 // first compatible archive we can find.

                                 return insertArchive(a,

@@ -912,7 +921,7 @@
             SdkSource[] remoteSources,

             ArchiveInfo[] localArchives) {

         // This is the requirement to match.

-        AndroidVersion v = pkg.getVersion();

+        AndroidVersion v = pkg.getAndroidVersion();

 

         // Find a platform that would satisfy the requirement.

 

@@ -922,7 +931,7 @@
             if (a != null) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformPackage) {

-                    if (v.equals(((PlatformPackage) p).getVersion())) {

+                    if (v.equals(((PlatformPackage) p).getAndroidVersion())) {

                         // We found one already installed.

                         return null;

                     }

@@ -936,7 +945,7 @@
             if (a != null) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformPackage) {

-                    if (v.equals(((PlatformPackage) p).getVersion())) {

+                    if (v.equals(((PlatformPackage) p).getAndroidVersion())) {

                         // The dependency is already scheduled for install, nothing else to do.

                         return ai;

                     }

@@ -949,7 +958,7 @@
             for (Archive a : selectedArchives) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformPackage) {

-                    if (v.equals(((PlatformPackage) p).getVersion())) {

+                    if (v.equals(((PlatformPackage) p).getAndroidVersion())) {

                         // It's not already in the list of things to install, so add it now

                         return insertArchive(a,

                                 outArchives,

@@ -967,7 +976,7 @@
         fetchRemotePackages(remotePkgs, remoteSources);

         for (Package p : remotePkgs) {

             if (p instanceof PlatformPackage) {

-                if (v.equals(((PlatformPackage) p).getVersion())) {

+                if (v.equals(((PlatformPackage) p).getAndroidVersion())) {

                     // It's not already in the list of things to install, so add the

                     // first compatible archive we can find.

                     for (Archive a : p.getArchives()) {

@@ -988,7 +997,7 @@
         // We end up here if nothing matches. We don't have a good platform to match.

         // We need to indicate this addon depends on a missing platform archive

         // so that it can be impossible to install later on.

-        return new MissingPlatformArchiveInfo(pkg.getVersion());

+        return new MissingPlatformArchiveInfo(pkg.getAndroidVersion());

     }

 

     /**

@@ -1025,7 +1034,7 @@
             if (a != null) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformPackage) {

-                    if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {

+                    if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {

                         // We found one already installed.

                         return null;

                     }

@@ -1042,7 +1051,7 @@
             if (a != null) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformPackage) {

-                    if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {

+                    if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {

                         if (api > foundApi) {

                             foundApi = api;

                             foundAi = ai;

@@ -1065,7 +1074,7 @@
             for (Archive a : selectedArchives) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformPackage) {

-                    if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {

+                    if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {

                         if (api > foundApi) {

                             foundApi = api;

                             foundArchive = a;

@@ -1079,7 +1088,7 @@
         fetchRemotePackages(remotePkgs, remoteSources);

         for (Package p : remotePkgs) {

             if (p instanceof PlatformPackage) {

-                if (((PlatformPackage) p).getVersion().isGreaterOrEqualThan(api)) {

+                if (((PlatformPackage) p).getAndroidVersion().isGreaterOrEqualThan(api)) {

                     if (api > foundApi) {

                         // It's not already in the list of things to install, so add the

                         // first compatible archive we can find.

@@ -1140,7 +1149,7 @@
             if (a != null) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformPackage) {

-                    if (((PlatformPackage) p).getVersion().equals(api)) {

+                    if (((PlatformPackage) p).getAndroidVersion().equals(api)) {

                         // We found one already installed.

                         return null;

                     }

@@ -1155,7 +1164,7 @@
             if (a != null) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformPackage) {

-                    if (((PlatformPackage) p).getVersion().equals(api)) {

+                    if (((PlatformPackage) p).getAndroidVersion().equals(api)) {

                         return ai;

                     }

                 }

@@ -1167,7 +1176,7 @@
             for (Archive a : selectedArchives) {

                 Package p = a.getParentPackage();

                 if (p instanceof PlatformPackage) {

-                    if (((PlatformPackage) p).getVersion().equals(api)) {

+                    if (((PlatformPackage) p).getAndroidVersion().equals(api)) {

                         // It's not already in the list of things to install, so add it now

                         return insertArchive(a,

                                 outArchives,

@@ -1185,7 +1194,7 @@
         fetchRemotePackages(remotePkgs, remoteSources);

         for (Package p : remotePkgs) {

             if (p instanceof PlatformPackage) {

-                if (((PlatformPackage) p).getVersion().equals(api)) {

+                if (((PlatformPackage) p).getAndroidVersion().equals(api)) {

                     // It's not already in the list of things to install, so add the

                     // first compatible archive we can find.

                     for (Archive a : p.getArchives()) {

@@ -1385,7 +1394,7 @@
      */

     private static class MissingArchiveInfo extends ArchiveInfo {

 

-        private final int mRevision;

+        private final FullRevision mRevision;

         private final String mTitle;

 

         public static final String TITLE_TOOL = "Tools";

@@ -1398,7 +1407,7 @@
          * @param title Typically "Tools" or "Platform-tools".

          * @param revision The required revision.

          */

-        public MissingArchiveInfo(String title, int revision) {

+        public MissingArchiveInfo(String title, FullRevision revision) {

             super(null /*newArchive*/, null /*replaced*/, null /*dependsOn*/);

             mTitle = title;

             mRevision = revision;

@@ -1418,7 +1427,9 @@
 

         @Override

         public String getShortDescription() {

-            return String.format("Missing Android SDK %1$s, revision %2$d", mTitle, mRevision);

+            return String.format("Missing Android SDK %1$s, revision %2$s",

+                    mTitle,

+                    mRevision.toShortString());

         }

     }

 }

diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
index a58e0f0..af40d78 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java
@@ -24,31 +24,28 @@
 import com.android.sdklib.SdkManager;

 import com.android.sdklib.internal.avd.AvdManager;

 import com.android.sdklib.internal.repository.AdbWrapper;

-import com.android.sdklib.internal.repository.AddonsListFetcher;

-import com.android.sdklib.internal.repository.AddonsListFetcher.Site;

-import com.android.sdklib.internal.repository.archives.Archive;

-import com.android.sdklib.internal.repository.archives.ArchiveInstaller;

-import com.android.sdklib.internal.repository.packages.AddonPackage;

-import com.android.sdklib.internal.repository.packages.Package;

-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;

-import com.android.sdklib.internal.repository.packages.ToolPackage;

-import com.android.sdklib.internal.repository.sources.SdkAddonSource;

-import com.android.sdklib.internal.repository.sources.SdkRepoSource;

-import com.android.sdklib.internal.repository.sources.SdkSource;

-import com.android.sdklib.internal.repository.sources.SdkSourceCategory;

-import com.android.sdklib.internal.repository.sources.SdkSources;

 import com.android.sdklib.internal.repository.DownloadCache;

 import com.android.sdklib.internal.repository.ITask;

 import com.android.sdklib.internal.repository.ITaskFactory;

 import com.android.sdklib.internal.repository.ITaskMonitor;

 import com.android.sdklib.internal.repository.LocalSdkParser;

 import com.android.sdklib.internal.repository.NullTaskMonitor;

+import com.android.sdklib.internal.repository.archives.Archive;

+import com.android.sdklib.internal.repository.archives.ArchiveInstaller;

+import com.android.sdklib.internal.repository.packages.AddonPackage;

+import com.android.sdklib.internal.repository.packages.Package;

+import com.android.sdklib.internal.repository.packages.PlatformToolPackage;

+import com.android.sdklib.internal.repository.packages.ToolPackage;

+import com.android.sdklib.internal.repository.sources.SdkRepoSource;

+import com.android.sdklib.internal.repository.sources.SdkSource;

+import com.android.sdklib.internal.repository.sources.SdkSourceCategory;

+import com.android.sdklib.internal.repository.sources.SdkSources;

 import com.android.sdklib.repository.SdkAddonConstants;

-import com.android.sdklib.repository.SdkAddonsListConstants;

 import com.android.sdklib.repository.SdkRepoConstants;

 import com.android.sdklib.util.LineUtil;

 import com.android.sdklib.util.SparseIntArray;

 import com.android.sdkuilib.internal.repository.icons.ImageFactory;

+import com.android.sdkuilib.internal.repository.sdkman2.PackageLoader;

 import com.android.sdkuilib.internal.repository.sdkman2.SdkUpdaterWindowImpl2;

 import com.android.sdkuilib.repository.ISdkChangeListener;

 

@@ -79,27 +76,32 @@
 

     private String mOsSdkRoot;

 

-    private final ISdkLog mSdkLog;

-    private ITaskFactory mTaskFactory;

-

-    private SdkManager mSdkManager;

-    private AvdManager mAvdManager;

-    private DownloadCache mDownloadCache; // lazily created in getDownloadCache

     private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();

     private final SdkSources mSources = new SdkSources();

-    private ImageFactory mImageFactory;

     private final SettingsController mSettingsController;

     private final ArrayList<ISdkChangeListener> mListeners = new ArrayList<ISdkChangeListener>();

+    private final ISdkLog mSdkLog;

+    private ITaskFactory mTaskFactory;

     private Shell mWindowShell;

-    private AndroidLocationException mAvdManagerInitError;

-

+    private SdkManager mSdkManager;

+    private AvdManager mAvdManager;

     /**

-     * 0 = need to fetch remote addons list once..

-     * 1 = fetch succeeded, don't need to do it any more.

-     * -1= fetch failed, do it again only if the user requests a refresh

-     *     or changes the force-http setting.

+     * The current {@link PackageLoader} to use.

+     * Lazily created in {@link #getPackageLoader()}.

      */

-    private int mStateFetchRemoteAddonsList;

+    private PackageLoader mPackageLoader;

+    /**

+     * The current {@link DownloadCache} to use.

+     * Lazily created in {@link #getDownloadCache()}.

+     */

+    private DownloadCache mDownloadCache;

+    /**

+     * The current {@link ImageFactory}.

+     * Set via {@link #setImageFactory(ImageFactory)} by the window implementation.

+     * It is null when invoked using the command-line interface.

+     */

+    private ImageFactory mImageFactory;

+    private AndroidLocationException mAvdManagerInitError;

 

     /**

      * Creates a new updater data.

@@ -111,7 +113,6 @@
         mOsSdkRoot = osSdkRoot;

         mSdkLog = sdkLog;

 

-        mDownloadCache = getDownloadCache();

         mSettingsController = new SettingsController(this);

 

         initSdk();

@@ -198,6 +199,14 @@
         return mWindowShell;

     }

 

+    public PackageLoader getPackageLoader() {

+        // The package loader is lazily initialized here.

+        if (mPackageLoader == null) {

+            mPackageLoader = new PackageLoader(this);

+        }

+        return mPackageLoader;

+    }

+

     /**

      * Check if any error occurred during initialization.

      * If it did, display an error message.

@@ -446,7 +455,7 @@
                                               mOsSdkRoot,

                                               forceHttp,

                                               mSdkManager,

-                                              mDownloadCache,

+                                              getDownloadCache(),

                                               monitor)) {

                             // We installed this archive.

                             newlyInstalledArchives.add(archive);

@@ -698,7 +707,7 @@
                 includeObsoletes);

 

         if (selectedArchives == null) {

-            loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));

+            getPackageLoader().loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));

             ul.addNewPlatforms(

                     archives,

                     getSources(),

@@ -733,7 +742,7 @@
      */

     private List<ArchiveInfo> getRemoteArchives_NoGUI(boolean includeAll) {

         refreshSources(true);

-        loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));

+        getPackageLoader().loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));

 

         List<ArchiveInfo> archives;

         SdkUpdaterLogic ul = new SdkUpdaterLogic(this);

@@ -998,9 +1007,7 @@
             @Override

             public void run(ITaskMonitor monitor) {

 

-                if (mStateFetchRemoteAddonsList <= 0) {

-                    loadRemoteAddonsListInTask(monitor);

-                }

+                getPackageLoader().loadRemoteAddonsList(monitor);

 

                 SdkSource[] sources = mSources.getAllSources();

                 monitor.setDescription("Refresh Sources");

@@ -1009,7 +1016,7 @@
                     if (forceFetching ||

                             source.getPackages() != null ||

                             source.getFetchError() != null) {

-                        source.load(mDownloadCache, monitor.createSubMonitor(1), forceHttp);

+                        source.load(getDownloadCache(), monitor.createSubMonitor(1), forceHttp);

                     }

                     monitor.incProgress(1);

                 }

@@ -1018,67 +1025,6 @@
     }

 

     /**

-     * Loads the remote add-ons list.

-     */

-    public void loadRemoteAddonsList(ITaskMonitor monitor) {

-

-        if (mStateFetchRemoteAddonsList != 0) {

-            return;

-        }

-

-        mTaskFactory.start("Load Add-ons List", monitor, new ITask() {

-            @Override

-            public void run(ITaskMonitor subMonitor) {

-                loadRemoteAddonsListInTask(subMonitor);

-            }

-        });

-    }

-

-    private void loadRemoteAddonsListInTask(ITaskMonitor monitor) {

-        mStateFetchRemoteAddonsList = -1;

-

-        String url = SdkAddonsListConstants.URL_ADDON_LIST;

-

-        // We override SdkRepoConstants.URL_GOOGLE_SDK_SITE if this is defined

-        String baseUrl = System.getenv("SDK_TEST_BASE_URL");            //$NON-NLS-1$

-        if (baseUrl != null) {

-            if (baseUrl.length() > 0 && baseUrl.endsWith("/")) {        //$NON-NLS-1$

-                if (url.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) {

-                    url = baseUrl + url.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length());

-                }

-            } else {

-                monitor.logError("Ignoring invalid SDK_TEST_BASE_URL: %1$s", baseUrl);  //$NON-NLS-1$

-            }

-        }

-

-        if (getSettingsController().getForceHttp()) {

-            url = url.replaceAll("https://", "http://");    //$NON-NLS-1$ //$NON-NLS-2$

-        }

-

-        // Hook to bypass loading 3rd party addons lists.

-        boolean fetch3rdParties = System.getenv("SDK_SKIP_3RD_PARTIES") == null;

-

-        AddonsListFetcher fetcher = new AddonsListFetcher();

-        Site[] sites = fetcher.fetch(url, mDownloadCache, monitor);

-        if (sites != null) {

-            mSources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY);

-

-            if (fetch3rdParties) {

-                for (Site s : sites) {

-                    mSources.add(SdkSourceCategory.ADDONS_3RD_PARTY,

-                                 new SdkAddonSource(s.getUrl(), s.getUiName()));

-                }

-            }

-

-            mSources.notifyChangeListeners();

-

-            mStateFetchRemoteAddonsList = 1;

-        }

-

-        monitor.setDescription("Fetched Add-ons List successfully");

-    }

-

-    /**

      * Safely invoke all the registered {@link ISdkChangeListener#onSdkLoaded()}.

      * This can be called from any thread.

      */

diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/AdtUpdateDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/AdtUpdateDialog.java
index d619ea0..ba9bce7 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/AdtUpdateDialog.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/AdtUpdateDialog.java
@@ -76,7 +76,7 @@
     private Map<Package, File> mResultPaths = null;

     private SettingsController mSettingsController;

     private PackageFilter mPackageFilter;

-    private PackageLoader mPackageMananger;

+    private PackageLoader mPackageLoader;

 

     private ProgressBar mProgressBar;

     private Label mStatusText;

@@ -221,12 +221,12 @@
 

         mUpdaterData.broadcastOnSdkLoaded();

 

-        mPackageMananger = new PackageLoader(mUpdaterData);

+        mPackageLoader = new PackageLoader(mUpdaterData);

     }

 

     @Override

     protected void eventLoop() {

-        mPackageMananger.loadPackagesWithInstallTask(

+        mPackageLoader.loadPackagesWithInstallTask(

                 mPackageFilter.installFlags(),

                 new IAutoInstallTask() {

             @Override

@@ -346,7 +346,7 @@
             boolean accept(Package pkg) {

                 if (pkg instanceof PlatformPackage) {

                     PlatformPackage pp = (PlatformPackage) pkg;

-                    AndroidVersion v = pp.getVersion();

+                    AndroidVersion v = pp.getAndroidVersion();

                     return !v.isPreview() && v.getApiLevel() == mApiLevel;

                 }

                 return false;

@@ -359,7 +359,7 @@
                         pkg instanceof PlatformPackage &&

                         !pkg.isLocal()) {

                     PlatformPackage pp = (PlatformPackage) pkg;

-                    AndroidVersion v = pp.getVersion();

+                    AndroidVersion v = pp.getAndroidVersion();

                     if (!v.isPreview()) {

                         int api = v.getApiLevel();

                         if (api > mApiLevel) {

@@ -388,7 +388,7 @@
                 if (!pkg.isLocal()) {

                     if (pkg instanceof PlatformPackage) {

                         PlatformPackage pp = (PlatformPackage) pkg;

-                        AndroidVersion v = pp.getVersion();

+                        AndroidVersion v = pp.getAndroidVersion();

                         if (!v.isPreview()) {

                             int level = v.getApiLevel();

                             if ((mFindMaxApi && level == mMaxApiLevel) ||

@@ -418,7 +418,7 @@
                         pkg instanceof PlatformPackage &&

                         !pkg.isLocal()) {

                     PlatformPackage pp = (PlatformPackage) pkg;

-                    AndroidVersion v = pp.getVersion();

+                    AndroidVersion v = pp.getAndroidVersion();

                     if (!v.isPreview()) {

                         int api = v.getApiLevel();

                         if (api > mMaxApiLevel) {

diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackageLoader.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackageLoader.java
index 4ac03e9..ecbefb7 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackageLoader.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackageLoader.java
@@ -16,6 +16,8 @@
 
 package com.android.sdkuilib.internal.repository.sdkman2;
 
+import com.android.sdklib.internal.repository.AddonsListFetcher;
+import com.android.sdklib.internal.repository.AddonsListFetcher.Site;
 import com.android.sdklib.internal.repository.DownloadCache;
 import com.android.sdklib.internal.repository.ITask;
 import com.android.sdklib.internal.repository.ITaskMonitor;
@@ -23,7 +25,12 @@
 import com.android.sdklib.internal.repository.archives.Archive;
 import com.android.sdklib.internal.repository.packages.Package;
 import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
+import com.android.sdklib.internal.repository.sources.SdkAddonSource;
 import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.internal.repository.sources.SdkSourceCategory;
+import com.android.sdklib.internal.repository.sources.SdkSources;
+import com.android.sdklib.repository.SdkAddonsListConstants;
+import com.android.sdklib.repository.SdkRepoConstants;
 import com.android.sdkuilib.internal.repository.UpdaterData;
 
 import org.eclipse.swt.widgets.Display;
@@ -39,13 +46,30 @@
  * Loads packages fetched from the remote SDK Repository and keeps track
  * of their state compared with the current local SDK installation.
  */
-class PackageLoader {
+public class PackageLoader {
 
+    /** The update data context. Never null. */
     private final UpdaterData mUpdaterData;
 
     /**
+     * The {@link DownloadCache} override. Can be null, in which case the one from
+     * {@link UpdaterData} is used instead.
+     * @see #getDownloadCache()
+     */
+    private final DownloadCache mOverrideCache;
+
+    /**
+     * 0 = need to fetch remote addons list once..
+     * 1 = fetch succeeded, don't need to do it any more.
+     * -1= fetch failed, do it again only if the user requests a refresh
+     *     or changes the force-http setting.
+     */
+    private int mStateFetchRemoteAddonsList;
+
+
+    /**
      * Interface for the callback called by
-     * {@link PackageLoader#loadPackages(DownloadCache, ISourceLoadedCallback)}.
+     * {@link PackageLoader#loadPackages(ISourceLoadedCallback)}.
      * <p/>
      * After processing each source, the package loader calls {@link #onUpdateSource}
      * with the list of packages found in that source.
@@ -128,12 +152,27 @@
     }
 
     /**
-     * Creates a new PackageManager associated with the given {@link UpdaterData}.
+     * Creates a new PackageManager associated with the given {@link UpdaterData}
+     * and using the {@link UpdaterData}'s default {@link DownloadCache}.
      *
      * @param updaterData The {@link UpdaterData}. Must not be null.
      */
     public PackageLoader(UpdaterData updaterData) {
         mUpdaterData = updaterData;
+        mOverrideCache = null;
+    }
+
+    /**
+     * Creates a new PackageManager associated with the given {@link UpdaterData}
+     * but using the specified {@link DownloadCache} instead of the one from
+     * {@link UpdaterData}.
+     *
+     * @param updaterData The {@link UpdaterData}. Must not be null.
+     * @param cache The {@link DownloadCache} to use instead of the one from {@link UpdaterData}.
+     */
+    public PackageLoader(UpdaterData updaterData, DownloadCache cache) {
+        mUpdaterData = updaterData;
+        mOverrideCache = cache;
     }
 
     /**
@@ -145,20 +184,12 @@
      * after each source is finished loaded. In return the callback tells the loader
      * whether to continue loading sources.
      */
-    public void loadPackages(
-            DownloadCache downloadCache,
-            final ISourceLoadedCallback sourceLoadedCallback) {
+    public void loadPackages(final ISourceLoadedCallback sourceLoadedCallback) {
         try {
             if (mUpdaterData == null) {
                 return;
             }
 
-            if (downloadCache == null) {
-                downloadCache = mUpdaterData.getDownloadCache();
-            }
-
-            final DownloadCache downloadCache2 = downloadCache;
-
             mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() {
                 @Override
                 public void run(ITaskMonitor monitor) {
@@ -176,7 +207,7 @@
 
                     // get remote packages
                     boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp();
-                    mUpdaterData.loadRemoteAddonsList(monitor.createSubMonitor(1));
+                    loadRemoteAddonsList(monitor.createSubMonitor(1));
 
                     SdkSource[] sources = mUpdaterData.getSources().getAllSources();
                     try {
@@ -186,7 +217,7 @@
                             for (SdkSource source : sources) {
                                 Package[] pkgs = source.getPackages();
                                 if (pkgs == null) {
-                                    source.load(downloadCache2,
+                                    source.load(getDownloadCache(),
                                             subMonitor.createSubMonitor(1),
                                             forceHttp);
                                     pkgs = source.getPackages();
@@ -215,8 +246,7 @@
     }
 
     /**
-     * Load packages, source by source using
-     * {@link #loadPackages(DownloadCache, ISourceLoadedCallback)},
+     * Load packages, source by source using {@link #loadPackages(ISourceLoadedCallback)},
      * and executes the given {@link IAutoInstallTask} on the current package list.
      * That is for each package known, the install task is queried to find if
      * the package is the one to be installed or updated.
@@ -248,8 +278,7 @@
             final int installFlags,
             final IAutoInstallTask installTask) {
 
-        loadPackages(mUpdaterData.getDownloadCache(),
-                     new ISourceLoadedCallback() {
+        loadPackages(new ISourceLoadedCallback() {
             List<Archive> mArchivesToInstall = new ArrayList<Archive>();
             Map<Package, File> mInstallPaths = new HashMap<Package, File>();
 
@@ -367,4 +396,77 @@
             }
         });
     }
+
+
+    /**
+     * Loads the remote add-ons list.
+     */
+    public void loadRemoteAddonsList(ITaskMonitor monitor) {
+
+        if (mStateFetchRemoteAddonsList != 0) {
+            return;
+        }
+
+        mUpdaterData.getTaskFactory().start("Load Add-ons List", monitor, new ITask() {
+            @Override
+            public void run(ITaskMonitor subMonitor) {
+                loadRemoteAddonsListInTask(subMonitor);
+            }
+        });
+    }
+
+    private void loadRemoteAddonsListInTask(ITaskMonitor monitor) {
+        mStateFetchRemoteAddonsList = -1;
+
+        String url = SdkAddonsListConstants.URL_ADDON_LIST;
+
+        // We override SdkRepoConstants.URL_GOOGLE_SDK_SITE if this is defined
+        String baseUrl = System.getenv("SDK_TEST_BASE_URL");            //$NON-NLS-1$
+        if (baseUrl != null) {
+            if (baseUrl.length() > 0 && baseUrl.endsWith("/")) {        //$NON-NLS-1$
+                if (url.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) {
+                    url = baseUrl + url.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length());
+                }
+            } else {
+                monitor.logError("Ignoring invalid SDK_TEST_BASE_URL: %1$s", baseUrl);  //$NON-NLS-1$
+            }
+        }
+
+        if (mUpdaterData.getSettingsController().getForceHttp()) {
+            url = url.replaceAll("https://", "http://");    //$NON-NLS-1$ //$NON-NLS-2$
+        }
+
+        // Hook to bypass loading 3rd party addons lists.
+        boolean fetch3rdParties = System.getenv("SDK_SKIP_3RD_PARTIES") == null;
+
+        AddonsListFetcher fetcher = new AddonsListFetcher();
+        Site[] sites = fetcher.fetch(url, getDownloadCache(), monitor);
+        if (sites != null) {
+            SdkSources sources = mUpdaterData.getSources();
+            sources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY);
+
+            if (fetch3rdParties) {
+                for (Site s : sites) {
+                    sources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
+                                 new SdkAddonSource(s.getUrl(), s.getUiName()));
+                }
+            }
+
+            sources.notifyChangeListeners();
+
+            mStateFetchRemoteAddonsList = 1;
+        }
+
+        monitor.setDescription("Fetched Add-ons List successfully");
+    }
+
+    /**
+     * Returns the {@link DownloadCache} to use.
+     *
+     * @return Returns {@link #mOverrideCache} if not null; otherwise returns the
+     *  one from {@link UpdaterData} is used instead.
+     */
+    private DownloadCache getDownloadCache() {
+        return mOverrideCache != null ? mOverrideCache : mUpdaterData.getDownloadCache();
+    }
 }
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java
index 46b5697..9e45748 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java
@@ -20,8 +20,10 @@
 import com.android.sdklib.IAndroidTarget;
 import com.android.sdklib.SdkConstants;
 import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.IPackageVersion;
+import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
+import com.android.sdklib.internal.repository.packages.IFullRevisionProvider;
 import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
 import com.android.sdklib.internal.repository.packages.PlatformPackage;
 import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
 import com.android.sdklib.internal.repository.packages.SystemImagePackage;
@@ -46,17 +48,11 @@
  * so that we can test it using head-less unit tests.
  */
 class PackagesDiffLogic {
-    private final PackageLoader mPackageLoader;
     private final UpdaterData mUpdaterData;
     private boolean mFirstLoadComplete = true;
 
     public PackagesDiffLogic(UpdaterData updaterData) {
         mUpdaterData = updaterData;
-        mPackageLoader = new PackageLoader(updaterData);
-    }
-
-    public PackageLoader getPackageLoader() {
-        return mPackageLoader;
     }
 
     /**
@@ -80,11 +76,12 @@
     /**
      * Mark all new and update PkgItems as checked.
      *
-     * @param selectNew If true, select all new packages
-     * @param selectUpdates If true, select all update packages
-     * @param selectTop If true, select the top platform. If the top platform has nothing installed,
-     *   select all items in it; if it is partially installed, at least select the platform and
-     *   system images if none of the system images are installed.
+     * @param selectNew If true, select all new packages (except the rc/preview ones).
+     * @param selectUpdates If true, select all update packages.
+     * @param selectTop If true, select the top platform.
+     *   If the top platform has nothing installed, select all items in it (except the rc/preview);
+     *   If it is partially installed, at least select the platform and system images if none of
+     *   the system images are installed.
      * @param currentPlatform The {@link SdkConstants#currentPlatform()} value.
      */
     public void checkNewUpdateItems(
@@ -97,7 +94,8 @@
         SparseArray<List<PkgItem>> platformItems = new SparseArray<List<PkgItem>>();
 
         // sort items in platforms... directly deal with new/update items
-        for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) {
+        List<PkgItem> allItems = getAllPkgItems(true /*byApi*/, true /*bySource*/);
+        for (PkgItem item : allItems) {
             if (!item.hasCompatibleArchive()) {
                 // Ignore items that have no archive compatible with the current platform.
                 continue;
@@ -107,8 +105,8 @@
             // since by definition they should target the same API level.
             int api = 0;
             Package p = item.getMainPackage();
-            if (p instanceof IPackageVersion) {
-                api = ((IPackageVersion) p).getVersion().getApiLevel();
+            if (p instanceof IAndroidVersionProvider) {
+                api = ((IAndroidVersionProvider) p).getAndroidVersion().getApiLevel();
             }
 
             if (selectTop && api > 0) {
@@ -129,8 +127,42 @@
                 items.add(item);
             }
 
-            if ((selectNew && item.getState() == PkgState.NEW) ||
-                    (selectUpdates && item.hasUpdatePkg())) {
+            if ((selectUpdates || selectNew) &&
+                    item.getState() == PkgState.NEW &&
+                    !item.getRevision().isPreview()) {
+                boolean sameFound = false;
+                Package newPkg = item.getMainPackage();
+                if (newPkg instanceof IFullRevisionProvider) {
+                    // We have a potential new non-preview package; but this kind of package
+                    // supports having previews, which means we want to make sure we're not
+                    // offering an older "new" non-preview if there's a newer preview installed.
+                    //
+                    // We should get into this odd situation only when updating an RC/preview
+                    // by a final release pkg.
+
+                    IFullRevisionProvider newPkg2 = (IFullRevisionProvider) newPkg;
+                    for (PkgItem item2 : allItems) {
+                        if (item2.getState() == PkgState.INSTALLED) {
+                            Package installed = item2.getMainPackage();
+
+                            if (installed.getRevision().isPreview() &&
+                                    newPkg2.sameItemAs(installed, true /*ignorePreviews*/)) {
+                                sameFound = true;
+
+                                if (installed.canBeUpdatedBy(newPkg) == UpdateInfo.UPDATE) {
+                                    item.setChecked(true);
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if (selectNew && !sameFound) {
+                    item.setChecked(true);
+                }
+
+            } else if (selectUpdates && item.hasUpdatePkg()) {
                 item.setChecked(true);
             }
         }
@@ -140,7 +172,8 @@
             if (!installedPlatforms.contains(maxApi)) {
                 // If the top platform has nothing installed at all, select everything in it
                 for (PkgItem item : items) {
-                    if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) {
+                    if ((item.getState() == PkgState.NEW && !item.getRevision().isPreview()) ||
+                            item.hasUpdatePkg()) {
                         item.setChecked(true);
                     }
                 }
@@ -151,7 +184,8 @@
                 // First make sure the platform package itself is installed, or select it.
                 for (PkgItem item : items) {
                      Package p = item.getMainPackage();
-                     if (p instanceof PlatformPackage && item.getState() == PkgState.NEW) {
+                     if (p instanceof PlatformPackage &&
+                             item.getState() == PkgState.NEW && !item.getRevision().isPreview()) {
                          item.setChecked(true);
                          break;
                      }
@@ -163,7 +197,7 @@
                     Package p = item.getMainPackage();
                     if (p instanceof PlatformPackage && item.getState() == PkgState.INSTALLED) {
                         if (item.hasUpdatePkg() && item.isChecked()) {
-                            // If the installed platform is schedule for update, look for the
+                            // If the installed platform is scheduled for update, look for the
                             // system image in the update package, not the current one.
                             p = item.getUpdatePkg();
                             if (p instanceof PlatformPackage) {
@@ -190,6 +224,7 @@
                          Package p = item.getMainPackage();
                          if (p instanceof PlatformPackage) {
                              if (item.getState() == PkgState.NEW &&
+                                     !item.getRevision().isPreview() &&
                                      ((PlatformPackage) p).getIncludedAbi() != null) {
                                  item.setChecked(true);
                                  hasSysImg = true;
@@ -220,7 +255,9 @@
             // On Windows, we'll also auto-select the USB driver
             for (PkgItem item : getAllPkgItems(true /*byApi*/, true /*bySource*/)) {
                 Package p = item.getMainPackage();
-                if (p instanceof ExtraPackage && item.getState() == PkgState.NEW) {
+                if (p instanceof ExtraPackage &&
+                        item.getState() == PkgState.NEW &&
+                        !item.getRevision().isPreview()) {
                     ExtraPackage ep = (ExtraPackage) p;
                     if (ep.getVendorId().equals("google") &&            //$NON-NLS-1$
                             ep.getPath().equals("usb_driver")) {        //$NON-NLS-1$
@@ -519,7 +556,7 @@
 
                         switch (currItem.getState()) {
                         case NEW:
-                            if (newPkg.getRevision() < mainPkg.getRevision()) {
+                            if (newPkg.getRevision().compareTo(mainPkg.getRevision()) < 0) {
                                 if (!op.isKeep(currItem)) {
                                     // The new item has a lower revision than the current one,
                                     // but the current one hasn't been marked as being kept so
@@ -528,7 +565,7 @@
                                     addNewItem(op, newPkg, PkgState.NEW);
                                     hasChanged = true;
                                 }
-                            } else if (newPkg.getRevision() > mainPkg.getRevision()) {
+                            } else if (newPkg.getRevision().compareTo(mainPkg.getRevision()) > 0) {
                                 // We have a more recent new version, remove the current one
                                 // and replace by a new one
                                 currItemIt.remove();
@@ -538,7 +575,7 @@
                             break;
                         case INSTALLED:
                             // if newPkg.revision<=mainPkg.revision: it's already installed, ignore.
-                            if (newPkg.getRevision() > mainPkg.getRevision()) {
+                            if (newPkg.getRevision().compareTo(mainPkg.getRevision()) > 0) {
                                 // This is a new update for the main package.
                                 if (currItem.mergeUpdate(newPkg)) {
                                     op.keep(currItem.getUpdatePkg());
@@ -607,12 +644,15 @@
         public Object getCategoryKey(Package pkg) {
             // Sort by API
 
-            if (pkg instanceof IPackageVersion) {
-                return ((IPackageVersion) pkg).getVersion();
+            if (pkg instanceof IAndroidVersionProvider) {
+                return ((IAndroidVersionProvider) pkg).getAndroidVersion();
 
             } else if (pkg instanceof ToolPackage || pkg instanceof PlatformToolPackage) {
-                return PkgCategoryApi.KEY_TOOLS;
-
+                if (pkg.getRevision().isPreview()) {
+                    return PkgCategoryApi.KEY_TOOLS_PREVIEW;
+                } else {
+                    return PkgCategoryApi.KEY_TOOLS;
+                }
             } else {
                 return PkgCategoryApi.KEY_EXTRA;
             }
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesPage.java
index 4ef7d6b..40a3538 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesPage.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesPage.java
@@ -17,6 +17,8 @@
 package com.android.sdkuilib.internal.repository.sdkman2;
 
 import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.repository.DownloadCache;
+import com.android.sdklib.internal.repository.DownloadCache.Strategy;
 import com.android.sdklib.internal.repository.IDescription;
 import com.android.sdklib.internal.repository.ITask;
 import com.android.sdklib.internal.repository.ITaskMonitor;
@@ -129,6 +131,7 @@
     private final SdkInvocationContext mContext;
     private final UpdaterData mUpdaterData;
     private final PackagesDiffLogic mDiffLogic;
+
     private boolean mDisplayArchives = false;
     private boolean mOperationPending;
 
@@ -168,8 +171,15 @@
     }
 
     public void performFirstLoad() {
-        // Initialize the package list the first time the page is shown.
-        loadPackages(true /*isFirstLoad*/);
+        // First a package loader is created that only checks
+        // the local cache xml files. It populates the package
+        // list based on what the client got last, essentially.
+        loadPackages(true /*useLocalCache*/);
+
+        // Next a regular package loader is created that will
+        // respect the expiration and refresh parameters of the
+        // download cache.
+        loadPackages(false /*useLocalCache*/);
     }
 
     @SuppressWarnings("unused")
@@ -576,11 +586,24 @@
         loadPackages();
     }
 
+    /**
+     * Performs a "normal" reload of the package information, use the default download
+     * cache and refreshing strategy as needed.
+     */
     private void loadPackages() {
-        loadPackages(false /*isFirstLoad*/);
+        loadPackages(false /*useLocalCache*/);
     }
 
-    private void loadPackages(final boolean isFirstLoad) {
+    /**
+     * Performs a reload of the package information.
+     *
+     * @param useLocalCache When true, the {@link PackageLoader} is switched to use
+     *  a specific {@link DownloadCache} using the {@link Strategy#ONLY_CACHE}, meaning
+     *  it will only use data from the local cache. It will not try to fetch or refresh
+     *  manifests. This is used once the very first time the sdk manager window opens
+     *  and is typically followed by a regular load with refresh.
+     */
+    private void loadPackages(final boolean useLocalCache) {
         if (mUpdaterData == null) {
             return;
         }
@@ -593,15 +616,26 @@
 
         final boolean displaySortByApi = isSortByApi();
 
-        if (!mTreeColumnName.isDisposed()) {
-            mTreeColumnName.setImage(
-                    getImage(displaySortByApi ? ICON_SORT_BY_API : ICON_SORT_BY_SOURCE));
+        if (mTreeColumnName.isDisposed()) {
+            // If the UI got disposed, don't try to load anything since we won't be
+            // able to display it anyway.
+            return;
         }
 
+        mTreeColumnName.setImage(getImage(displaySortByApi ? ICON_SORT_BY_API
+                                                           : ICON_SORT_BY_SOURCE));
+
+        PackageLoader packageLoader = null;
+        if (useLocalCache) {
+            packageLoader =
+                new PackageLoader(mUpdaterData, new DownloadCache(Strategy.ONLY_CACHE));
+        } else {
+            packageLoader = mUpdaterData.getPackageLoader();
+        }
+        assert packageLoader != null;
+
         mDiffLogic.updateStart();
-        mDiffLogic.getPackageLoader().loadPackages(
-                mUpdaterData.getDownloadCache(), // TODO do a first pass with Cache=SERVE_CACHE
-                new ISourceLoadedCallback() {
+        packageLoader.loadPackages(new ISourceLoadedCallback() {
             @Override
             public boolean onUpdateSource(SdkSource source, Package[] newPackages) {
                 // This runs in a thread and must not access UI directly.
@@ -640,7 +674,9 @@
                                 refreshViewerInput();
                             }
 
-                            if (mDiffLogic.isFirstLoadComplete() && !mGroupPackages.isDisposed()) {
+                            if (!useLocalCache &&
+                                    mDiffLogic.isFirstLoadComplete() &&
+                                    !mGroupPackages.isDisposed()) {
                                 // At the end of the first load, if nothing is selected then
                                 // automatically select all new and update packages.
                                 Object[] checked = mTreeViewer.getCheckedElements();
@@ -1313,7 +1349,7 @@
             } else if (mColumn == mColumnRevision) {
                 if (element instanceof PkgItem) {
                     PkgItem pkg = (PkgItem) element;
-                    return Integer.toString(pkg.getRevision());
+                    return pkg.getRevision().toShortString();
                 }
 
             } else if (mColumn == mColumnStatus) {
@@ -1343,7 +1379,7 @@
 
                 } else if (element instanceof Package) {
                     // This is an update package.
-                    return "New revision " + Integer.toString(((Package) element).getRevision());
+                    return "New revision " + ((Package) element).getRevision().toShortString();
                 }
             }
 
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgCategoryApi.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgCategoryApi.java
index 5bb4917..d1e3a28 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgCategoryApi.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgCategoryApi.java
@@ -31,7 +31,9 @@
     // them.
     // (Note: don't use integer.max to avoid integers wrapping in comparisons. We can
     // revisit the day we get 2^30 platforms.)
-    public final static AndroidVersion KEY_TOOLS = new AndroidVersion(Integer.MAX_VALUE / 2, null);;
+    public final static AndroidVersion KEY_TOOLS = new AndroidVersion(Integer.MAX_VALUE / 2, null);
+    public final static AndroidVersion KEY_TOOLS_PREVIEW =
+                                               new AndroidVersion(Integer.MAX_VALUE / 2 - 1, null);
     public final static AndroidVersion KEY_EXTRA = new AndroidVersion(-1, null);
 
     public PkgCategoryApi(AndroidVersion version, String platformName, Object iconRef) {
@@ -55,6 +57,8 @@
         AndroidVersion key = (AndroidVersion) getKey();
         if (key.equals(KEY_TOOLS)) {
             return "TOOLS";             //$NON-NLS-1$ // for internal debug use only
+        } else if (key.equals(KEY_TOOLS_PREVIEW)) {
+                return "TOOLS-PREVIEW"; //$NON-NLS-1$ // for internal debug use only
         } else if (key.equals(KEY_EXTRA)) {
             return "EXTRAS";            //$NON-NLS-1$ // for internal debug use only
         } else {
@@ -70,6 +74,8 @@
 
             if (key.equals(KEY_TOOLS)) {
                 label = "Tools";
+            } else if (key.equals(KEY_TOOLS_PREVIEW)) {
+                label = "Tools (Beta Channel)";
             } else if (key.equals(KEY_EXTRA)) {
                 label = "Extras";
             } else {
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java
index 5b90375..89910cd 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java
@@ -17,7 +17,8 @@
 package com.android.sdkuilib.internal.repository.sdkman2;
 
 import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.packages.IPackageVersion;
+import com.android.sdklib.internal.repository.packages.FullRevision;
+import com.android.sdklib.internal.repository.packages.IAndroidVersionProvider;
 import com.android.sdklib.internal.repository.packages.Package;
 import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
 import com.android.sdklib.internal.repository.sources.SdkSource;
@@ -89,7 +90,7 @@
         return mMainPkg.getListDescription();
     }
 
-    public int getRevision() {
+    public FullRevision getRevision() {
         return mMainPkg.getRevision();
     }
 
@@ -110,8 +111,8 @@
     }
 
     public int getApi() {
-        return mMainPkg instanceof IPackageVersion ?
-                ((IPackageVersion) mMainPkg).getVersion().getApiLevel() :
+        return mMainPkg instanceof IAndroidVersionProvider ?
+                ((IAndroidVersionProvider) mMainPkg).getAndroidVersion().getApiLevel() :
                     -1;
     }
 
@@ -149,24 +150,24 @@
 
     /**
      * Checks whether the main packages are of the same type and are
-     * not an update of each other.
+     * not an update of each other and have the same revision number.
      */
     public boolean isSameMainPackageAs(Package pkg) {
         if (mMainPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
             // package revision numbers must match
-            return mMainPkg.getRevision() == pkg.getRevision();
+            return mMainPkg.getRevision().equals(pkg.getRevision());
         }
         return false;
     }
 
     /**
      * Checks whether the update packages are of the same type and are
-     * not an update of each other.
+     * not an update of each other and have the same revision numbers.
      */
     public boolean isSameUpdatePackageAs(Package pkg) {
         if (mUpdatePkg != null && mUpdatePkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
             // package revision numbers must match
-            return mUpdatePkg.getRevision() == pkg.getRevision();
+            return mUpdatePkg.getRevision().equals(pkg.getRevision());
         }
         return false;
     }
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java
index 8444f9f..3448852 100755
--- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java
+++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java
@@ -100,6 +100,13 @@
     /**

      * Starts the task and block till it's either finished or canceled.

      * This can be called from a non-UI thread safely.

+     * <p/>

+     * When a task is started from within a monitor, it reuses the thread

+     * from the parent. Otherwise it starts a new thread and runs it own

+     * UI loop. This means the task can perform UI operations using

+     * {@link Display#asyncExec(Runnable)}.

+     * <p/>

+     * In either case, the method only returns when the task has finished.

      */

     public void startTask(

             final String title,

diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java
index a328ef1..26b39ed 100755
--- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java
+++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java
@@ -18,6 +18,7 @@
 
 import com.android.sdklib.SdkConstants;
 import com.android.sdklib.internal.repository.packages.BrokenPackage;
+import com.android.sdklib.internal.repository.packages.FullRevision;
 import com.android.sdklib.internal.repository.packages.MockAddonPackage;
 import com.android.sdklib.internal.repository.packages.MockBrokenPackage;
 import com.android.sdklib.internal.repository.packages.MockEmptyPackage;
@@ -1290,7 +1291,6 @@
     }
 
     public void testBrokenAddon() {
-
         SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
         SdkSource src2 = new SdkRepoSource("http://2.example.com/url2", "repo2");
 
@@ -1383,8 +1383,6 @@
     }
 
     public void testToolsUpdate() {
-        //
-
         SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
         SdkSource src2 = new SdkRepoSource("http://2.example.com/url2", "repo2");
         MockPlatformPackage p1;
@@ -1427,6 +1425,327 @@
                 "-- <NEW, pkg:The addon B from vendor 1, Android API 1, revision 6>\n",
                 getTree(m, false /*displaySortByApi*/));
     }
+
+    public void testToolsMinorUpdate() {
+        // Test: Check a minor revision updates an installed major revision.
+
+        SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
+
+        m.updateStart();
+        m.updateSourcePackages(true /*sortByApi*/, null /*locals*/, new Package[] {
+                new MockToolPackage(3, 3),                                          // Tools 3.0.0
+                new MockPlatformToolPackage(src1, 3),
+        });
+        m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+                new MockToolPackage(src1, new FullRevision(3, 0, 1), 3),          // Tools 3.0.1
+        });
+        m.updateEnd(true /*sortByApi*/);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+                getTree(m, true /*displaySortByApi*/));
+        assertEquals(
+                "PkgCategorySource <source=Local Packages (no.source), #items=1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+                "PkgCategorySource <source=repo1 (1.example.com), #items=1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3>\n",
+                getTree(m, false /*displaySortByApi*/));
+    }
+
+    public void testToolsPreviews() {
+        // Test: No local tools installed. The remote server has both tools and platforms
+        // in release and RC versions.
+
+        SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
+
+        m.updateStart();
+        m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+                new MockToolPackage(src1, new FullRevision(2, 0, 0), 3),          // Tools 2
+                new MockToolPackage(src1, new FullRevision(4, 0, 0, 1), 3),       // Tools 4 rc1
+                new MockPlatformToolPackage(src1, new FullRevision(3, 0, 0)),     // Plat-T 3
+                new MockPlatformToolPackage(src1, new FullRevision(5, 0, 0, 1)),  // Plat-T 5 rc1
+        });
+        m.updateEnd(true /*sortByApi*/);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 2>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 3>\n" +
+                "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 5 rc1>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+                getTree(m, true /*displaySortByApi*/));
+        assertEquals(
+                "PkgCategorySource <source=repo1 (1.example.com), #items=4>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 2>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 3>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 5 rc1>\n",
+                getTree(m, false /*displaySortByApi*/));
+    }
+
+    public void testPreviewUpdateInstalledRelease() {
+        // Test: Local release Tools 3.0.0 installed, server has both a release 3.0.1 available
+        // and a Tools Preview 4.0.0 rc1 available.
+        // => v3 is updated by 3.0.1
+        // => v4.0.0rc1 does not update 3.0.0, instead it's a separate download.
+
+        SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
+
+        m.updateStart();
+        m.updateSourcePackages(true /*sortByApi*/, null /*locals*/, new Package[] {
+                new MockToolPackage(3, 3),    // tool package has no source defined
+                new MockPlatformToolPackage(src1, 3),
+                new MockPlatformPackage(src1, 1, 2, 3),    // API 1
+        });
+        m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+                new MockToolPackage(src1, 3, 3),                                  // Tools 3
+                new MockToolPackage(src1, new FullRevision(3, 0, 1), 3),          // Tools 3.0.1
+                new MockToolPackage(src1, new FullRevision(4, 0, 0, 1), 3),       // Tools 4 rc1
+                new MockPlatformToolPackage(src1, new FullRevision(3, 0, 1)),     // PT    3.0.1
+                new MockPlatformToolPackage(src1, new FullRevision(4, 0, 0, 1)),  // PT    4 rc1
+        });
+        m.updateEnd(true /*sortByApi*/);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3, updated by:Android SDK Platform-tools, revision 3.0.1>\n" +
+                "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 4 rc1>\n" +
+                "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+                getTree(m, true /*displaySortByApi*/));
+        assertEquals(
+                "PkgCategorySource <source=Local Packages (no.source), #items=1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+                "PkgCategorySource <source=repo1 (1.example.com), #items=4>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 3, updated by:Android SDK Platform-tools, revision 3.0.1>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 4 rc1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+                getTree(m, false /*displaySortByApi*/));
+
+        // Now request to check new items and updates:
+        // Tools 4 rc1 is greater than the installed Tools 3, but it's a preview so we will NOT
+        //   auto-select it by default even though we requested to select "NEW" packages. We
+        //   want the user to manually opt-in into the rc/preview package.
+        // However Tools 3 has a 3.0.1 update that we'll auto-select.
+        m.checkNewUpdateItems(true, true, false, SdkConstants.PLATFORM_LINUX);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+                "-- < * INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+                "-- < * INSTALLED, pkg:Android SDK Platform-tools, revision 3, updated by:Android SDK Platform-tools, revision 3.0.1>\n" +
+                "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 4 rc1>\n" +
+                "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+                getTree(m, true /*displaySortByApi*/));
+        assertEquals(
+                "PkgCategorySource <source=Local Packages (no.source), #items=1>\n" +
+                "-- < * INSTALLED, pkg:Android SDK Tools, revision 3, updated by:Android SDK Tools, revision 3.0.1>\n" +
+                "PkgCategorySource <source=repo1 (1.example.com), #items=4>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 4 rc1>\n" +
+                "-- < * INSTALLED, pkg:Android SDK Platform-tools, revision 3, updated by:Android SDK Platform-tools, revision 3.0.1>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 4 rc1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+                getTree(m, false /*displaySortByApi*/));
+
+    }
+
+    public void testPreviewUpdateInstalledPreview() {
+        // Test: Local preview Tools 3.0.1rc1 installed, server has both a release 3.0.0 available
+        // and a Tools Preview 3.0.1 rc2 available.
+        // => Installed 3.0.1rc1 can be updated by 3.0.1rc2
+        // => There's a separate "new" download for 3.0.0, not installed and NOT updating 3.0.1rc1.
+
+        SdkSource src1 = new SdkRepoSource("http://1.example.com/url1", "repo1");
+
+        m.updateStart();
+        m.updateSourcePackages(true /*sortByApi*/, null /*locals*/, new Package[] {
+                new MockToolPackage(src1, new FullRevision(3, 0, 1, 1), 4),       //  T 3.0.1rc1
+                new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1, 1)),  // PT 4.0.1rc1
+                new MockPlatformPackage(src1, 1, 2, 3),    // API 1
+        });
+        m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+                new MockToolPackage(src1, new FullRevision(3, 0, 0), 4),          //  T 3.0.0
+                new MockToolPackage(src1, new FullRevision(3, 0, 1, 2), 4),       //  T 3.0.1rc2
+                new MockPlatformToolPackage(src1, new FullRevision(4, 0, 0)),     // PT 4.0.0
+                new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1, 2)),  // PT 4.0.1 rc2
+        });
+        m.updateEnd(true /*sortByApi*/);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 3>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 4>\n" +
+                "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1, updated by:Android SDK Tools, revision 3.0.1 rc2>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1, updated by:Android SDK Platform-tools, revision 4.0.1 rc2>\n" +
+                "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+                getTree(m, true /*displaySortByApi*/));
+        assertEquals(
+                "PkgCategorySource <source=repo1 (1.example.com), #items=5>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 3>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1, updated by:Android SDK Tools, revision 3.0.1 rc2>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 4>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1, updated by:Android SDK Platform-tools, revision 4.0.1 rc2>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+                getTree(m, false /*displaySortByApi*/));
+
+        // Auto select new and update items. In this case:
+        // - the previews have updates available.
+        // - we're not selecting the non-installed "3.0" version that is older than the
+        //   currently installed "3.0.1rc1" version since that would be a downgrade.
+        m.checkNewUpdateItems(true, true, false, SdkConstants.PLATFORM_LINUX);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 3>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 4>\n" +
+                "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+                "-- < * INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1, updated by:Android SDK Tools, revision 3.0.1 rc2>\n" +
+                "-- < * INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1, updated by:Android SDK Platform-tools, revision 4.0.1 rc2>\n" +
+                "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+                getTree(m, true /*displaySortByApi*/));
+        assertEquals(
+                "PkgCategorySource <source=repo1 (1.example.com), #items=5>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 3>\n" +
+                "-- < * INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1, updated by:Android SDK Tools, revision 3.0.1 rc2>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 4>\n" +
+                "-- < * INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1, updated by:Android SDK Platform-tools, revision 4.0.1 rc2>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+                getTree(m, false /*displaySortByApi*/));
+
+        // -----
+
+        // Now simulate that the server has a final package (3.0.1) to replace the
+        // installed 3.0.1rc1 package. It's not installed yet, just available.
+        // - A new 3.0.1 will be available.
+        // - The server no longer lists the RC since there's a final package, yet it is
+        //   still locally installed.
+        // - The 3.0.1 rc1 is not listed as having an update, since we treat the previews
+        //   separately. TODO: consider having the 3.0.1 show up as both a new item /and/
+        //   as an update to the 3.0.1rc1. That may have some other side effects.
+
+        m.uncheckAllItems();
+        m.updateStart();
+        m.updateSourcePackages(true /*sortByApi*/, null /*locals*/, new Package[] {
+                new MockToolPackage(src1, new FullRevision(3, 0, 1, 1), 4),       //  T 3.0.1rc1
+                new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1, 1)),  // PT 4.0.1rc1
+                new MockPlatformPackage(src1, 1, 2, 3),    // API 1
+        });
+        m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+                new MockToolPackage(src1, new FullRevision(3, 0, 1), 4),          //  T 3.0.1
+                new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1)),     // PT 4.0.1
+        });
+        m.updateEnd(true /*sortByApi*/);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 3.0.1>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+                "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1>\n" +
+                "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+                getTree(m, true /*displaySortByApi*/));
+        assertEquals(
+                "PkgCategorySource <source=repo1 (1.example.com), #items=5>\n" +
+                "-- <NEW, pkg:Android SDK Tools, revision 3.0.1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1>\n" +
+                "-- <NEW, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+                getTree(m, false /*displaySortByApi*/));
+
+        // Auto select new and update items. In this case the new items are considered
+        // updates and yet new at the same time.
+        // Test by selecting new items only:
+        m.checkNewUpdateItems(true, false, false, SdkConstants.PLATFORM_LINUX);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+                "-- < * NEW, pkg:Android SDK Tools, revision 3.0.1>\n" +
+                "-- < * NEW, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+                "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1>\n" +
+                "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+                getTree(m, true /*displaySortByApi*/));
+
+        // Test by selecting update items only:
+        m.uncheckAllItems();
+        m.checkNewUpdateItems(false, true, false, SdkConstants.PLATFORM_LINUX);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+                "-- < * NEW, pkg:Android SDK Tools, revision 3.0.1>\n" +
+                "-- < * NEW, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+                "PkgCategoryApi <API=TOOLS-PREVIEW, label=Tools (Beta Channel), #items=2>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1 rc1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1 rc1>\n" +
+                "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+                getTree(m, true /*displaySortByApi*/));
+
+
+        // -----
+
+        // Now simulate that the user has installed the final package (3.0.1) to replace the
+        // installed 3.0.1rc1 package.
+        // - The 3.0.1 is installed.
+        // - The 3.0.1 rc1 isn't listed anymore by the server.
+
+        m.uncheckAllItems();
+        m.updateStart();
+        m.updateSourcePackages(true /*sortByApi*/, null /*locals*/, new Package[] {
+                new MockToolPackage(src1, new FullRevision(3, 0, 1), 4),          //  T 3.0.1
+                new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1)),     // PT 4.0.1
+                new MockPlatformPackage(src1, 1, 2, 3),    // API 1
+        });
+        m.updateSourcePackages(true /*sortByApi*/, src1, new Package[] {
+                new MockToolPackage(src1, new FullRevision(3, 0, 1), 4),          //  T 3.0.1
+                new MockPlatformToolPackage(src1, new FullRevision(4, 0, 1)),     // PT 4.0.1
+        });
+        m.updateEnd(true /*sortByApi*/);
+
+        assertEquals(
+                "PkgCategoryApi <API=TOOLS, label=Tools, #items=2>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+                "PkgCategoryApi <API=API 1, label=Android android-1 (API 1), #items=1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n" +
+                "PkgCategoryApi <API=EXTRAS, label=Extras, #items=0>\n",
+                getTree(m, true /*displaySortByApi*/));
+        assertEquals(
+                "PkgCategorySource <source=repo1 (1.example.com), #items=3>\n" +
+                "-- <INSTALLED, pkg:Android SDK Tools, revision 3.0.1>\n" +
+                "-- <INSTALLED, pkg:Android SDK Platform-tools, revision 4.0.1>\n" +
+                "-- <INSTALLED, pkg:SDK Platform Android android-1, API 1, revision 2>\n",
+                getTree(m, false /*displaySortByApi*/));
+    }
+
+
+
     // ----
 
     /**