Merge "Unhide new Beam push APIs."
diff --git a/Android.mk b/Android.mk
index cacdee9..4e3929c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -111,6 +111,7 @@
 	core/java/android/database/IContentObserver.aidl \
 	core/java/android/hardware/ISerialManager.aidl \
 	core/java/android/hardware/input/IInputManager.aidl \
+	core/java/android/hardware/input/IInputDevicesChangedListener.aidl \
 	core/java/android/hardware/usb/IUsbManager.aidl \
 	core/java/android/net/IConnectivityManager.aidl \
 	core/java/android/net/INetworkManagementEventObserver.aidl \
diff --git a/api/16.txt b/api/16.txt
index 8ff7675..de99eee 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -24839,7 +24839,6 @@
   }
 
   public final class GeolocationPermissions {
-    ctor public GeolocationPermissions();
     method public void allow(java.lang.String);
     method public void clear(java.lang.String);
     method public void clearAll();
@@ -25129,7 +25128,6 @@
   }
 
   public final class WebStorage {
-    ctor public WebStorage();
     method public void deleteAllData();
     method public void deleteOrigin(java.lang.String);
     method public static android.webkit.WebStorage getInstance();
@@ -25204,7 +25202,6 @@
     method public java.lang.String getTitle();
     method public java.lang.String getUrl();
     method public deprecated int getVisibleTitleHeight();
-    method public deprecated android.view.View getZoomControls();
     method public void goBack();
     method public void goBackOrForward(int);
     method public void goForward();
@@ -41832,7 +41829,7 @@
     method public static void fail();
   }
 
-  public class AssertionFailedError extends java.lang.Error {
+  public class AssertionFailedError extends java.lang.AssertionError {
     ctor public AssertionFailedError();
     ctor public AssertionFailedError(java.lang.String);
   }
@@ -41890,9 +41887,9 @@
     method public synchronized void addListener(junit.framework.TestListener);
     method public void endTest(junit.framework.Test);
     method public synchronized int errorCount();
-    method public synchronized java.util.Enumeration errors();
+    method public synchronized java.util.Enumeration<junit.framework.TestFailure> errors();
     method public synchronized int failureCount();
-    method public synchronized java.util.Enumeration failures();
+    method public synchronized java.util.Enumeration<junit.framework.TestFailure> failures();
     method public synchronized void removeListener(junit.framework.TestListener);
     method protected void run(junit.framework.TestCase);
     method public synchronized int runCount();
@@ -41909,21 +41906,21 @@
 
   public class TestSuite implements junit.framework.Test {
     ctor public TestSuite();
-    ctor public TestSuite(java.lang.Class, java.lang.String);
-    ctor public TestSuite(java.lang.Class);
+    ctor public TestSuite(java.lang.Class<?>);
+    ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>, java.lang.String);
     ctor public TestSuite(java.lang.String);
     method public void addTest(junit.framework.Test);
-    method public void addTestSuite(java.lang.Class);
+    method public void addTestSuite(java.lang.Class<? extends junit.framework.TestCase>);
     method public int countTestCases();
-    method public static junit.framework.Test createTest(java.lang.Class, java.lang.String);
+    method public static junit.framework.Test createTest(java.lang.Class<?>, java.lang.String);
     method public java.lang.String getName();
-    method public static java.lang.reflect.Constructor getTestConstructor(java.lang.Class) throws java.lang.NoSuchMethodException;
+    method public static java.lang.reflect.Constructor<?> getTestConstructor(java.lang.Class) throws java.lang.NoSuchMethodException;
     method public void run(junit.framework.TestResult);
     method public void runTest(junit.framework.Test, junit.framework.TestResult);
     method public void setName(java.lang.String);
     method public junit.framework.Test testAt(int);
     method public int testCount();
-    method public java.util.Enumeration tests();
+    method public java.util.Enumeration<junit.framework.Test> tests();
   }
 
 }
@@ -41946,7 +41943,7 @@
     method protected static java.util.Properties getPreferences();
     method public junit.framework.Test getTest(java.lang.String);
     method public static boolean inVAJava();
-    method protected java.lang.Class loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
+    method protected java.lang.Class<?> loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
     method protected java.lang.String processArguments(java.lang.String[]);
     method protected abstract void runFailed(java.lang.String);
     method public static void savePreferences() throws java.io.IOException;
diff --git a/api/current.txt b/api/current.txt
index c04f467..eacce52 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3686,6 +3686,7 @@
     field public static final int PRIORITY_MIN = -2; // 0xfffffffe
     field public static final int STREAM_DEFAULT = -1; // 0xffffffff
     field public int audioStreamType;
+    field public android.widget.RemoteViews bigContentView;
     field public android.app.PendingIntent contentIntent;
     field public android.widget.RemoteViews contentView;
     field public int defaults;
@@ -3708,6 +3709,18 @@
     field public long when;
   }
 
+  public static class Notification.BigPictureStyle {
+    ctor public Notification.BigPictureStyle(android.app.Notification.Builder);
+    method public android.app.Notification.BigPictureStyle bigPicture(android.graphics.Bitmap);
+    method public android.app.Notification build();
+  }
+
+  public static class Notification.BigTextStyle {
+    ctor public Notification.BigTextStyle(android.app.Notification.Builder);
+    method public android.app.Notification.BigTextStyle bigText(java.lang.CharSequence);
+    method public android.app.Notification build();
+  }
+
   public static class Notification.Builder {
     ctor public Notification.Builder(android.content.Context);
     method public android.app.Notification.Builder addAction(int, java.lang.CharSequence, android.app.PendingIntent);
@@ -3737,6 +3750,7 @@
     method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+    method public android.app.Notification.Builder setUsesChronometer(boolean);
     method public android.app.Notification.Builder setUsesIntruderAlert(boolean);
     method public android.app.Notification.Builder setVibrate(long[]);
     method public android.app.Notification.Builder setWhen(long);
@@ -8492,6 +8506,7 @@
     method public android.graphics.Paint.Align getTextAlign();
     method public void getTextBounds(java.lang.String, int, int, android.graphics.Rect);
     method public void getTextBounds(char[], int, int, android.graphics.Rect);
+    method public java.util.Locale getTextLocale();
     method public void getTextPath(char[], int, int, float, float, android.graphics.Path);
     method public void getTextPath(java.lang.String, int, int, float, float, android.graphics.Path);
     method public float getTextScaleX();
@@ -8541,6 +8556,7 @@
     method public void setStyle(android.graphics.Paint.Style);
     method public void setSubpixelText(boolean);
     method public void setTextAlign(android.graphics.Paint.Align);
+    method public void setTextLocale(java.util.Locale);
     method public void setTextScaleX(float);
     method public void setTextSize(float);
     method public void setTextSkewX(float);
@@ -9451,6 +9467,7 @@
     method public static android.hardware.Camera open();
     method public final void reconnect() throws java.io.IOException;
     method public final void release();
+    method public void setAutoFocusMoveCallback(android.hardware.Camera.AutoFocusMoveCallback);
     method public final void setDisplayOrientation(int);
     method public final void setErrorCallback(android.hardware.Camera.ErrorCallback);
     method public final void setFaceDetectionListener(android.hardware.Camera.FaceDetectionListener);
@@ -9486,6 +9503,10 @@
     method public abstract void onAutoFocus(boolean, android.hardware.Camera);
   }
 
+  public static abstract interface Camera.AutoFocusMoveCallback {
+    method public abstract void onAutoFocusMoving(boolean, android.hardware.Camera);
+  }
+
   public static class Camera.CameraInfo {
     ctor public Camera.CameraInfo();
     field public static final int CAMERA_FACING_BACK = 0; // 0x0
@@ -9820,10 +9841,20 @@
 package android.hardware.input {
 
   public final class InputManager {
+    method public android.view.InputDevice getInputDevice(int);
+    method public int[] getInputDeviceIds();
+    method public void registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler);
+    method public void unregisterInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener);
     field public static final java.lang.String ACTION_QUERY_KEYBOARD_LAYOUTS = "android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS";
     field public static final java.lang.String META_DATA_KEYBOARD_LAYOUTS = "android.hardware.input.metadata.KEYBOARD_LAYOUTS";
   }
 
+  public static abstract interface InputManager.InputDeviceListener {
+    method public abstract void onInputDeviceAdded(int);
+    method public abstract void onInputDeviceChanged(int);
+    method public abstract void onInputDeviceRemoved(int);
+  }
+
 }
 
 package android.hardware.usb {
@@ -16828,7 +16859,7 @@
     method public static android.net.Uri getLookupUri(android.content.ContentResolver, android.net.Uri);
     method public static android.net.Uri getLookupUri(long, java.lang.String);
     method public static android.net.Uri lookupContact(android.content.ContentResolver, android.net.Uri);
-    method public static void markAsContacted(android.content.ContentResolver, long);
+    method public static deprecated void markAsContacted(android.content.ContentResolver, long);
     method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri, boolean);
     method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri);
     field public static final android.net.Uri CONTENT_FILTER_URI;
@@ -16919,6 +16950,7 @@
 
   public static final class ContactsContract.DataUsageFeedback {
     ctor public ContactsContract.DataUsageFeedback();
+    field public static final android.net.Uri DELETE_USAGE_URI;
     field public static final android.net.Uri FEEDBACK_URI;
     field public static final java.lang.String USAGE_TYPE = "type";
     field public static final java.lang.String USAGE_TYPE_CALL = "call";
@@ -17999,15 +18031,24 @@
     method public static android.renderscript.Allocation createTyped(android.renderscript.RenderScript, android.renderscript.Type, int);
     method public static android.renderscript.Allocation createTyped(android.renderscript.RenderScript, android.renderscript.Type);
     method public void generateMipmaps();
+    method public int getBytesSize();
+    method public android.renderscript.Element getElement();
+    method public android.view.Surface getSurface();
     method public android.renderscript.Type getType();
+    method public int getUsage();
+    method public void ioReceive();
+    method public void ioSend();
     method public synchronized void resize(int);
     method public void setFromFieldPacker(int, android.renderscript.FieldPacker);
     method public void setFromFieldPacker(int, int, android.renderscript.FieldPacker);
+    method public void setSurface(android.view.Surface);
     method public void syncAll(int);
     field public static final int USAGE_GRAPHICS_CONSTANTS = 8; // 0x8
     field public static final int USAGE_GRAPHICS_RENDER_TARGET = 16; // 0x10
     field public static final int USAGE_GRAPHICS_TEXTURE = 2; // 0x2
     field public static final int USAGE_GRAPHICS_VERTEX = 4; // 0x4
+    field public static final int USAGE_IO_INPUT = 32; // 0x20
+    field public static final int USAGE_IO_OUTPUT = 64; // 0x40
     field public static final int USAGE_SCRIPT = 1; // 0x1
   }
 
@@ -18095,6 +18136,7 @@
     method public static android.renderscript.Element F64_2(android.renderscript.RenderScript);
     method public static android.renderscript.Element F64_3(android.renderscript.RenderScript);
     method public static android.renderscript.Element F64_4(android.renderscript.RenderScript);
+    method public static android.renderscript.Element FONT(android.renderscript.RenderScript);
     method public static android.renderscript.Element I16(android.renderscript.RenderScript);
     method public static android.renderscript.Element I16_2(android.renderscript.RenderScript);
     method public static android.renderscript.Element I16_3(android.renderscript.RenderScript);
@@ -18146,6 +18188,15 @@
     method public static android.renderscript.Element U8_4(android.renderscript.RenderScript);
     method public static android.renderscript.Element createPixel(android.renderscript.RenderScript, android.renderscript.Element.DataType, android.renderscript.Element.DataKind);
     method public static android.renderscript.Element createVector(android.renderscript.RenderScript, android.renderscript.Element.DataType, int);
+    method public int getBytesSize();
+    method public android.renderscript.Element.DataKind getDataKind();
+    method public android.renderscript.Element.DataType getDataType();
+    method public android.renderscript.Element getSubElement(int);
+    method public int getSubElementArraySize(int);
+    method public int getSubElementCount();
+    method public java.lang.String getSubElementName(int);
+    method public int getSubElementOffsetBytes(int);
+    method public int getVectorSize();
     method public boolean isCompatible(android.renderscript.Element);
     method public boolean isComplex();
   }
@@ -18178,8 +18229,10 @@
     enum_constant public static final android.renderscript.Element.DataType MATRIX_2X2;
     enum_constant public static final android.renderscript.Element.DataType MATRIX_3X3;
     enum_constant public static final android.renderscript.Element.DataType MATRIX_4X4;
+    enum_constant public static final android.renderscript.Element.DataType NONE;
     enum_constant public static final android.renderscript.Element.DataType RS_ALLOCATION;
     enum_constant public static final android.renderscript.Element.DataType RS_ELEMENT;
+    enum_constant public static final android.renderscript.Element.DataType RS_FONT;
     enum_constant public static final android.renderscript.Element.DataType RS_MESH;
     enum_constant public static final android.renderscript.Element.DataType RS_PROGRAM_FRAGMENT;
     enum_constant public static final android.renderscript.Element.DataType RS_PROGRAM_RASTER;
@@ -18492,12 +18545,18 @@
     method public void bindConstants(android.renderscript.Allocation, int);
     method public void bindSampler(android.renderscript.Sampler, int) throws java.lang.IllegalArgumentException;
     method public void bindTexture(android.renderscript.Allocation, int) throws java.lang.IllegalArgumentException;
+    method public android.renderscript.Type getConstant(int);
+    method public int getConstantCount();
+    method public int getTextureCount();
+    method public java.lang.String getTextureName(int);
+    method public android.renderscript.Program.TextureType getTextureType(int);
   }
 
   public static class Program.BaseProgramBuilder {
     ctor protected Program.BaseProgramBuilder(android.renderscript.RenderScript);
     method public android.renderscript.Program.BaseProgramBuilder addConstant(android.renderscript.Type) throws java.lang.IllegalStateException;
     method public android.renderscript.Program.BaseProgramBuilder addTexture(android.renderscript.Program.TextureType) throws java.lang.IllegalArgumentException;
+    method public android.renderscript.Program.BaseProgramBuilder addTexture(android.renderscript.Program.TextureType, java.lang.String) throws java.lang.IllegalArgumentException;
     method public int getCurrentConstantIndex();
     method public int getCurrentTextureIndex();
     method protected void initProgram(android.renderscript.Program);
@@ -18553,6 +18612,8 @@
     method public static android.renderscript.ProgramRaster CULL_BACK(android.renderscript.RenderScript);
     method public static android.renderscript.ProgramRaster CULL_FRONT(android.renderscript.RenderScript);
     method public static android.renderscript.ProgramRaster CULL_NONE(android.renderscript.RenderScript);
+    method public android.renderscript.ProgramRaster.CullMode getCullMode();
+    method public boolean isPointSpriteEnabled();
   }
 
   public static class ProgramRaster.Builder {
@@ -18575,6 +18636,15 @@
     method public static android.renderscript.ProgramStore BLEND_ALPHA_DEPTH_TEST(android.renderscript.RenderScript);
     method public static android.renderscript.ProgramStore BLEND_NONE_DEPTH_NONE(android.renderscript.RenderScript);
     method public static android.renderscript.ProgramStore BLEND_NONE_DEPTH_TEST(android.renderscript.RenderScript);
+    method public android.renderscript.ProgramStore.BlendDstFunc getBlendDstFunc();
+    method public android.renderscript.ProgramStore.BlendSrcFunc getBlendSrcFunc();
+    method public android.renderscript.ProgramStore.DepthFunc getDepthFunc();
+    method public boolean isColorMaskAlphaEnabled();
+    method public boolean isColorMaskBlueEnabled();
+    method public boolean isColorMaskGreenEnabled();
+    method public boolean isColorMaskRedEnabled();
+    method public boolean isDepthMaskEnabled();
+    method public boolean isDitherEnabled();
   }
 
   public static final class ProgramStore.BlendDstFunc extends java.lang.Enum {
@@ -18627,6 +18697,8 @@
   }
 
   public class ProgramVertex extends android.renderscript.Program {
+    method public android.renderscript.Element getInput(int);
+    method public int getInputCount();
   }
 
   public static class ProgramVertex.Builder extends android.renderscript.Program.BaseProgramBuilder {
@@ -18764,6 +18836,11 @@
     method public static android.renderscript.Sampler WRAP_LINEAR(android.renderscript.RenderScript);
     method public static android.renderscript.Sampler WRAP_LINEAR_MIP_LINEAR(android.renderscript.RenderScript);
     method public static android.renderscript.Sampler WRAP_NEAREST(android.renderscript.RenderScript);
+    method public float getAnisotropy();
+    method public android.renderscript.Sampler.Value getMagnification();
+    method public android.renderscript.Sampler.Value getMinification();
+    method public android.renderscript.Sampler.Value getWrapS();
+    method public android.renderscript.Sampler.Value getWrapT();
   }
 
   public static class Sampler.Builder {
@@ -19703,12 +19780,12 @@
     method public final void testApplicationTestCaseSetUpProperly() throws java.lang.Exception;
   }
 
-  public class AssertionFailedError extends java.lang.Error {
+  public deprecated class AssertionFailedError extends java.lang.Error {
     ctor public AssertionFailedError();
     ctor public AssertionFailedError(java.lang.String);
   }
 
-  public class ComparisonFailure extends android.test.AssertionFailedError {
+  public deprecated class ComparisonFailure extends android.test.AssertionFailedError {
     ctor public ComparisonFailure(java.lang.String, java.lang.String, java.lang.String);
   }
 
@@ -19750,6 +19827,7 @@
     ctor public InstrumentationTestSuite(android.app.Instrumentation);
     ctor public InstrumentationTestSuite(java.lang.String, android.app.Instrumentation);
     ctor public InstrumentationTestSuite(java.lang.Class, android.app.Instrumentation);
+    method public void addTestSuite(java.lang.Class);
   }
 
   public class IsolatedContext extends android.content.ContextWrapper {
@@ -25435,7 +25513,6 @@
   }
 
   public final class GeolocationPermissions {
-    ctor public GeolocationPermissions();
     method public void allow(java.lang.String);
     method public void clear(java.lang.String);
     method public void clearAll();
@@ -25725,7 +25802,6 @@
   }
 
   public final class WebStorage {
-    ctor public WebStorage();
     method public void deleteAllData();
     method public void deleteOrigin(java.lang.String);
     method public static android.webkit.WebStorage getInstance();
@@ -25801,7 +25877,6 @@
     method public java.lang.String getTitle();
     method public java.lang.String getUrl();
     method public deprecated int getVisibleTitleHeight();
-    method public deprecated android.view.View getZoomControls();
     method public void goBack();
     method public void goBackOrForward(int);
     method public void goForward();
@@ -42504,15 +42579,21 @@
     method public static void assertTrue(boolean);
     method public static void fail(java.lang.String);
     method public static void fail();
+    method public static void failNotEquals(java.lang.String, java.lang.Object, java.lang.Object);
+    method public static void failNotSame(java.lang.String, java.lang.Object, java.lang.Object);
+    method public static void failSame(java.lang.String);
+    method public static java.lang.String format(java.lang.String, java.lang.Object, java.lang.Object);
   }
 
-  public class AssertionFailedError extends java.lang.Error {
+  public class AssertionFailedError extends java.lang.AssertionError {
     ctor public AssertionFailedError();
     ctor public AssertionFailedError(java.lang.String);
   }
 
   public class ComparisonFailure extends junit.framework.AssertionFailedError {
     ctor public ComparisonFailure(java.lang.String, java.lang.String, java.lang.String);
+    method public java.lang.String getActual();
+    method public java.lang.String getExpected();
   }
 
   public abstract interface Protectable {
@@ -42564,9 +42645,9 @@
     method public synchronized void addListener(junit.framework.TestListener);
     method public void endTest(junit.framework.Test);
     method public synchronized int errorCount();
-    method public synchronized java.util.Enumeration errors();
+    method public synchronized java.util.Enumeration<junit.framework.TestFailure> errors();
     method public synchronized int failureCount();
-    method public synchronized java.util.Enumeration failures();
+    method public synchronized java.util.Enumeration<junit.framework.TestFailure> failures();
     method public synchronized void removeListener(junit.framework.TestListener);
     method protected void run(junit.framework.TestCase);
     method public synchronized int runCount();
@@ -42583,21 +42664,24 @@
 
   public class TestSuite implements junit.framework.Test {
     ctor public TestSuite();
-    ctor public TestSuite(java.lang.Class, java.lang.String);
-    ctor public TestSuite(java.lang.Class);
+    ctor public TestSuite(java.lang.Class<?>);
+    ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>, java.lang.String);
     ctor public TestSuite(java.lang.String);
+    ctor public TestSuite(java.lang.Class<?>...);
+    ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>[], java.lang.String);
     method public void addTest(junit.framework.Test);
-    method public void addTestSuite(java.lang.Class);
+    method public void addTestSuite(java.lang.Class<? extends junit.framework.TestCase>);
     method public int countTestCases();
-    method public static junit.framework.Test createTest(java.lang.Class, java.lang.String);
+    method public static junit.framework.Test createTest(java.lang.Class<?>, java.lang.String);
     method public java.lang.String getName();
-    method public static java.lang.reflect.Constructor getTestConstructor(java.lang.Class) throws java.lang.NoSuchMethodException;
+    method public static java.lang.reflect.Constructor<?> getTestConstructor(java.lang.Class<?>) throws java.lang.NoSuchMethodException;
     method public void run(junit.framework.TestResult);
     method public void runTest(junit.framework.Test, junit.framework.TestResult);
     method public void setName(java.lang.String);
     method public junit.framework.Test testAt(int);
     method public int testCount();
-    method public java.util.Enumeration tests();
+    method public java.util.Enumeration<junit.framework.Test> tests();
+    method public static junit.framework.Test warning(java.lang.String);
   }
 
 }
@@ -42614,13 +42698,13 @@
     method public java.lang.String extractClassName(java.lang.String);
     method public static java.lang.String getFilteredTrace(java.lang.Throwable);
     method public static java.lang.String getFilteredTrace(java.lang.String);
-    method public junit.runner.TestSuiteLoader getLoader();
+    method public deprecated junit.runner.TestSuiteLoader getLoader();
     method public static java.lang.String getPreference(java.lang.String);
     method public static int getPreference(java.lang.String, int);
     method protected static java.util.Properties getPreferences();
     method public junit.framework.Test getTest(java.lang.String);
-    method public static boolean inVAJava();
-    method protected java.lang.Class loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
+    method public static deprecated boolean inVAJava();
+    method protected java.lang.Class<?> loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
     method protected java.lang.String processArguments(java.lang.String[]);
     method protected abstract void runFailed(java.lang.String);
     method public static void savePreferences() throws java.io.IOException;
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index c5a4171..f9fa444 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -420,11 +420,7 @@
         if (duration < 0) {
             throw new IllegalArgumentException("duration must be a value of zero or greater");
         }
-        for (Node node : mNodes) {
-            // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
-            // insert "play-after" delays
-            node.animation.setDuration(duration);
-        }
+        // Just record the value for now - it will be used later when the AnimatorSet starts
         mDuration = duration;
         return this;
     }
@@ -456,6 +452,14 @@
         mTerminated = false;
         mStarted = true;
 
+        if (mDuration >= 0) {
+            // If the duration was set on this AnimatorSet, pass it along to all child animations
+            for (Node node : mNodes) {
+                // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
+                // insert "play-after" delays
+                node.animation.setDuration(mDuration);
+            }
+        }
         // First, sort the nodes (if necessary). This will ensure that sortedNodes
         // contains the animation nodes in the correct order.
         sortNodes();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 227900e..1c820dc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4656,7 +4656,7 @@
 
     /**
      * Print the Activity's state into the given stream.  This gets invoked if
-     * you run "adb shell dumpsys activity <activity_component_name>".
+     * you run "adb shell dumpsys activity &lt;activity_component_name&gt;".
      *
      * @param prefix Desired prefix to prepend at each line of output.
      * @param fd The raw file descriptor that the dump is being sent to.
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index a3fdf3e..7e1589f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1000,7 +1000,7 @@
             }
             return true;
         }
-        
+
         case GOING_TO_SLEEP_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             goingToSleep();
@@ -1015,6 +1015,13 @@
             return true;
         }
 
+        case SET_LOCK_SCREEN_SHOWN_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            setLockScreenShown(data.readInt() != 0);
+            reply.writeNoException();
+            return true;
+        }
+
         case SET_DEBUG_APP_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String pn = data.readString();
@@ -2912,6 +2919,17 @@
         data.recycle();
         reply.recycle();
     }
+    public void setLockScreenShown(boolean shown) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(shown ? 1 : 0);
+        mRemote.transact(SET_LOCK_SCREEN_SHOWN_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
     public void setDebugApp(
         String packageName, boolean waitForDebugger, boolean persistent)
         throws RemoteException
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index c493f0f..d3ba497 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -203,7 +203,7 @@
  * <li> {@link #onCreateView} creates and returns the view hierarchy associated
  * with the fragment.
  * <li> {@link #onActivityCreated} tells the fragment that its activity has
- * completed its own {@link Activity#onCreate Activity.onCreaate}.
+ * completed its own {@link Activity#onCreate Activity.onCreate()}.
  * <li> {@link #onStart} makes the fragment visible to the user (based on its
  * containing activity being started).
  * <li> {@link #onResume} makes the fragment interacting with the user (based on its
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index c71b186..3fc2280 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -205,7 +205,8 @@
     // Note: probably don't want to allow applications access to these.
     public void goingToSleep() throws RemoteException;
     public void wakingUp() throws RemoteException;
-    
+    public void setLockScreenShown(boolean shown) throws RemoteException;
+
     public void unhandledBack() throws RemoteException;
     public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException;
     public void setDebugApp(
@@ -588,4 +589,5 @@
     int GET_CURRENT_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+144;
     int TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+145;
     int NAVIGATE_UP_TO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+146;
+    int SET_LOCK_SCREEN_SHOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+147;
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 04c64a0..5cce25f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -196,10 +196,9 @@
     public RemoteViews intruderView;
 
     /**
-     * A larger version of {@link #contentView}, giving the Notification an
+     * A large-format version of {@link #contentView}, giving the Notification an
      * opportunity to show more detail. The system UI may choose to show this
      * instead of the normal content view at its discretion.
-     * @hide
      */
     public RemoteViews bigContentView;
 
@@ -987,8 +986,6 @@
         }
 
         /**
-         * @hide
-         * 
          * Show the {@link Notification#when} field as a countdown (or count-up) timer instead of a timestamp.  
          *
          * @see Notification#when
@@ -1609,23 +1606,21 @@
     }
 
     /**
-     * @hide because this API is still very rough
+     * Helper class for generating large-format notifications that include a large image attachment.
      * 
-     * This is a "rebuilder": It consumes a Builder object and modifies its output.
-     * 
-     * This represents the "big picture" style notification, with a large Bitmap atop the usual notification.
-     *
-     * Usage:
+     * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so:
      * <pre class="prettyprint">
      * Notification noti = new Notification.BigPictureStyle(
      *      new Notification.Builder()
-     *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
+     *         .setContentTitle(&quot;New photo from &quot; + sender.toString())
      *         .setContentText(subject)
-     *         .setSmallIcon(R.drawable.new_mail)
+     *         .setSmallIcon(R.drawable.new_post)
      *         .setLargeIcon(aBitmap))
      *      .bigPicture(aBigBitmap)
      *      .build();
      * </pre>
+     * 
+     * @see Notification#bigContentView
      */
     public static class BigPictureStyle {
         private Builder mBuilder;
@@ -1656,13 +1651,9 @@
     }
 
     /**
-     * @hide because this API is still very rough
+     * Helper class for generating large-format notifications that include a lot of text.
      * 
-     * This is a "rebuilder": It consumes a Builder object and modifies its output.
-     * 
-     * This represents the "big text" style notification, with more area for the main content text to be read in its entirety.
-     *
-     * Usage:
+     * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so:
      * <pre class="prettyprint">
      * Notification noti = new Notification.BigPictureStyle(
      *      new Notification.Builder()
@@ -1673,6 +1664,8 @@
      *      .bigText(aVeryLongString)
      *      .build();
      * </pre>
+     * 
+     * @see Notification#bigContentView
      */
     public static class BigTextStyle {
         private Builder mBuilder;
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 207ae76..cb43d4c 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -666,8 +666,8 @@
     
     /**
      * Print the Service's state into the given stream.  This gets invoked if
-     * you run "adb shell dumpsys activity service <yourservicename>".
-     * This is distinct from "dumpsys <servicename>", which only works for
+     * you run "adb shell dumpsys activity service &lt;yourservicename&gt;".
+     * This is distinct from "dumpsys &lt;servicename&gt;", which only works for
      * named system services and which invokes the {@link IBinder#dump} method
      * on the {@link IBinder} interface registered with ServiceManager.
      *
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 05ef194..1206056 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -1127,7 +1127,7 @@
 
     /**
      * Print the Provider's state into the given stream.  This gets invoked if
-     * you run "adb shell dumpsys activity provider <provider_component_name>".
+     * you run "adb shell dumpsys activity provider &lt;provider_component_name&gt;".
      *
      * @param prefix Desired prefix to prepend at each line of output.
      * @param fd The raw file descriptor that the dump is being sent to.
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 6219de7..34c40a0 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -205,6 +205,9 @@
 
     private final PowerManager mPowerManager;
 
+    // Use this as a random offset to seed all periodic syncs
+    private int mSyncRandomOffsetMillis;
+
     private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
     private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
 
@@ -438,6 +441,9 @@
             // do this synchronously to ensure we have the accounts before this call returns
             onAccountsUpdated(null);
         }
+
+        // Pick a random second in a day to seed all periodic syncs
+        mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000;
     }
 
     /**
@@ -666,6 +672,7 @@
 
     private void sendCheckAlarmsMessage() {
         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
+        mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS);
         mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
     }
 
@@ -714,6 +721,8 @@
     }
 
     private void increaseBackoffSetting(SyncOperation op) {
+        // TODO: Use this function to align it to an already scheduled sync
+        //       operation in the specified window
         final long now = SystemClock.elapsedRealtime();
 
         final Pair<Long, Long> previousSettings =
@@ -1060,6 +1069,8 @@
         final long now = SystemClock.elapsedRealtime();
         pw.print("now: "); pw.print(now);
         pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
+        pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000));
+        pw.println(" (HH:MM:SS)");
         pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
                 pw.println(" (HH:MM:SS)");
         pw.print("time spent syncing: ");
@@ -1771,6 +1782,9 @@
             AccountAndUser[] accounts = mAccounts;
 
             final long nowAbsolute = System.currentTimeMillis();
+            final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
+                                               ? (nowAbsolute  - mSyncRandomOffsetMillis) : 0;
+
             ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
             for (SyncStorageEngine.AuthorityInfo info : infos) {
                 // skip the sync if the account of this operation no longer exists
@@ -1792,16 +1806,32 @@
                 SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info);
                 for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) {
                     final Bundle extras = info.periodicSyncs.get(i).first;
-                    final Long periodInSeconds = info.periodicSyncs.get(i).second;
+                    final Long periodInMillis = info.periodicSyncs.get(i).second * 1000;
                     // find when this periodic sync was last scheduled to run
                     final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
-                    // compute when this periodic sync should next run - this can be in the future
-                    // for example if the user changed the time, synced and changed back.
-                    final long nextPollTimeAbsolute = lastPollTimeAbsolute > nowAbsolute
-                            ? nowAbsolute
-                            : lastPollTimeAbsolute + periodInSeconds * 1000;
-                    // if it is ready to run then schedule it and mark it as having been scheduled
-                    if (nextPollTimeAbsolute <= nowAbsolute) {
+
+                    long remainingMillis
+                            = periodInMillis - (shiftedNowAbsolute % periodInMillis);
+
+                    /*
+                     * Sync scheduling strategy:
+                     *    Set the next periodic sync based on a random offset (in seconds).
+                     *
+                     *    Also sync right now if any of the following cases hold
+                     *    and mark it as having been scheduled
+                     *
+                     * Case 1:  This sync is ready to run now.
+                     * Case 2:  If the lastPollTimeAbsolute is in the future,
+                     *          sync now and reinitialize. This can happen for
+                     *          example if the user changed the time, synced and
+                     *          changed back.
+                     * Case 3:  If we failed to sync at the last scheduled time
+                     */
+                    if (remainingMillis == periodInMillis  // Case 1
+                            || lastPollTimeAbsolute > nowAbsolute // Case 2
+                            || (nowAbsolute - lastPollTimeAbsolute
+                                    >= periodInMillis)) { // Case 3
+                        // Sync now
                         final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
                                 info.account, info.userId, info.authority);
                         final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
@@ -1819,12 +1849,13 @@
                                                 info.account, info.userId, info.authority),
                                         syncAdapterInfo.type.allowParallelSyncs()));
                         status.setPeriodicSyncTime(i, nowAbsolute);
-                    } else {
-                        // it isn't ready to run, remember this time if it is earlier than
-                        // earliestFuturePollTime
-                        if (nextPollTimeAbsolute < earliestFuturePollTime) {
-                            earliestFuturePollTime = nextPollTimeAbsolute;
-                        }
+                    }
+                    // Compute when this periodic sync should next run
+                    final long nextPollTimeAbsolute = nowAbsolute + remainingMillis;
+
+                    // remember this time if it is earlier than earliestFuturePollTime
+                    if (nextPollTimeAbsolute < earliestFuturePollTime) {
+                        earliestFuturePollTime = nextPollTimeAbsolute;
                     }
                 }
             }
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index d3baf70..d821918 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -37,6 +37,7 @@
 import android.os.Parcel;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -49,6 +50,7 @@
 import java.util.Calendar;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Random;
 import java.util.TimeZone;
 import java.util.List;
 
@@ -65,6 +67,7 @@
 
     private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
     private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
+    private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds";
     private static final String XML_ATTR_ENABLED = "enabled";
     private static final String XML_ATTR_USER = "user";
     private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
@@ -277,6 +280,8 @@
 
     private static volatile SyncStorageEngine sSyncStorageEngine = null;
 
+    private int mSyncRandomOffset;
+
     /**
      * This file contains the core engine state: all accounts and the
      * settings for them.  It must never be lost, and should be changed
@@ -375,6 +380,10 @@
         }
     }
 
+    public int getSyncRandomOffset() {
+        return mSyncRandomOffset;
+    }
+
     public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
         synchronized (mAuthorities) {
             mChangeListeners.register(callback, mask);
@@ -1465,6 +1474,16 @@
                 } catch (NumberFormatException e) {
                     // don't care
                 }
+                String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
+                try {
+                    mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
+                } catch (NumberFormatException e) {
+                    mSyncRandomOffset = 0;
+                }
+                if (mSyncRandomOffset == 0) {
+                    Random random = new Random(System.currentTimeMillis());
+                    mSyncRandomOffset = random.nextInt(86400);
+                }
                 mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
                 eventType = parser.next();
                 AuthorityInfo authority = null;
@@ -1705,6 +1724,7 @@
             out.startTag(null, "accounts");
             out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
             out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
+            out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
 
             // Write the Sync Automatically flags for each user
             final int M = mMasterSyncAutomatically.size();
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 56fd5f8..9b8454a 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -373,6 +373,6 @@
     List<UserInfo> getUsers();
     UserInfo getUser(int userId);
 
-    void setPermissionEnforcement(String permission, int enforcement);
-    int getPermissionEnforcement(String permission);
+    void setPermissionEnforced(String permission, boolean enforced);
+    boolean isPermissionEnforced(String permission);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b06b4a5..675f77e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -28,6 +28,7 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.util.AndroidException;
 import android.util.DisplayMetrics;
@@ -1091,9 +1092,7 @@
             = "android.content.pm.extra.VERIFICATION_INSTALL_FLAGS";
 
     /** {@hide} */
-    public static final int ENFORCEMENT_DEFAULT = 0;
-    /** {@hide} */
-    public static final int ENFORCEMENT_YES = 1;
+    public static final boolean DEFAULT_ENFORCE_READ_EXTERNAL_STORAGE = !"user".equals(Build.TYPE);
 
     /**
      * Retrieve overall information about an application package that is
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 2af58be..c682852 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -32,7 +32,6 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.TypedValue;
 import android.util.LongSparseArray;
 
@@ -86,8 +85,8 @@
     // single-threaded, and after that these are immutable.
     private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables
             = new LongSparseArray<Drawable.ConstantState>();
-    private static final SparseArray<ColorStateList> mPreloadedColorStateLists
-            = new SparseArray<ColorStateList>();
+    private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists
+            = new LongSparseArray<ColorStateList>();
     private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
             = new LongSparseArray<Drawable.ConstantState>();
     private static boolean mPreloaded;
@@ -98,8 +97,8 @@
     // These are protected by the mTmpValue lock.
     private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
             = new LongSparseArray<WeakReference<Drawable.ConstantState> >();
-    private final SparseArray<WeakReference<ColorStateList> > mColorStateListCache
-            = new SparseArray<WeakReference<ColorStateList> >();
+    private final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache
+            = new LongSparseArray<WeakReference<ColorStateList> >();
     private final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache
             = new LongSparseArray<WeakReference<Drawable.ConstantState> >();
     private boolean mPreloading;
@@ -118,22 +117,6 @@
     
     private CompatibilityInfo mCompatibilityInfo;
 
-    private static final LongSparseArray<Object> EMPTY_ARRAY = new LongSparseArray<Object>(0) {
-        @Override
-        public void put(long k, Object o) {
-            throw new UnsupportedOperationException();
-        }
-        @Override
-        public void append(long k, Object o) {
-            throw new UnsupportedOperationException();
-        }
-    };
-
-    @SuppressWarnings("unchecked")
-    private static <T> LongSparseArray<T> emptySparseArray() {
-        return (LongSparseArray<T>) EMPTY_ARRAY;
-    }
-
     /** @hide */
     public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
         return selectSystemTheme(curTheme, targetSdkVersion,
@@ -180,9 +163,8 @@
      * @param config Desired device configuration to consider when 
      *               selecting/computing resource values (optional).
      */
-    public Resources(AssetManager assets, DisplayMetrics metrics,
-            Configuration config) {
-        this(assets, metrics, config, (CompatibilityInfo) null);
+    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
+        this(assets, metrics, config, null);
     }
 
     /**
@@ -1883,7 +1865,8 @@
             return dr;
         }
 
-        Drawable.ConstantState cs = isColorDrawable ? sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key);
+        Drawable.ConstantState cs = isColorDrawable ?
+                sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key);
         if (cs != null) {
             dr = cs.newDrawable(this);
         } else {
@@ -2005,21 +1988,21 @@
             }
         }
 
-        final int key = (value.assetCookie << 24) | value.data;
+        final long key = (((long) value.assetCookie) << 32) | value.data;
 
         ColorStateList csl;
 
         if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
                 value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
 
-            csl = mPreloadedColorStateLists.get(key);
+            csl = sPreloadedColorStateLists.get(key);
             if (csl != null) {
                 return csl;
             }
 
             csl = ColorStateList.valueOf(value.data);
             if (mPreloading) {
-                mPreloadedColorStateLists.put(key, csl);
+                sPreloadedColorStateLists.put(key, csl);
             }
 
             return csl;
@@ -2030,7 +2013,7 @@
             return csl;
         }
 
-        csl = mPreloadedColorStateLists.get(key);
+        csl = sPreloadedColorStateLists.get(key);
         if (csl != null) {
             return csl;
         }
@@ -2063,14 +2046,13 @@
 
         if (csl != null) {
             if (mPreloading) {
-                mPreloadedColorStateLists.put(key, csl);
+                sPreloadedColorStateLists.put(key, csl);
             } else {
                 synchronized (mTmpValue) {
                     //Log.i(TAG, "Saving cached color state list @ #" +
                     //        Integer.toHexString(key.intValue())
                     //        + " in " + this + ": " + csl);
-                    mColorStateListCache.put(
-                        key, new WeakReference<ColorStateList>(csl));
+                    mColorStateListCache.put(key, new WeakReference<ColorStateList>(csl));
                 }
             }
         }
@@ -2078,7 +2060,7 @@
         return csl;
     }
 
-    private ColorStateList getCachedColorStateList(int key) {
+    private ColorStateList getCachedColorStateList(long key) {
         synchronized (mTmpValue) {
             WeakReference<ColorStateList> wr = mColorStateListCache.get(key);
             if (wr != null) {   // we have the key
@@ -2088,8 +2070,7 @@
                     //        Integer.toHexString(((Integer)key).intValue())
                     //        + " in " + this + ": " + entry);
                     return entry;
-                }
-                else {  // our entry has been purged
+                } else {  // our entry has been purged
                     mColorStateListCache.delete(key);
                 }
             }
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 83b6986..640b47b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -950,11 +950,10 @@
     /**
      * Callback interface used to notify on auto focus start and stop.
      *
-     * <p>This is useful for continuous autofocus -- {@link Parameters#FOCUS_MODE_CONTINUOUS_VIDEO}
-     * and {@link Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can
-     * show autofocus animation.</p>
-     *
-     * @hide
+     * <p>This is only supported in continuous autofocus modes -- {@link
+     * Parameters#FOCUS_MODE_CONTINUOUS_VIDEO} and {@link
+     * Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can show
+     * autofocus animation based on this.</p>
      */
     public interface AutoFocusMoveCallback
     {
@@ -962,7 +961,7 @@
          * Called when the camera auto focus starts or stops.
          *
          * @param start true if focus starts to move, false if focus stops to move
-         * @param camera  the Camera service object
+         * @param camera the Camera service object
          */
         void onAutoFocusMoving(boolean start, Camera camera);
     }
@@ -971,7 +970,6 @@
      * Sets camera auto-focus move callback.
      *
      * @param cb the callback to run
-     * @hide
      */
     public void setAutoFocusMoveCallback(AutoFocusMoveCallback cb) {
         mAutoFocusMoveCallback = cb;
diff --git a/core/java/android/hardware/input/IInputDevicesChangedListener.aidl b/core/java/android/hardware/input/IInputDevicesChangedListener.aidl
new file mode 100644
index 0000000..5d8ada1
--- /dev/null
+++ b/core/java/android/hardware/input/IInputDevicesChangedListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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 android.hardware.input;
+
+/** @hide */
+interface IInputDevicesChangedListener {
+    /* Called when input devices changed, such as a device being added,
+     * removed or changing configuration.
+     *
+     * The parameter is an array of pairs (deviceId, generation) indicating the current
+     * device id and generation of all input devices.  The client can determine what
+     * has happened by comparing the result to its prior observations.
+     */
+    oneway void onInputDevicesChanged(in int[] deviceIdAndGeneration);
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 47e0d1e..ca8321f 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -17,6 +17,7 @@
 package android.hardware.input;
 
 import android.hardware.input.KeyboardLayout;
+import android.hardware.input.IInputDevicesChangedListener;
 import android.view.InputDevice;
 import android.view.InputEvent;
 
@@ -42,4 +43,7 @@
     String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
     void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
             String keyboardLayoutDescriptor);
+
+    // Registers an input devices changed listener.
+    void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 3b3c237..35c49a1 100755
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,7 +19,10 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.Context;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
@@ -29,6 +32,8 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 
+import java.util.ArrayList;
+
 /**
  * Provides information about input devices and available key layouts.
  * <p>
@@ -40,11 +45,22 @@
  */
 public final class InputManager {
     private static final String TAG = "InputManager";
+    private static final boolean DEBUG = false;
+
+    private static final int MSG_DEVICE_ADDED = 1;
+    private static final int MSG_DEVICE_REMOVED = 2;
+    private static final int MSG_DEVICE_CHANGED = 3;
 
     private static InputManager sInstance;
 
     private final IInputManager mIm;
-    private final SparseArray<InputDevice> mInputDevices = new SparseArray<InputDevice>();
+
+    // Guarded by mInputDevicesLock
+    private final Object mInputDevicesLock = new Object();
+    private SparseArray<InputDevice> mInputDevices;
+    private InputDevicesChangedListener mInputDevicesChangedListener;
+    private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners =
+            new ArrayList<InputDeviceListenerDelegate>();
 
     /**
      * Broadcast Action: Query available keyboard layouts.
@@ -169,6 +185,103 @@
     }
 
     /**
+     * Gets information about the input device with the specified id.
+     * @param id The device id.
+     * @return The input device or null if not found.
+     */
+    public InputDevice getInputDevice(int id) {
+        synchronized (mInputDevicesLock) {
+            populateInputDevicesLocked();
+
+            int index = mInputDevices.indexOfKey(id);
+            if (index < 0) {
+                return null;
+            }
+
+            InputDevice inputDevice = mInputDevices.valueAt(index);
+            if (inputDevice == null) {
+                try {
+                    inputDevice = mIm.getInputDevice(id);
+                } catch (RemoteException ex) {
+                    throw new RuntimeException("Could not get input device information.", ex);
+                }
+            }
+            mInputDevices.setValueAt(index, inputDevice);
+            return inputDevice;
+        }
+    }
+
+    /**
+     * Gets the ids of all input devices in the system.
+     * @return The input device ids.
+     */
+    public int[] getInputDeviceIds() {
+        synchronized (mInputDevicesLock) {
+            populateInputDevicesLocked();
+
+            final int count = mInputDevices.size();
+            final int[] ids = new int[count];
+            for (int i = 0; i < count; i++) {
+                ids[i] = mInputDevices.keyAt(i);
+            }
+            return ids;
+        }
+    }
+
+    /**
+     * Registers an input device listener to receive notifications about when
+     * input devices are added, removed or changed.
+     *
+     * @param listener The listener to register.
+     * @param handler The handler on which the listener should be invoked, or null
+     * if the listener should be invoked on the calling thread's looper.
+     *
+     * @see #unregisterInputDeviceListener
+     */
+    public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDevicesLock) {
+            int index = findInputDeviceListenerLocked(listener);
+            if (index < 0) {
+                mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
+            }
+        }
+    }
+
+    /**
+     * Unregisters an input device listener.
+     *
+     * @param listener The listener to unregister.
+     *
+     * @see #registerInputDeviceListener
+     */
+    public void unregisterInputDeviceListener(InputDeviceListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDevicesLock) {
+            int index = findInputDeviceListenerLocked(listener);
+            if (index >= 0) {
+                mInputDeviceListeners.remove(index);
+            }
+        }
+    }
+
+    private int findInputDeviceListenerLocked(InputDeviceListener listener) {
+        final int numListeners = mInputDeviceListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            if (mInputDeviceListeners.get(i).mListener == listener) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
      * Gets information about all supported keyboard layouts.
      * <p>
      * The input manager consults the built-in keyboard layouts as well
@@ -327,50 +440,6 @@
     }
 
     /**
-     * Gets information about the input device with the specified id.
-     * @param id The device id.
-     * @return The input device or null if not found.
-     *
-     * @hide
-     */
-    public InputDevice getInputDevice(int id) {
-        synchronized (mInputDevices) {
-            InputDevice inputDevice = mInputDevices.get(id);
-            if (inputDevice != null) {
-                return inputDevice;
-            }
-        }
-        final InputDevice newInputDevice;
-        try {
-            newInputDevice = mIm.getInputDevice(id);
-        } catch (RemoteException ex) {
-            throw new RuntimeException("Could not get input device information.", ex);
-        }
-        synchronized (mInputDevices) {
-            InputDevice inputDevice = mInputDevices.get(id);
-            if (inputDevice != null) {
-                return inputDevice;
-            }
-            mInputDevices.put(id, newInputDevice);
-            return newInputDevice;
-        }
-    }
-
-    /**
-     * Gets the ids of all input devices in the system.
-     * @return The input device ids.
-     *
-     * @hide
-     */
-    public int[] getInputDeviceIds() {
-        try {
-            return mIm.getInputDeviceIds();
-        } catch (RemoteException ex) {
-            throw new RuntimeException("Could not get input device ids.", ex);
-        }
-    }
-
-    /**
      * Queries the framework about whether any physical keys exist on the
      * any keyboard attached to the device that are capable of producing the given
      * array of key codes.
@@ -429,4 +498,151 @@
             return false;
         }
     }
+
+    private void populateInputDevicesLocked() {
+        if (mInputDevicesChangedListener == null) {
+            final InputDevicesChangedListener listener = new InputDevicesChangedListener();
+            try {
+                mIm.registerInputDevicesChangedListener(listener);
+            } catch (RemoteException ex) {
+                throw new RuntimeException(
+                        "Could not get register input device changed listener", ex);
+            }
+            mInputDevicesChangedListener = listener;
+        }
+
+        if (mInputDevices == null) {
+            final int[] ids;
+            try {
+                ids = mIm.getInputDeviceIds();
+            } catch (RemoteException ex) {
+                throw new RuntimeException("Could not get input device ids.", ex);
+            }
+
+            mInputDevices = new SparseArray<InputDevice>();
+            for (int i = 0; i < ids.length; i++) {
+                mInputDevices.put(ids[i], null);
+            }
+        }
+    }
+
+    private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
+        if (DEBUG) {
+            Log.d(TAG, "Received input devices changed.");
+        }
+
+        synchronized (mInputDevicesLock) {
+            for (int i = mInputDevices.size(); --i > 0; ) {
+                final int deviceId = mInputDevices.keyAt(i);
+                if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Device removed: " + deviceId);
+                    }
+                    mInputDevices.removeAt(i);
+                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId);
+                }
+            }
+
+            for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+                final int deviceId = deviceIdAndGeneration[i];
+                int index = mInputDevices.indexOfKey(deviceId);
+                if (index >= 0) {
+                    final InputDevice device = mInputDevices.valueAt(index);
+                    if (device != null) {
+                        final int generation = deviceIdAndGeneration[i + 1];
+                        if (device.getGeneration() != generation) {
+                            if (DEBUG) {
+                                Log.d(TAG, "Device changed: " + deviceId);
+                            }
+                            mInputDevices.setValueAt(index, null);
+                            sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId);
+                        }
+                    }
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, "Device added: " + deviceId);
+                    }
+                    mInputDevices.put(deviceId, null);
+                    sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId);
+                }
+            }
+        }
+    }
+
+    private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
+        final int numListeners = mInputDeviceListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
+            listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
+        }
+    }
+
+    private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
+        for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+            if (deviceIdAndGeneration[i] == deviceId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Listens for changes in input devices.
+     */
+    public interface InputDeviceListener {
+        /**
+         * Called whenever an input device has been added to the system.
+         * Use {@link InputManager#getInputDevice} to get more information about the device.
+         *
+         * @param deviceId The id of the input device that was added.
+         */
+        void onInputDeviceAdded(int deviceId);
+
+        /**
+         * Called whenever an input device has been removed from the system.
+         *
+         * @param deviceId The id of the input device that was removed.
+         */
+        void onInputDeviceRemoved(int deviceId);
+
+        /**
+         * Called whenever the properties of an input device have changed since they
+         * were last queried.  Use {@link InputManager#getInputDevice} to get
+         * a fresh {@link InputDevice} object with the new properties.
+         *
+         * @param deviceId The id of the input device that changed.
+         */
+        void onInputDeviceChanged(int deviceId);
+    }
+
+    private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
+        @Override
+        public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
+            InputManager.this.onInputDevicesChanged(deviceIdAndGeneration);
+        }
+    }
+
+    private static final class InputDeviceListenerDelegate extends Handler {
+        public final InputDeviceListener mListener;
+
+        public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
+            super(handler != null ? handler.getLooper() : Looper.myLooper());
+            mListener = listener;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DEVICE_ADDED:
+                    mListener.onInputDeviceAdded(msg.arg1);
+                    break;
+                case MSG_DEVICE_REMOVED:
+                    mListener.onInputDeviceRemoved(msg.arg1);
+                    break;
+                case MSG_DEVICE_CHANGED:
+                    mListener.onInputDeviceChanged(msg.arg1);
+                    break;
+            }
+        }
+    }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index ba7dc4a3..332f40a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1712,8 +1712,8 @@
     
     /**
      * Override this to intercept key down events before they are processed by the
-     * application.  If you return true, the application will not itself
-     * process the event.  If you return true, the normal application processing
+     * application.  If you return true, the application will not 
+     * process the event itself.  If you return false, the normal application processing
      * will occur as if the IME had not seen the event at all.
      * 
      * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
diff --git a/core/java/android/net/nsd/DnsSdServiceInfo.java b/core/java/android/net/nsd/DnsSdServiceInfo.java
index 47d6ec6..33c3eb9 100644
--- a/core/java/android/net/nsd/DnsSdServiceInfo.java
+++ b/core/java/android/net/nsd/DnsSdServiceInfo.java
@@ -19,6 +19,8 @@
 import android.os.Parcelable;
 import android.os.Parcel;
 
+import java.net.InetAddress;
+
 /**
  * Defines a service based on DNS service discovery
  * {@hide}
@@ -27,20 +29,20 @@
 
     private String mServiceName;
 
-    private String mRegistrationType;
+    private String mServiceType;
 
     private DnsSdTxtRecord mTxtRecord;
 
-    private String mHostname;
+    private InetAddress mHost;
 
     private int mPort;
 
-    DnsSdServiceInfo() {
+    public DnsSdServiceInfo() {
     }
 
-    DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
+    public DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
         mServiceName = sn;
-        mRegistrationType = rt;
+        mServiceType = rt;
         mTxtRecord = tr;
     }
 
@@ -59,13 +61,13 @@
     @Override
     /** @hide */
     public String getServiceType() {
-        return mRegistrationType;
+        return mServiceType;
     }
 
     @Override
     /** @hide */
     public void setServiceType(String s) {
-        mRegistrationType = s;
+        mServiceType = s;
     }
 
     public DnsSdTxtRecord getTxtRecord() {
@@ -76,12 +78,12 @@
         mTxtRecord = new DnsSdTxtRecord(t);
     }
 
-    public String getHostName() {
-        return mHostname;
+    public InetAddress getHost() {
+        return mHost;
     }
 
-    public void setHostName(String s) {
-        mHostname = s;
+    public void setHost(InetAddress s) {
+        mHost = s;
     }
 
     public int getPort() {
@@ -96,7 +98,9 @@
         StringBuffer sb = new StringBuffer();
 
         sb.append("name: ").append(mServiceName).
-            append("type: ").append(mRegistrationType).
+            append("type: ").append(mServiceType).
+            append("host: ").append(mHost).
+            append("port: ").append(mPort).
             append("txtRecord: ").append(mTxtRecord);
         return sb.toString();
     }
@@ -109,9 +113,14 @@
     /** Implement the Parcelable interface */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mServiceName);
-        dest.writeString(mRegistrationType);
+        dest.writeString(mServiceType);
         dest.writeParcelable(mTxtRecord, flags);
-        dest.writeString(mHostname);
+        if (mHost != null) {
+            dest.writeByte((byte)1);
+            dest.writeByteArray(mHost.getAddress());
+        } else {
+            dest.writeByte((byte)0);
+        }
         dest.writeInt(mPort);
     }
 
@@ -121,9 +130,15 @@
             public DnsSdServiceInfo createFromParcel(Parcel in) {
                 DnsSdServiceInfo info = new DnsSdServiceInfo();
                 info.mServiceName = in.readString();
-                info.mRegistrationType = in.readString();
+                info.mServiceType = in.readString();
                 info.mTxtRecord = in.readParcelable(null);
-                info.mHostname = in.readString();
+
+                if (in.readByte() == 1) {
+                    try {
+                        info.mHost = InetAddress.getByAddress(in.createByteArray());
+                    } catch (java.net.UnknownHostException e) {}
+                }
+
                 info.mPort = in.readInt();
                 return info;
             }
diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java
index 6d4342c..952e02f 100644
--- a/core/java/android/net/nsd/DnsSdTxtRecord.java
+++ b/core/java/android/net/nsd/DnsSdTxtRecord.java
@@ -24,6 +24,8 @@
 import android.os.Parcelable;
 import android.os.Parcel;
 
+import java.util.Arrays;
+
 /**
  * This class handles TXT record data for DNS based service discovery as specified at
  * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
@@ -160,7 +162,7 @@
 
     /* Gets the raw data in bytes */
     public byte[] getRawData() {
-        return mData;
+        return (byte[]) mData.clone();
     }
 
     private void insert(byte[] keyBytes, byte[] value, int index) {
@@ -279,6 +281,24 @@
         return result != null ? result : "";
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (!(o instanceof DnsSdTxtRecord)) {
+            return false;
+        }
+
+        DnsSdTxtRecord record = (DnsSdTxtRecord)o;
+        return  Arrays.equals(record.mData, mData);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(mData);
+    }
+
     /** Implement the Parcelable interface */
     public int describeContents() {
         return 0;
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index a109a98..505f11b 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -93,6 +93,15 @@
     /** @hide */
     public static final int RESOLVE_SERVICE_SUCCEEDED               = BASE + 17;
 
+    /** @hide */
+    public static final int STOP_RESOLVE                            = BASE + 18;
+    /** @hide */
+    public static final int STOP_RESOLVE_FAILED                     = BASE + 19;
+    /** @hide */
+    public static final int STOP_RESOLVE_SUCCEEDED                  = BASE + 20;
+
+
+
     /**
      * Create a new Nsd instance. Applications use
      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -117,10 +126,23 @@
 
     /**
      * Indicates that the operation failed because the framework is busy and
-     * unable to service the request
+     * unable to service the request.
      */
     public static final int BUSY                = 2;
 
+    /**
+     * Indicates that the operation failed because it is already active.
+     */
+    public static final int ALREADY_ACTIVE      = 3;
+
+    /**
+     * Indicates that the operation failed because maximum limit on
+     * service registrations has reached.
+     */
+    public static final int MAX_REGS_REACHED    = 4;
+
+
+
     /** Interface for callback invocation when framework channel is connected or lost */
     public interface ChannelListener {
         public void onChannelConnected(Channel c);
@@ -188,6 +210,7 @@
         private DnsSdRegisterListener mDnsSdRegisterListener;
         private DnsSdUpdateRegistrationListener mDnsSdUpdateListener;
         private DnsSdResolveListener mDnsSdResolveListener;
+        private ActionListener mDnsSdStopResolveListener;
 
         AsyncChannel mAsyncChannel;
         ServiceHandler mHandler;
@@ -278,6 +301,16 @@
                                     (DnsSdServiceInfo) message.obj);
                         }
                         break;
+                    case STOP_RESOLVE_FAILED:
+                        if (mDnsSdStopResolveListener!= null) {
+                            mDnsSdStopResolveListener.onFailure(message.arg1);
+                        }
+                        break;
+                    case STOP_RESOLVE_SUCCEEDED:
+                        if (mDnsSdStopResolveListener != null) {
+                            mDnsSdStopResolveListener.onSuccess();
+                        }
+                        break;
                     default:
                         Log.d(TAG, "Ignored " + message);
                         break;
@@ -345,6 +378,14 @@
         c.mDnsSdResolveListener = b;
     }
 
+    /**
+     * Set the listener for stopping service resolution. Can be null.
+     */
+    public void setStopResolveListener(Channel c, ActionListener b) {
+        if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+        c.mDnsSdStopResolveListener = b;
+    }
+
     public void registerService(Channel c, DnsSdServiceInfo serviceInfo) {
         if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
         if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo");
@@ -378,6 +419,13 @@
         c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo);
     }
 
+    public void stopServiceResolve(Channel c) {
+        if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+        if (c.mDnsSdResolveListener == null) throw new
+                IllegalStateException("Resolve listener needs to be set first");
+        c.mAsyncChannel.sendMessage(STOP_RESOLVE);
+    }
+
     /**
      * Get a reference to NetworkService handler. This is used to establish
      * an AsyncChannel communication with the service
diff --git a/core/java/android/provider/BrowserContract.java b/core/java/android/provider/BrowserContract.java
index d678205..118b5eb 100644
--- a/core/java/android/provider/BrowserContract.java
+++ b/core/java/android/provider/BrowserContract.java
@@ -30,6 +30,15 @@
 import android.util.Pair;
 
 /**
+ * <p>
+ * The contract between the browser provider and applications. Contains the definition
+ * for the supported URIS and columns.
+ * </p>
+ * <h3>Overview</h3>
+ * <p>
+ * BrowserContract defines an database of browser-related information which are bookmarks,
+ * history, images and the mapping between the image and URL.
+ * </p>
  * @hide
  */
 public class BrowserContract {
@@ -45,12 +54,14 @@
      * the dirty flag is not automatically set and the "syncToNetwork" parameter
      * is set to false when calling
      * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+     * @hide
      */
     public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
 
     /**
      * A parameter for use when querying any table that allows specifying a limit on the number
      * of rows returned.
+     * @hide
      */
     public static final String PARAM_LIMIT = "limit";
 
@@ -58,6 +69,8 @@
      * Generic columns for use by sync adapters. The specific functions of
      * these columns are private to the sync adapter. Other clients of the API
      * should not attempt to either read or write these columns.
+     *
+     * @hide
      */
     interface BaseSyncColumns {
         /** Generic column for use by sync adapters. */
@@ -74,6 +87,7 @@
 
     /**
      * Convenience definitions for use in implementing chrome bookmarks sync in the Bookmarks table.
+     * @hide
      */
     public static final class ChromeSyncColumns {
         private ChromeSyncColumns() {}
@@ -93,6 +107,7 @@
     /**
      * Columns that appear when each row of a table belongs to a specific
      * account, including sync information that an account may need.
+     * @hide
      */
     interface SyncColumns extends BaseSyncColumns {
         /**
@@ -144,13 +159,14 @@
         public static final String _ID = "_id";
 
         /**
-         * The URL of the bookmark.
+         * This column is valid when the row is a URL. The history table's URL
+         * can not be updated.
          * <P>Type: TEXT (URL)</P>
          */
         public static final String URL = "url";
 
         /**
-         * The user visible title of the bookmark.
+         * The user visible title.
          * <P>Type: TEXT</P>
          */
         public static final String TITLE = "title";
@@ -159,10 +175,14 @@
          * The time that this row was created on its originating client (msecs
          * since the epoch).
          * <P>Type: INTEGER</P>
+         * @hide
          */
         public static final String DATE_CREATED = "created";
     }
 
+    /**
+     * @hide
+     */
     interface ImageColumns {
         /**
          * The favicon of the bookmark, may be NULL.
@@ -182,7 +202,6 @@
          * The touch icon for the web page, may be NULL.
          * Must decode via {@link BitmapFactory#decodeByteArray}.
          * <p>Type: BLOB (image)</p>
-         * @hide
          */
         public static final String TOUCH_ICON = "touch_icon";
     }
@@ -200,9 +219,26 @@
          */
         public static final String VISITS = "visits";
 
+        /**
+         * @hide
+         */
         public static final String USER_ENTERED = "user_entered";
     }
 
+    interface ImageMappingColumns {
+        /**
+         * The ID of the image in Images. One image can map onto the multiple URLs.
+         * <P>Type: INTEGER (long)</P>
+         */
+        public static final String IMAGE_ID = "image_id";
+
+        /**
+         * The URL. The URL can map onto the different type of images.
+         * <P>Type: TEXT (URL)</P>
+         */
+        public static final String URL = "url";
+    }
+
     /**
      * The bookmarks table, which holds the user's browser bookmarks.
      */
@@ -218,24 +254,71 @@
         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks");
 
         /**
+         * Used in {@link Bookmarks#TYPE} column and indicats the row is a bookmark.
+         */
+        public static final int BOOKMARK_TYPE_BOOKMARK = 1;
+
+        /**
+         * Used in {@link Bookmarks#TYPE} column and indicats the row is a folder.
+         */
+        public static final int BOOKMARK_TYPE_FOLDER = 2;
+
+        /**
+         * Used in {@link Bookmarks#TYPE} column and indicats the row is the bookmark bar folder.
+         */
+        public static final int BOOKMARK_TYPE_BOOKMARK_BAR_FOLDER = 3;
+
+        /**
+         * Used in {@link Bookmarks#TYPE} column and indicats the row is other folder and
+         */
+        public static final int BOOKMARK_TYPE_OTHER_FOLDER = 4;
+
+        /**
+         * Used in {@link Bookmarks#TYPE} column and indicats the row is other folder, .
+         */
+        public static final int BOOKMARK_TYPE_MOBILE_FOLDER = 5;
+
+        /**
+         * The type of the item.
+         * <P>Type: INTEGER</P>
+         * <p>Allowed values are:</p>
+         * <p>
+         * <ul>
+         * <li>{@link #BOOKMARK_TYPE_BOOKMARK}</li>
+         * <li>{@link #BOOKMARK_TYPE_FOLDER}</li>
+         * <li>{@link #BOOKMARK_TYPE_BOOKMARK_BAR_FOLDER}</li>
+         * <li>{@link #BOOKMARK_TYPE_OTHER_FOLDER}</li>
+         * <li>{@link #BOOKMARK_TYPE_MOBILE_FOLDER}</li>
+         * </ul>
+         * </p>
+         * <p> The TYPE_BOOKMARK_BAR_FOLDER, TYPE_OTHER_FOLDER and TYPE_MOBILE_FOLDER
+         * can not be updated or deleted.</p>
+         */
+        public static final String TYPE = "type";
+
+        /**
          * The content:// style URI for the default folder
+         * @hide
          */
         public static final Uri CONTENT_URI_DEFAULT_FOLDER =
                 Uri.withAppendedPath(CONTENT_URI, "folder");
 
         /**
          * Query parameter used to specify an account name
+         * @hide
          */
         public static final String PARAM_ACCOUNT_NAME = "acct_name";
 
         /**
          * Query parameter used to specify an account type
+         * @hide
          */
         public static final String PARAM_ACCOUNT_TYPE = "acct_type";
 
         /**
          * Builds a URI that points to a specific folder.
          * @param folderId the ID of the folder to point to
+         * @hide
          */
         public static final Uri buildFolderUri(long folderId) {
             return ContentUris.withAppendedId(CONTENT_URI_DEFAULT_FOLDER, folderId);
@@ -255,6 +338,7 @@
          * Query parameter to use if you want to see deleted bookmarks that are still
          * around on the device and haven't been synced yet.
          * @see #IS_DELETED
+         * @hide
          */
         public static final String QUERY_PARAMETER_SHOW_DELETED = "show_deleted";
 
@@ -262,6 +346,7 @@
          * Flag indicating if an item is a folder or bookmark. Non-zero values indicate
          * a folder and zero indicates a bookmark.
          * <P>Type: INTEGER (boolean)</P>
+         * @hide
          */
         public static final String IS_FOLDER = "folder";
 
@@ -274,6 +359,7 @@
         /**
          * The source ID for an item's parent. Read-only.
          * @see #PARENT
+         * @hide
          */
         public static final String PARENT_SOURCE_ID = "parent_source";
 
@@ -281,6 +367,7 @@
          * The position of the bookmark in relation to it's siblings that share the same
          * {@link #PARENT}. May be negative.
          * <P>Type: INTEGER</P>
+         * @hide
          */
         public static final String POSITION = "position";
 
@@ -288,6 +375,7 @@
          * The item that the bookmark should be inserted after.
          * May be negative.
          * <P>Type: INTEGER</P>
+         * @hide
          */
         public static final String INSERT_AFTER = "insert_after";
 
@@ -296,6 +384,7 @@
          * May be negative.
          * <P>Type: INTEGER</P>
          * @see #INSERT_AFTER
+         * @hide
          */
         public static final String INSERT_AFTER_SOURCE_ID = "insert_after_source";
 
@@ -305,12 +394,14 @@
          * to the URI when performing your query.
          * <p>Type: INTEGER (non-zero if the item has been deleted, zero if it hasn't)
          * @see #QUERY_PARAMETER_SHOW_DELETED
+         * @hide
          */
         public static final String IS_DELETED = "deleted";
     }
 
     /**
      * Read-only table that lists all the accounts that are used to provide bookmarks.
+     * @hide
      */
     public static final class Accounts {
         /**
@@ -410,6 +501,7 @@
      * A table provided for sync adapters to use for storing private sync state data.
      *
      * @see SyncStateContract
+     * @hide
      */
     public static final class SyncState implements SyncStateContract.Columns {
         /**
@@ -459,8 +551,18 @@
     }
 
     /**
-     * Stores images for URLs. Only support query() and update().
-     * @hide
+     * <p>
+     * Stores images for URLs.
+     * </p>
+     * <p>
+     * The rows in this table can not be updated since there might have multiple URLs mapping onto
+     * the same image. If you want to update a URL's image, you need to add the new image in this
+     * table, then update the mapping onto the added image.
+     * </p>
+     * <p>
+     * Every image should be at least associated with one URL, otherwise it will be removed after a
+     * while.
+     * </p>
      */
     public static final class Images implements ImageColumns {
         /**
@@ -474,15 +576,93 @@
         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "images");
 
         /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of images.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/images";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} of a single image.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/images";
+
+        /**
+         * Used in {@link Images#TYPE} column and indicats the row is a favicon.
+         */
+        public static final int IMAGE_TYPE_FAVICON = 1;
+
+        /**
+         * Used in {@link Images#TYPE} column and indicats the row is a precomposed touch icon.
+         */
+        public static final int IMAGE_TYPE_PRECOMPOSED_TOUCH_ICON = 2;
+
+        /**
+         * Used in {@link Images#TYPE} column and indicats the row is a touch icon.
+         */
+        public static final int IMAGE_TYPE_TOUCH_ICON = 4;
+
+        /**
+         * The type of item in the table.
+         * <P>Type: INTEGER</P>
+         * <p>Allowed values are:</p>
+         * <p>
+         * <ul>
+         * <li>{@link #IMAGE_TYPE_FAVICON}</li>
+         * <li>{@link #IMAGE_TYPE_PRECOMPOSED_TOUCH_ICON}</li>
+         * <li>{@link #IMAGE_TYPE_TOUCH_ICON}</li>
+         * </ul>
+         * </p>
+         */
+        public static final String TYPE = "type";
+
+        /**
+         * The image data.
+         * <p>Type: BLOB (image)</p>
+         */
+        public static final String DATA = "data";
+
+        /**
          * The URL the images came from.
          * <P>Type: TEXT (URL)</P>
+         * @hide
          */
         public static final String URL = "url_key";
     }
 
     /**
+     * <p>
+     * A table that stores the mappings between the image and the URL.
+     * </p>
+     * <p>
+     * Deleting or Updating a mapping might also deletes the mapped image if there is no other URL
+     * maps onto it.
+     * </p>
+     */
+    public static final class ImageMappings implements ImageMappingColumns {
+        /**
+         * This utility class cannot be instantiated
+         */
+        private ImageMappings() {}
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "image_mappings");
+
+        /**
+         * The MIME type of {@link #CONTENT_URI} providing a directory of image mappings.
+         */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image_mappings";
+
+        /**
+         * The MIME type of a {@link #CONTENT_URI} of a single image mapping.
+         */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/image_mappings";
+    }
+
+    /**
      * A combined view of bookmarks and history. All bookmarks in all folders are included and
      * no folders are included.
+     * @hide
      */
     public static final class Combined implements CommonColumns, HistoryColumns, ImageColumns {
         /**
@@ -505,6 +685,7 @@
 
     /**
      * A table that stores settings specific to the browser. Only support query and insert.
+     * @hide
      */
     public static final class Settings {
         /**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 0e9306b..035d8c4 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1536,7 +1536,11 @@
          *
          * @param resolver the ContentResolver to use
          * @param contactId the person who was contacted
+         *
+         * @deprecated The class DataUsageStatUpdater of the Android support library should
+         *     be used instead.
          */
+        @Deprecated
         public static void markAsContacted(ContentResolver resolver, long contactId) {
             Uri uri = ContentUris.withAppendedId(CONTENT_URI, contactId);
             ContentValues values = new ContentValues();
@@ -7452,7 +7456,7 @@
     /**
      * <p>
      * API allowing applications to send usage information for each {@link Data} row to the
-     * Contacts Provider.
+     * Contacts Provider.  Applications can also clear all usage information.
      * </p>
      * <p>
      * With the feedback, Contacts Provider may return more contextually appropriate results for
@@ -7497,6 +7501,12 @@
      * boolean successful = resolver.update(uri, new ContentValues(), null, null) > 0;
      * </pre>
      * </p>
+     * <p>
+     * Applications can also clear all usage information with:
+     * <pre>
+     * boolean successful = resolver.delete(DataUsageFeedback.DELETE_USAGE_URI, null, null) > 0;
+     * </pre>
+     * </p>
      */
     public static final class DataUsageFeedback {
 
@@ -7508,6 +7518,14 @@
                 Uri.withAppendedPath(Data.CONTENT_URI, "usagefeedback");
 
         /**
+         * The content:// style URI for deleting all usage information.
+         * Must be used with {@link ContentResolver#delete(Uri, String, String[])}.
+         * The {@code where} and {@code selectionArgs} parameters are ignored.
+         */
+        public static final Uri DELETE_USAGE_URI =
+                Uri.withAppendedPath(Contacts.CONTENT_URI, "delete_usage");
+
+        /**
          * <p>
          * Name for query parameter specifying the type of data usage.
          * </p>
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index bd6170b..cd8d51f 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -704,6 +704,37 @@
          */
         public static final int STATUS_BLOCKED = 498;
 
+        /** {@hide} */
+        public static String statusToString(int status) {
+            switch (status) {
+                case STATUS_PENDING: return "PENDING";
+                case STATUS_RUNNING: return "RUNNING";
+                case STATUS_PAUSED_BY_APP: return "PAUSED_BY_APP";
+                case STATUS_WAITING_TO_RETRY: return "WAITING_TO_RETRY";
+                case STATUS_WAITING_FOR_NETWORK: return "WAITING_FOR_NETWORK";
+                case STATUS_QUEUED_FOR_WIFI: return "QUEUED_FOR_WIFI";
+                case STATUS_INSUFFICIENT_SPACE_ERROR: return "INSUFFICIENT_SPACE_ERROR";
+                case STATUS_DEVICE_NOT_FOUND_ERROR: return "DEVICE_NOT_FOUND_ERROR";
+                case STATUS_SUCCESS: return "SUCCESS";
+                case STATUS_BAD_REQUEST: return "BAD_REQUEST";
+                case STATUS_NOT_ACCEPTABLE: return "NOT_ACCEPTABLE";
+                case STATUS_LENGTH_REQUIRED: return "LENGTH_REQUIRED";
+                case STATUS_PRECONDITION_FAILED: return "PRECONDITION_FAILED";
+                case STATUS_FILE_ALREADY_EXISTS_ERROR: return "FILE_ALREADY_EXISTS_ERROR";
+                case STATUS_CANNOT_RESUME: return "CANNOT_RESUME";
+                case STATUS_CANCELED: return "CANCELED";
+                case STATUS_UNKNOWN_ERROR: return "UNKNOWN_ERROR";
+                case STATUS_FILE_ERROR: return "FILE_ERROR";
+                case STATUS_UNHANDLED_REDIRECT: return "UNHANDLED_REDIRECT";
+                case STATUS_UNHANDLED_HTTP_CODE: return "UNHANDLED_HTTP_CODE";
+                case STATUS_HTTP_DATA_ERROR: return "HTTP_DATA_ERROR";
+                case STATUS_HTTP_EXCEPTION: return "HTTP_EXCEPTION";
+                case STATUS_TOO_MANY_REDIRECTS: return "TOO_MANY_REDIRECTS";
+                case STATUS_BLOCKED: return "BLOCKED";
+                default: return Integer.toString(status);
+            }
+        }
+
         /**
          * This download is visible but only shows in the notifications
          * while it's in progress.
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 6056c75..11c169e 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -75,7 +75,7 @@
                 if (spans[i] instanceof NoCopySpan) {
                     continue;
                 }
-                
+
                 int st = sp.getSpanStart(spans[i]) - start;
                 int en = sp.getSpanEnd(spans[i]) - start;
                 int fl = sp.getSpanFlags(spans[i]);
@@ -212,7 +212,7 @@
 
         if (mGapLength > 2 * length())
             resizeFor(length());
-        
+
         return ret; // == this
     }
 
@@ -220,7 +220,7 @@
     public void clear() {
         replace(0, length(), "", 0, 0);
     }
-    
+
     // Documentation from interface
     public void clearSpans() {
         for (int i = mSpanCount - 1; i >= 0; i--) {
@@ -257,45 +257,50 @@
         return append(String.valueOf(text));
     }
 
-    private void change(int start, int end, CharSequence tb, int tbstart, int tbend) {
-        checkRange("replace", start, end);
+    private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
+        // Can be negative
+        final int nbNewChars = (csEnd - csStart) - (end - start);
 
         for (int i = mSpanCount - 1; i >= 0; i--) {
+            int spanStart = mSpanStarts[i];
+            if (spanStart > mGapStart)
+                spanStart -= mGapLength;
+
+            int spanEnd = mSpanEnds[i];
+            if (spanEnd > mGapStart)
+                spanEnd -= mGapLength;
+
             if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
-                int st = mSpanStarts[i];
-                if (st > mGapStart)
-                    st -= mGapLength;
-
-                int en = mSpanEnds[i];
-                if (en > mGapStart)
-                    en -= mGapLength;
-
-                int ost = st;
-                int oen = en;
+                int ost = spanStart;
+                int oen = spanEnd;
                 int clen = length();
 
-                if (st > start && st <= end) {
-                    for (st = end; st < clen; st++)
-                        if (st > end && charAt(st - 1) == '\n')
+                if (spanStart > start && spanStart <= end) {
+                    for (spanStart = end; spanStart < clen; spanStart++)
+                        if (spanStart > end && charAt(spanStart - 1) == '\n')
                             break;
                 }
 
-                if (en > start && en <= end) {
-                    for (en = end; en < clen; en++)
-                        if (en > end && charAt(en - 1) == '\n')
+                if (spanEnd > start && spanEnd <= end) {
+                    for (spanEnd = end; spanEnd < clen; spanEnd++)
+                        if (spanEnd > end && charAt(spanEnd - 1) == '\n')
                             break;
                 }
 
-                if (st != ost || en != oen)
-                    setSpan(false, mSpans[i], st, en, mSpanFlags[i]);
+                if (spanStart != ost || spanEnd != oen)
+                    setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
             }
+
+            int flags = 0;
+            if (spanStart == start) flags |= SPAN_START_AT_START;
+            else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
+            if (spanEnd == start) flags |= SPAN_END_AT_START;
+            else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
+            mSpanFlags[i] |= flags;
         }
 
         moveGapTo(end);
 
-        // Can be negative
-        final int nbNewChars = (tbend - tbstart) - (end - start);
-
         if (nbNewChars >= mGapLength) {
             resizeFor(mText.length + nbNewChars - mGapLength);
         }
@@ -306,7 +311,7 @@
         if (mGapLength < 1)
             new Exception("mGapLength < 1").printStackTrace();
 
-        TextUtils.getChars(tb, tbstart, tbend, mText, start);
+        TextUtils.getChars(cs, csStart, csEnd, mText, start);
 
         if (end > start) {
             // no need for span fixup on pure insertion
@@ -340,21 +345,23 @@
             }
         }
 
-        if (tb instanceof Spanned) {
-            Spanned sp = (Spanned) tb;
-            Object[] spans = sp.getSpans(tbstart, tbend, Object.class);
+        mSpanCountBeforeAdd = mSpanCount;
+
+        if (cs instanceof Spanned) {
+            Spanned sp = (Spanned) cs;
+            Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
 
             for (int i = 0; i < spans.length; i++) {
                 int st = sp.getSpanStart(spans[i]);
                 int en = sp.getSpanEnd(spans[i]);
 
-                if (st < tbstart) st = tbstart;
-                if (en > tbend) en = tbend;
+                if (st < csStart) st = csStart;
+                if (en > csEnd) en = csEnd;
 
                 // Add span only if this object is not yet used as a span in this string
-                if (getSpanStart(spans[i]) < 0) {
-                        setSpan(false, spans[i], st - tbstart + start, en - tbstart + start,
-                                sp.getSpanFlags(spans[i]));
+                if (getSpanStart(spans[i]) < 0 && !(spans[i] instanceof SpanWatcher)) {
+                    setSpan(false, spans[i], st - csStart + start, en - csStart + start,
+                            sp.getSpanFlags(spans[i]));
                 }
             }
         }
@@ -390,6 +397,8 @@
     // Documentation from interface
     public SpannableStringBuilder replace(final int start, final int end,
             CharSequence tb, int tbstart, int tbend) {
+        checkRange("replace", start, end);
+
         int filtercount = mFilters.length;
         for (int i = 0; i < filtercount; i++) {
             CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
@@ -404,10 +413,6 @@
         final int origLen = end - start;
         final int newLen = tbend - tbstart;
 
-        if (origLen == 0 && newLen == 0) {
-            return this;
-        }
-
         TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
         sendBeforeTextChanged(textWatchers, start, origLen, newLen);
 
@@ -415,43 +420,101 @@
         // a text replacement. If replaced or replacement text length is zero, this
         // is already taken care of.
         boolean adjustSelection = origLen != 0 && newLen != 0;
-        int selstart = 0;
-        int selend = 0;
+        int selectionStart = 0;
+        int selectionEnd = 0;
         if (adjustSelection) {
-            selstart = Selection.getSelectionStart(this);
-            selend = Selection.getSelectionEnd(this);
+            selectionStart = Selection.getSelectionStart(this);
+            selectionEnd = Selection.getSelectionEnd(this);
         }
 
-        checkRange("replace", start, end);
-
         change(start, end, tb, tbstart, tbend);
 
         if (adjustSelection) {
-            if (selstart > start && selstart < end) {
-                long off = selstart - start;
+            if (selectionStart > start && selectionStart < end) {
+                final int offset = (selectionStart - start) * newLen / origLen;
+                selectionStart = start + offset;
 
-                off = off * newLen / origLen;
-                selstart = (int) off + start;
-
-                setSpan(false, Selection.SELECTION_START, selstart, selstart,
+                setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
                         Spanned.SPAN_POINT_POINT);
             }
-            if (selend > start && selend < end) {
-                long off = selend - start;
+            if (selectionEnd > start && selectionEnd < end) {
+                final int offset = (selectionEnd - start) * newLen / origLen;
+                selectionEnd = start + offset;
 
-                off = off * newLen / origLen;
-                selend = (int) off + start;
-
-                setSpan(false, Selection.SELECTION_END, selend, selend, Spanned.SPAN_POINT_POINT);
+                setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
+                        Spanned.SPAN_POINT_POINT);
             }
         }
 
         sendTextChanged(textWatchers, start, origLen, newLen);
         sendAfterTextChanged(textWatchers);
 
+        // Span watchers need to be called after text watchers, which may update the layout
+        sendToSpanWatchers(start, end, newLen - origLen);
+
         return this; 
     }
 
+    private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
+        for (int i = 0; i < mSpanCountBeforeAdd; i++) {
+            int spanStart = mSpanStarts[i];
+            int spanEnd = mSpanEnds[i];
+            if (spanStart > mGapStart) spanStart -= mGapLength;
+            if (spanEnd > mGapStart) spanEnd -= mGapLength;
+            int spanFlags = mSpanFlags[i];
+
+            int newReplaceEnd = replaceEnd + nbNewChars;
+            boolean spanChanged = false;
+            int previousSpanStart = spanStart;
+            if (spanStart > newReplaceEnd) {
+                if (nbNewChars != 0) {
+                    previousSpanStart -= nbNewChars;
+                    spanChanged = true;
+                }
+            } else if (spanStart >= replaceStart) {
+                // No change if span start was already at replace interval boundaries before replace
+                if ((spanStart != replaceStart ||
+                        ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
+                        (spanStart != newReplaceEnd ||
+                        ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
+                    // TODO previousSpanStart is incorrect, but we would need to save all the
+                    // previous spans' positions before replace to provide it
+                    spanChanged = true;
+                }
+            }
+            int previousSpanEnd = spanEnd;
+            if (spanEnd > newReplaceEnd) {
+                if (nbNewChars != 0) {
+                    previousSpanEnd -= nbNewChars;
+                    spanChanged = true;
+                }
+            } else if (spanEnd >= replaceStart) {
+                // No change if span start was already at replace interval boundaries before replace
+                if ((spanEnd != replaceStart ||
+                        ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
+                        (spanEnd != newReplaceEnd ||
+                        ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
+                    // TODO same as above for previousSpanEnd
+                    spanChanged = true;
+                }
+            }
+
+            if (spanChanged) {
+                sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
+            }
+            mSpanFlags[i] &= ~SPAN_START_END_MASK;
+        }
+
+        // The spans starting at mIntermediateSpanCount were added from the replacement text
+        for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
+            int spanStart = mSpanStarts[i];
+            int spanEnd = mSpanEnds[i];
+            if (spanStart > mGapStart) spanStart -= mGapLength;
+            if (spanEnd > mGapStart) spanEnd -= mGapLength;
+            sendSpanAdded(mSpans[i], spanStart, spanEnd);
+        }
+    }
+
     /**
      * Mark the specified range of text with the specified object.
      * The flags determine how the span will behave when text is
@@ -788,13 +851,12 @@
         if (end <= mGapStart) {
             System.arraycopy(mText, start, dest, destoff, end - start);
         } else if (start >= mGapStart) {
-            System.arraycopy(mText, start + mGapLength,
-                             dest, destoff, end - start);
+            System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
         } else {
             System.arraycopy(mText, start, dest, destoff, mGapStart - start);
             System.arraycopy(mText, mGapStart + mGapLength,
-                             dest, destoff + (mGapStart - start),
-                             end - mGapStart);
+                    dest, destoff + (mGapStart - start),
+                    end - mGapStart);
         }
     }
 
@@ -863,12 +925,14 @@
         }
     }
 
-    private void sendSpanChanged(Object what, int s, int e, int st, int en) {
-        SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), SpanWatcher.class);
-        int n = recip.length;
-
+    private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
+        // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
+        // called, so that the order of the span does not affect this broadcast.
+        SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
+                Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
+        int n = spanWatchers.length;
         for (int i = 0; i < n; i++) {
-            recip[i].onSpanChanged(this, what, s, e, st, en);
+            spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
         }
     }
 
@@ -879,26 +943,23 @@
     private void checkRange(final String operation, int start, int end) {
         if (end < start) {
             throw new IndexOutOfBoundsException(operation + " " +
-                                                region(start, end) +
-                                                " has end before start");
+                    region(start, end) + " has end before start");
         }
 
         int len = length();
 
         if (start > len || end > len) {
             throw new IndexOutOfBoundsException(operation + " " +
-                                                region(start, end) +
-                                                " ends beyond length " + len);
+                    region(start, end) + " ends beyond length " + len);
         }
 
         if (start < 0 || end < 0) {
             throw new IndexOutOfBoundsException(operation + " " +
-                                                region(start, end) +
-                                                " starts before 0");
+                    region(start, end) + " starts before 0");
         }
     }
 
-/*
+    /*
     private boolean isprint(char c) { // XXX
         if (c >= ' ' && c <= '~')
             return true;
@@ -977,7 +1038,7 @@
 
         System.out.print("\n");
     }
-*/
+     */
 
     /**
      * Don't call this yourself -- exists for Canvas to use internally.
@@ -1023,7 +1084,7 @@
         }
     }
 
-   /**
+    /**
      * Don't call this yourself -- exists for Paint to use internally.
      * {@hide}
      */
@@ -1059,8 +1120,7 @@
         if (end <= mGapStart) {
             ret = p.getTextWidths(mText, start, end - start, widths);
         } else if (start >= mGapStart) {
-            ret = p.getTextWidths(mText, start + mGapLength, end - start,
-                                  widths);
+            ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
         } else {
             char[] buf = TextUtils.obtain(end - start);
 
@@ -1205,6 +1265,7 @@
     private int[] mSpanEnds;
     private int[] mSpanFlags;
     private int mSpanCount;
+    private int mSpanCountBeforeAdd;
 
     // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
     private static final int MARK = 1;
@@ -1214,4 +1275,11 @@
     private static final int START_MASK = 0xF0;
     private static final int END_MASK = 0x0F;
     private static final int START_SHIFT = 4;
+
+    // These bits are not (currently) used by SPANNED flags
+    private static final int SPAN_START_AT_START = 0x1000;
+    private static final int SPAN_START_AT_END = 0x2000;
+    private static final int SPAN_END_AT_START = 0x4000;
+    private static final int SPAN_END_AT_END = 0x8000;
+    private static final int SPAN_START_END_MASK = 0xF000;
 }
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index aa0ac74..8bc36b7 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -237,6 +237,17 @@
     abstract boolean validate();
 
     /**
+     * This method ensures the hardware renderer is in a valid state
+     * before executing the specified action.
+     * 
+     * This method will attempt to set a valid state even if the window
+     * the renderer is attached to was destroyed.
+     *
+     * @return true if the action was run
+     */
+    abstract boolean safelyRun(Runnable action);
+
+    /**
      * Setup the hardware renderer for drawing. This is called whenever the
      * size of the target surface changes or when the surface is first created.
      * 
@@ -1380,26 +1391,40 @@
         }
 
         @Override
-        void destroyHardwareResources(View view) {
-            if (view != null) {
-                boolean needsContext = true;
-                if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false;
+        boolean safelyRun(Runnable action) {
+            boolean needsContext = true;
+            if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false;
 
-                if (needsContext) {
-                    Gl20RendererEglContext managedContext =
-                            (Gl20RendererEglContext) sEglContextStorage.get();
-                    if (managedContext == null) return;
-                    usePbufferSurface(managedContext.getContext());
-                }
+            if (needsContext) {
+                Gl20RendererEglContext managedContext =
+                        (Gl20RendererEglContext) sEglContextStorage.get();
+                if (managedContext == null) return false;
+                usePbufferSurface(managedContext.getContext());
+            }
 
-                destroyResources(view);
-                GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
-
+            try {
+                action.run();
+            } finally {
                 if (needsContext) {
                     sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
                             EGL_NO_SURFACE, EGL_NO_CONTEXT);
                 }
             }
+
+            return true;
+        }
+
+        @Override
+        void destroyHardwareResources(final View view) {
+            if (view != null) {
+                safelyRun(new Runnable() {
+                    @Override
+                    public void run() {
+                        destroyResources(view);
+                        GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
+                    }
+                });
+            }
         }
 
         private static void destroyResources(View view) {
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 75b2c746..4ebb679 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -40,6 +40,7 @@
  */
 public final class InputDevice implements Parcelable {
     private final int mId;
+    private final int mGeneration;
     private final String mName;
     private final String mDescriptor;
     private final int mSources;
@@ -302,9 +303,10 @@
     };
 
     // Called by native code.
-    private InputDevice(int id, String name, String descriptor, int sources,
+    private InputDevice(int id, int generation, String name, String descriptor, int sources,
             int keyboardType, KeyCharacterMap keyCharacterMap) {
         mId = id;
+        mGeneration = generation;
         mName = name;
         mDescriptor = descriptor;
         mSources = sources;
@@ -314,6 +316,7 @@
 
     private InputDevice(Parcel in) {
         mId = in.readInt();
+        mGeneration = in.readInt();
         mName = in.readString();
         mDescriptor = in.readString();
         mSources = in.readInt();
@@ -364,6 +367,19 @@
     }
 
     /**
+     * Gets a generation number for this input device.
+     * The generation number is incremented whenever the device is reconfigured and its
+     * properties may have changed.
+     *
+     * @return The generation number.
+     *
+     * @hide
+     */
+    public int getGeneration() {
+        return mGeneration;
+    }
+
+    /**
      * Gets the input device descriptor, which is a stable identifier for an input device.
      * <p>
      * An input device descriptor uniquely identifies an input device.  Its value
@@ -595,6 +611,7 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mId);
+        out.writeInt(mGeneration);
         out.writeString(mName);
         out.writeString(mDescriptor);
         out.writeInt(mSources);
@@ -624,6 +641,7 @@
         StringBuilder description = new StringBuilder();
         description.append("Input Device ").append(mId).append(": ").append(mName).append("\n");
         description.append("  Descriptor: ").append(mDescriptor).append("\n");
+        description.append("  Generation: ").append(mGeneration).append("\n");
 
         description.append("  Keyboard Type: ");
         switch (mKeyboardType) {
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 3d165ea..12d7b12 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -22,8 +22,6 @@
 import android.util.AndroidRuntimeException;
 import android.util.SparseIntArray;
 import android.hardware.input.InputManager;
-import android.util.SparseArray;
-import android.view.InputDevice.MotionRange;
 
 import java.lang.Character;
 
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 3cd8b71..32029ba 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -187,7 +187,9 @@
     public void setOpaque(boolean opaque) {
         if (opaque != mOpaque) {
             mOpaque = opaque;
-            updateLayer();
+            if (mLayer != null) {
+                updateLayer();
+            }
         }
     }
 
@@ -204,7 +206,18 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        destroySurface();
+        if (mLayer != null && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) {
+            boolean success = mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() {
+                @Override
+                public void run() {
+                    destroySurface();
+                }
+            });
+
+            if (!success) {
+                Log.w(LOG_TAG, "TextureView was not able to destroy its surface: " + this);
+            }
+        }
     }
 
     private void destroySurface() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0be7a87..1fa19d1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4483,10 +4483,8 @@
         getDrawingRect(bounds);
         info.setBoundsInParent(bounds);
 
-        int[] locationOnScreen = mAttachInfo.mInvalidateChildLocation;
-        getLocationOnScreen(locationOnScreen);
-        bounds.offsetTo(0, 0);
-        bounds.offset(locationOnScreen[0], locationOnScreen[1]);
+        getGlobalVisibleRect(bounds);
+        bounds.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
         info.setBoundsInScreen(bounds);
 
         if ((mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
@@ -9792,7 +9790,7 @@
      * @attr ref android.R.styleable#View_scrollbarSize
      */
     public int getScrollBarSize() {
-        return mScrollCache == null ? ViewConfiguration.getScrollBarSize() :
+        return mScrollCache == null ? ViewConfiguration.get(mContext).getScaledScrollBarSize() :
                 mScrollCache.scrollBarSize;
     }
 
@@ -12971,6 +12969,7 @@
      *        background
      */
     public void setBackground(Drawable background) {
+        //noinspection deprecation
         setBackgroundDrawable(background);
     }
 
@@ -14296,7 +14295,15 @@
      */
     public void setAnimation(Animation animation) {
         mCurrentAnimation = animation;
+
         if (animation != null) {
+            // If the screen is off assume the animation start time is now instead of
+            // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time
+            // would cause the animation to start when the screen turns back on
+            if (mAttachInfo != null && !mAttachInfo.mScreenOn &&
+                    animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
+                animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
+            }
             animation.reset();
         }
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 899fb32..7a43cf1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5065,6 +5065,23 @@
     }
 
     /**
+     * Computes whether a view is visible on the screen.
+     *
+     * @param view The view to check.
+     * @return Whether the view is visible on the screen.
+     */
+    private boolean isDisplayedOnScreen(View view) {
+        // The first two checks are made also made by isShown() which
+        // however traverses the tree up to the parent to catch that.
+        // Therefore, we do some fail fast check to minimize the up
+        // tree traversal.
+        return (view.mAttachInfo != null
+                && view.mAttachInfo.mWindowVisibility == View.VISIBLE
+                && view.isShown()
+                && view.getGlobalVisibleRect(mTempRect));
+    }
+
+    /**
      * Class for managing accessibility interactions initiated from the system
      * and targeting the view hierarchy. A *ClientThread method is to be
      * called from the interaction connection this ViewAncestor gives the
@@ -5175,7 +5192,7 @@
                 } else {
                     target = findViewByAccessibilityId(accessibilityViewId);
                 }
-                if (target != null && target.getVisibility() == View.VISIBLE) {
+                if (target != null && isDisplayedOnScreen(target)) {
                     getAccessibilityNodePrefetcher().prefetchAccessibilityNodeInfos(target,
                             virtualDescendantId, prefetchFlags, infos);
                 }
@@ -5231,7 +5248,7 @@
                 }
                 if (root != null) {
                     View target = root.findViewById(viewId);
-                    if (target != null && target.getVisibility() == View.VISIBLE) {
+                    if (target != null && isDisplayedOnScreen(target)) {
                         info = target.createAccessibilityNodeInfo();
                     }
                 }
@@ -5287,7 +5304,7 @@
                 } else {
                     target = ViewRootImpl.this.mView;
                 }
-                if (target != null && target.getVisibility() == View.VISIBLE) {
+                if (target != null && isDisplayedOnScreen(target)) {
                     AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
                     if (provider != null) {
                         infos = provider.findAccessibilityNodeInfosByText(text,
@@ -5304,7 +5321,7 @@
                             final int viewCount = foundViews.size();
                             for (int i = 0; i < viewCount; i++) {
                                 View foundView = foundViews.get(i);
-                                if (foundView.getVisibility() == View.VISIBLE) {
+                                if (isDisplayedOnScreen(foundView)) {
                                     provider = foundView.getAccessibilityNodeProvider();
                                     if (provider != null) {
                                         List<AccessibilityNodeInfo> infosFromProvider =
@@ -5367,7 +5384,7 @@
             boolean succeeded = false;
             try {
                 View target = findViewByAccessibilityId(accessibilityViewId);
-                if (target != null && target.getVisibility() == View.VISIBLE) {
+                if (target != null && isDisplayedOnScreen(target)) {
                     AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
                     if (provider != null) {
                         succeeded = provider.performAccessibilityAction(action,
@@ -5505,7 +5522,7 @@
                     View child = parentGroup.getChildAt(i);
                     if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE
                             && child.getAccessibilityViewId() != current.getAccessibilityViewId()
-                            && child.getVisibility() == View.VISIBLE) {
+                            && isDisplayedOnScreen(child)) {
                         final long childNodeId = AccessibilityNodeInfo.makeNodeId(
                                 child.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
                         AccessibilityNodeInfo info = null;
@@ -5533,7 +5550,7 @@
                 final int childCount = rootGroup.getChildCount();
                 for (int i = 0; i < childCount; i++) {
                     View child = rootGroup.getChildAt(i);
-                    if (child.getVisibility() == View.VISIBLE
+                    if (isDisplayedOnScreen(child)
                             && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
                         final long childNodeId = AccessibilityNodeInfo.makeNodeId(
                                 child.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 66bdc5d..27baaea 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -667,7 +667,7 @@
     /**
      * Create and return an animation to re-display a force hidden window.
      */
-    public Animation createForceHideEnterAnimation();
+    public Animation createForceHideEnterAnimation(boolean onWallpaper);
     
     /**
      * Called from the input reader thread before a key is enqueued.
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 800ebc8..79bd5d3 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -147,6 +147,40 @@
         }
     }
 
+    private class JsResultReceiver implements JsResult.ResultReceiver {
+        // This prevents a user from interacting with the result before WebCore is
+        // ready to handle it.
+        private boolean mReady;
+        // Tells us if the user tried to confirm or cancel the result before WebCore
+        // is ready.
+        private boolean mTriedToNotifyBeforeReady;
+
+        public JsPromptResult mJsResult = new JsPromptResult(this);
+
+        final void setReady() {
+            mReady = true;
+            if (mTriedToNotifyBeforeReady) {
+                notifyCallbackProxy();
+            }
+        }
+
+        /* Wake up the WebCore thread. */
+        @Override
+        public void onJsResultComplete(JsResult result) {
+            if (mReady) {
+                notifyCallbackProxy();
+            } else {
+                mTriedToNotifyBeforeReady = true;
+            }
+        }
+
+        private void notifyCallbackProxy() {
+            synchronized (CallbackProxy.this) {
+                CallbackProxy.this.notify();
+            }
+        }
+}
+
     /**
      * Construct a new CallbackProxy.
      */
@@ -531,14 +565,15 @@
 
             case JS_ALERT:
                 if (mWebChromeClient != null) {
-                    final JsResult res = (JsResult) msg.obj;
+                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+                    final JsResult res = receiver.mJsResult;
                     String message = msg.getData().getString("message");
                     String url = msg.getData().getString("url");
                     if (!mWebChromeClient.onJsAlert(mWebView.getWebView(), url, message,
                             res)) {
                         if (!canShowAlertDialog()) {
                             res.cancel();
-                            res.setReady();
+                            receiver.setReady();
                             break;
                         }
                         new AlertDialog.Builder(mContext)
@@ -561,20 +596,21 @@
                                         })
                                 .show();
                     }
-                    res.setReady();
+                    receiver.setReady();
                 }
                 break;
 
             case JS_CONFIRM:
                 if (mWebChromeClient != null) {
-                    final JsResult res = (JsResult) msg.obj;
+                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+                    final JsResult res = receiver.mJsResult;
                     String message = msg.getData().getString("message");
                     String url = msg.getData().getString("url");
                     if (!mWebChromeClient.onJsConfirm(mWebView.getWebView(), url, message,
                             res)) {
                         if (!canShowAlertDialog()) {
                             res.cancel();
-                            res.setReady();
+                            receiver.setReady();
                             break;
                         }
                         new AlertDialog.Builder(mContext)
@@ -605,13 +641,14 @@
                     }
                     // Tell the JsResult that it is ready for client
                     // interaction.
-                    res.setReady();
+                    receiver.setReady();
                 }
                 break;
 
             case JS_PROMPT:
                 if (mWebChromeClient != null) {
-                    final JsPromptResult res = (JsPromptResult) msg.obj;
+                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+                    final JsPromptResult res = receiver.mJsResult;
                     String message = msg.getData().getString("message");
                     String defaultVal = msg.getData().getString("default");
                     String url = msg.getData().getString("url");
@@ -619,7 +656,7 @@
                                 defaultVal, res)) {
                         if (!canShowAlertDialog()) {
                             res.cancel();
-                            res.setReady();
+                            receiver.setReady();
                             break;
                         }
                         final LayoutInflater factory = LayoutInflater
@@ -662,20 +699,21 @@
                     }
                     // Tell the JsResult that it is ready for client
                     // interaction.
-                    res.setReady();
+                    receiver.setReady();
                 }
                 break;
 
             case JS_UNLOAD:
                 if (mWebChromeClient != null) {
-                    final JsResult res = (JsResult) msg.obj;
+                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+                    final JsResult res = receiver.mJsResult;
                     String message = msg.getData().getString("message");
                     String url = msg.getData().getString("url");
                     if (!mWebChromeClient.onJsBeforeUnload(mWebView.getWebView(), url,
                             message, res)) {
                         if (!canShowAlertDialog()) {
                             res.cancel();
-                            res.setReady();
+                            receiver.setReady();
                             break;
                         }
                         final String m = mContext.getString(
@@ -700,19 +738,20 @@
                                         })
                                 .show();
                     }
-                    res.setReady();
+                    receiver.setReady();
                 }
                 break;
 
             case JS_TIMEOUT:
                 if(mWebChromeClient != null) {
-                    final JsResult res = (JsResult) msg.obj;
+                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+                    final JsResult res = receiver.mJsResult;
                     if(mWebChromeClient.onJsTimeout()) {
                         res.confirm();
                     } else {
                         res.cancel();
                     }
-                    res.setReady();
+                    receiver.setReady();
                 }
                 break;
 
@@ -791,7 +830,8 @@
             case OPEN_FILE_CHOOSER:
                 if (mWebChromeClient != null) {
                     UploadFileMessageData data = (UploadFileMessageData)msg.obj;
-                    mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType());
+                    mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType(),
+                            data.getCapture());
                 }
                 break;
 
@@ -1331,7 +1371,7 @@
         if (mWebChromeClient == null) {
             return;
         }
-        JsResult result = new JsResult(this, false);
+        JsResultReceiver result = new JsResultReceiver();
         Message alert = obtainMessage(JS_ALERT, result);
         alert.getData().putString("message", message);
         alert.getData().putString("url", url);
@@ -1352,7 +1392,7 @@
         if (mWebChromeClient == null) {
             return false;
         }
-        JsResult result = new JsResult(this, false);
+        JsResultReceiver result = new JsResultReceiver();
         Message confirm = obtainMessage(JS_CONFIRM, result);
         confirm.getData().putString("message", message);
         confirm.getData().putString("url", url);
@@ -1365,7 +1405,7 @@
                 Log.e(LOGTAG, Log.getStackTraceString(e));
             }
         }
-        return result.getResult();
+        return result.mJsResult.getResult();
     }
 
     public String onJsPrompt(String url, String message, String defaultValue) {
@@ -1374,7 +1414,7 @@
         if (mWebChromeClient == null) {
             return null;
         }
-        JsPromptResult result = new JsPromptResult(this);
+        JsResultReceiver result = new JsResultReceiver();
         Message prompt = obtainMessage(JS_PROMPT, result);
         prompt.getData().putString("message", message);
         prompt.getData().putString("default", defaultValue);
@@ -1388,7 +1428,7 @@
                 Log.e(LOGTAG, Log.getStackTraceString(e));
             }
         }
-        return result.getStringResult();
+        return result.mJsResult.getStringResult();
     }
 
     public boolean onJsBeforeUnload(String url, String message) {
@@ -1397,7 +1437,7 @@
         if (mWebChromeClient == null) {
             return true;
         }
-        JsResult result = new JsResult(this, true);
+        JsResultReceiver result = new JsResultReceiver();
         Message confirm = obtainMessage(JS_UNLOAD, result);
         confirm.getData().putString("message", message);
         confirm.getData().putString("url", url);
@@ -1410,7 +1450,7 @@
                 Log.e(LOGTAG, Log.getStackTraceString(e));
             }
         }
-        return result.getResult();
+        return result.mJsResult.getResult();
     }
 
     /**
@@ -1540,7 +1580,7 @@
         if (mWebChromeClient == null) {
             return true;
         }
-        JsResult result = new JsResult(this, true);
+        JsResultReceiver result = new JsResultReceiver();
         Message timeout = obtainMessage(JS_TIMEOUT, result);
         synchronized (this) {
             sendMessage(timeout);
@@ -1551,7 +1591,7 @@
                 Log.e(LOGTAG, Log.getStackTraceString(e));
             }
         }
-        return result.getResult();
+        return result.mJsResult.getResult();
     }
 
     public void getVisitedHistory(ValueCallback<String[]> callback) {
@@ -1566,10 +1606,12 @@
     private static class UploadFileMessageData {
         private UploadFile mCallback;
         private String mAcceptType;
+        private String mCapture;
 
-        public UploadFileMessageData(UploadFile uploadFile, String acceptType) {
+        public UploadFileMessageData(UploadFile uploadFile, String acceptType, String capture) {
             mCallback = uploadFile;
             mAcceptType = acceptType;
+            mCapture = capture;
         }
 
         public UploadFile getUploadFile() {
@@ -1579,6 +1621,10 @@
         public String getAcceptType() {
             return mAcceptType;
         }
+
+        public String getCapture() {
+            return mCapture;
+        }
     }
 
     private class UploadFile implements ValueCallback<Uri> {
@@ -1597,13 +1643,13 @@
     /**
      * Called by WebViewCore to open a file chooser.
      */
-    /* package */ Uri openFileChooser(String acceptType) {
+    /* package */ Uri openFileChooser(String acceptType, String capture) {
         if (mWebChromeClient == null) {
             return null;
         }
         Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
         UploadFile uploadFile = new UploadFile();
-        UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType);
+        UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType, capture);
         myMessage.obj = data;
         synchronized (this) {
             sendMessage(myMessage);
diff --git a/core/java/android/webkit/DeviceMotionAndOrientationManager.java b/core/java/android/webkit/DeviceMotionAndOrientationManager.java
index 79b78d8..ea1e9ff 100644
--- a/core/java/android/webkit/DeviceMotionAndOrientationManager.java
+++ b/core/java/android/webkit/DeviceMotionAndOrientationManager.java
@@ -22,9 +22,8 @@
  *
  * This could be part of WebViewCore, but have moved it to its own class to
  * avoid bloat there.
- * @hide
  */
-public final class DeviceMotionAndOrientationManager {
+final class DeviceMotionAndOrientationManager {
     private WebViewCore mWebViewCore;
 
     public DeviceMotionAndOrientationManager(WebViewCore webViewCore) {
@@ -32,12 +31,12 @@
     }
 
     /**
-     * Sets whether the Page for this WebViewCore should use a mock DeviceOrientation
+     * Sets that the Page for this WebViewCore should use a mock DeviceOrientation
      * client.
      */
-    public void useMock() {
+    public void setUseMock() {
         assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
-        nativeUseMock(mWebViewCore);
+        nativeSetUseMock(mWebViewCore);
     }
 
     /**
@@ -66,7 +65,7 @@
     }
 
     // Native functions
-    private static native void nativeUseMock(WebViewCore webViewCore);
+    private static native void nativeSetUseMock(WebViewCore webViewCore);
     private static native void nativeSetMockOrientation(WebViewCore webViewCore,
             boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
             boolean canProvideGamma, double gamma);
diff --git a/core/java/android/webkit/DeviceMotionService.java b/core/java/android/webkit/DeviceMotionService.java
index b4d5759..9121429 100755
--- a/core/java/android/webkit/DeviceMotionService.java
+++ b/core/java/android/webkit/DeviceMotionService.java
@@ -153,6 +153,7 @@
      * SensorEventListener implementation.
      * Callbacks happen on the thread on which we registered - the WebCore thread.
      */
+    @Override
     public void onSensorChanged(SensorEvent event) {
         assert(event.values.length == 3);
         assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
@@ -170,6 +171,7 @@
         }
     }
 
+    @Override
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
         assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
     }
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
index 47c8ab7..2e8656c 100755
--- a/core/java/android/webkit/DeviceOrientationService.java
+++ b/core/java/android/webkit/DeviceOrientationService.java
@@ -184,6 +184,7 @@
      * SensorEventListener implementation.
      * Callbacks happen on the thread on which we registered - the WebCore thread.
      */
+    @Override
     public void onSensorChanged(SensorEvent event) {
         assert(event.values.length == 3);
         assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
@@ -217,6 +218,7 @@
         }
     }
 
+    @Override
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
         assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
     }
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java
index 93eb082..1160d57 100755
--- a/core/java/android/webkit/GeolocationPermissions.java
+++ b/core/java/android/webkit/GeolocationPermissions.java
@@ -293,6 +293,16 @@
         postMessage(Message.obtain(null, CLEAR_ALL));
     }
 
+    /**
+     * This class should not be instantiated directly, applications must only use
+     * {@link #getInstance()} to obtain the instance.
+     * Note this constructor was erroneously public and published in SDK levels prior to 16, but
+     * applications using it would receive a non-functional instance of this class (there was no
+     * way to call createHandler() and createUIHandler(), so it would not work).
+     * @hide
+     */
+    public GeolocationPermissions() {}
+
     // Native functions, run on the WebKit thread.
     private static native Set nativeGetOrigins();
     private static native boolean nativeGetAllowed(String origin);
diff --git a/core/java/android/webkit/JsPromptResult.java b/core/java/android/webkit/JsPromptResult.java
index 9fcd1bc..a1bf124 100644
--- a/core/java/android/webkit/JsPromptResult.java
+++ b/core/java/android/webkit/JsPromptResult.java
@@ -18,11 +18,11 @@
 
 
 /**
- * Public class for handling javascript prompt requests. A
- * JsDialogHandlerInterface implentation will receive a jsPrompt call with a
- * JsPromptResult parameter. This parameter is used to return a result to
- * WebView. The client can call cancel() to cancel the dialog or confirm() with
- * the user's input to confirm the dialog.
+ * Public class for handling JavaScript prompt requests. The WebChromeClient will receive a
+ * {@link WebChromeClient#onJsPrompt(WebView, String, String, String, JsPromptResult)} call with a
+ * JsPromptResult instance as a parameter. This parameter is used to return the result of this user
+ * dialog prompt back to the WebView instance. The client can call cancel() to cancel the dialog or
+ * confirm() with the user's input to confirm the dialog.
  */
 public class JsPromptResult extends JsResult {
     // String result of the prompt
@@ -36,17 +36,17 @@
         confirm();
     }
 
-    /*package*/ JsPromptResult(CallbackProxy proxy) {
-        super(proxy, /* unused */ false);
+    /**
+     * @hide Only for use by WebViewProvider implementations
+     */
+    public JsPromptResult(ResultReceiver receiver) {
+        super(receiver);
     }
 
-    /*package*/ String getStringResult() {
+    /**
+     * @hide Only for use by WebViewProvider implementations
+     */
+    public String getStringResult() {
         return mStringResult;
     }
-
-    @Override
-    /*package*/ void handleDefault() {
-        mStringResult = null;
-        super.handleDefault();
-    }
 }
diff --git a/core/java/android/webkit/JsResult.java b/core/java/android/webkit/JsResult.java
index e61ab21..07b686f 100644
--- a/core/java/android/webkit/JsResult.java
+++ b/core/java/android/webkit/JsResult.java
@@ -16,23 +16,27 @@
 
 package android.webkit;
 
-
+/**
+ * An instance of this class is passed as a parameter in various {@link WebChromeClient} action
+ * notifications. The object is used as a handle onto the underlying JavaScript-originated request,
+ * and provides a means for the client to indicate whether this action should proceed.
+ */
 public class JsResult {
-    // This prevents a user from interacting with the result before WebCore is
-    // ready to handle it.
-    private boolean mReady;
-    // Tells us if the user tried to confirm or cancel the result before WebCore
-    // is ready.
-    private boolean mTriedToNotifyBeforeReady;
     // This is a basic result of a confirm or prompt dialog.
     protected boolean mResult;
     /**
-     *  This is the caller of the prompt and is the object that is waiting.
-     *  @hide
+     * Callback interface, implemented by the WebViewProvider implementation to receive
+     * notifications when the JavaScript result represented by a JsResult instance has
+     * @hide Only for use by WebViewProvider implementations
      */
-    protected final CallbackProxy mProxy;
-    // This is the default value of the result.
-    private final boolean mDefaultValue;
+    public interface ResultReceiver {
+        public void onJsResultComplete(JsResult result);
+    }
+    /**
+     * This is the caller of the prompt and is the object that is waiting.
+     * @hide
+     */
+    protected final ResultReceiver mReceiver;
 
     /**
      * Handle the result if the user cancelled the dialog.
@@ -50,36 +54,22 @@
         wakeUp();
     }
 
-    /*package*/ JsResult(CallbackProxy proxy, boolean defaultVal) {
-        mProxy = proxy;
-        mDefaultValue = defaultVal;
+    /**
+     * @hide Only for use by WebViewProvider implementations
+     */
+    public JsResult(ResultReceiver receiver) {
+        mReceiver = receiver;
     }
 
-    /*package*/ final boolean getResult() {
+    /**
+     * @hide Only for use by WebViewProvider implementations
+     */
+    public final boolean getResult() {
         return mResult;
     }
 
-    /*package*/ final void setReady() {
-        mReady = true;
-        if (mTriedToNotifyBeforeReady) {
-            wakeUp();
-        }
-    }
-
-    /*package*/ void handleDefault() {
-        setReady();
-        mResult = mDefaultValue;
-        wakeUp();
-    }
-
-    /* Wake up the WebCore thread. */
+    /* Notify the caller that the JsResult has completed */
     protected final void wakeUp() {
-        if (mReady) {
-            synchronized (mProxy) {
-                mProxy.notify();
-            }
-        } else {
-            mTriedToNotifyBeforeReady = true;
-        }
+        mReceiver.onJsResultComplete(this);
     }
 }
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index a6ef0ce..5cb0d41 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -346,9 +346,11 @@
      *      onReceiveValue must be called to wake up the thread.a
      * @param acceptType The value of the 'accept' attribute of the input tag
      *         associated with this file picker.
+     * @param capture The value of the 'capture' attribute of the input tag
+     *         associated with this file picker.
      * @hide
      */
-    public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType) {
+    public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
         uploadFile.onReceiveValue(null);
     }
 
diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java
index 2300c2e..3745258 100644
--- a/core/java/android/webkit/WebStorage.java
+++ b/core/java/android/webkit/WebStorage.java
@@ -418,6 +418,16 @@
         }
     }
 
+    /**
+     * This class should not be instantiated directly, applications must only use
+     * {@link #getInstance()} to obtain the instance.
+     * Note this constructor was erroneously public and published in SDK levels prior to 16, but
+     * applications using it would receive a non-functional instance of this class (there was no
+     * way to call createHandler() and createUIHandler(), so it would not work).
+     * @hide
+     */
+    public WebStorage() {}
+
     // Native functions
     private static native Set nativeGetOrigins();
     private static native long nativeGetUsageForOrigin(String origin);
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 84632c6..5498622 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1281,8 +1281,10 @@
      * @deprecated {@link #findAllAsync} is preferred.
      * @see #setFindListener
      */
+    @Deprecated
     public int findAll(String find) {
         checkThread();
+        StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
         return mProvider.findAll(find);
     }
 
@@ -1539,6 +1541,7 @@
      *
      * @deprecated The built-in zoom mechanism is preferred, see
      *             {@link WebSettings#setBuiltInZoomControls(boolean)}.
+     * @hide since API version 16.
      */
     @Deprecated
     public View getZoomControls() {
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 5dc2681..504788e 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -828,6 +828,13 @@
     // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
     // the screen all-the-time. Good for profiling our drawing code
     static private final boolean AUTO_REDRAW_HACK = false;
+
+    // The rate at which edit text is scrolled in content pixels per millisecond
+    static private final float TEXT_SCROLL_RATE = 0.01f;
+
+    // The presumed scroll rate for the first scroll of edit text
+    static private final long TEXT_SCROLL_FIRST_SCROLL_MS = 16;
+
     // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
     private boolean mAutoRedraw;
 
@@ -853,6 +860,7 @@
     boolean mIsEditingText = false;
     ArrayList<Message> mBatchedTextChanges = new ArrayList<Message>();
     boolean mIsBatchingTextChanges = false;
+    private long mLastEditScroll = 0;
 
     private static class OnTrimMemoryListener implements ComponentCallbacks2 {
         private static OnTrimMemoryListener sInstance = null;
@@ -1037,9 +1045,6 @@
     // pages with the space bar, in pixels.
     private static final int PAGE_SCROLL_OVERLAP = 24;
 
-    // Time between successive calls to text scroll fling animation
-    private static final int TEXT_SCROLL_ANIMATION_DELAY_MS = 16;
-
     /**
      * These prevent calling requestLayout if either dimension is fixed. This
      * depends on the layout parameters and the measure specs.
@@ -1207,7 +1212,7 @@
     static final int RELOCATE_AUTO_COMPLETE_POPUP       = 146;
     static final int FOCUS_NODE_CHANGED                 = 147;
     static final int AUTOFILL_FORM                      = 148;
-    static final int ANIMATE_TEXT_SCROLL                = 149;
+    static final int SCROLL_EDIT_TEXT                   = 149;
     static final int EDIT_TEXT_SIZE_CHANGED             = 150;
     static final int SHOW_CARET_HANDLE                  = 151;
     static final int UPDATE_CONTENT_BOUNDS              = 152;
@@ -5049,8 +5054,8 @@
      *
      * debug only
      */
-    public void useMockDeviceOrientation() {
-        mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION);
+    public void setUseMockDeviceOrientation() {
+        mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_DEVICE_ORIENTATION);
     }
 
     /**
@@ -6002,9 +6007,9 @@
                     data.mNativeLayer = nativeScrollableLayer(
                             contentX, contentY, data.mNativeLayerRect, null);
                     data.mSlop = viewToContentDimension(mNavSlop);
-                    mTouchHighlightRegion.setEmpty();
+                    removeTouchHighlight();
                     if (!mBlockWebkitViewMessages) {
-                        mTouchHighlightRequested = System.currentTimeMillis();
+                        mTouchHighlightRequested = SystemClock.uptimeMillis();
                         mWebViewCore.sendMessageAtFrontOfQueue(
                                 EventHub.HIT_TEST, data);
                     }
@@ -6091,6 +6096,11 @@
                                 mSelectDraggingTextQuad.containsPoint(handleX, handleY);
                         boolean inEditBounds = mEditTextContentBounds
                                 .contains(handleX, handleY);
+                        if (mIsEditingText && !inEditBounds) {
+                            beginScrollEdit();
+                        } else {
+                            endScrollEdit();
+                        }
                         if (inCursorText || (mIsEditingText && !inEditBounds)) {
                             snapDraggingCursor();
                         }
@@ -6240,6 +6250,7 @@
                 break;
             }
             case MotionEvent.ACTION_UP: {
+                endScrollEdit();
                 if (!mConfirmMove && mIsEditingText && mSelectionStarted &&
                         mIsCaretSelection) {
                     showPasteWindow();
@@ -6335,6 +6346,86 @@
         }
     }
 
+    /**
+     * Returns the text scroll speed in content pixels per millisecond based on
+     * the touch location.
+     * @param coordinate The x or y touch coordinate in content space
+     * @param min The minimum coordinate (x or y) of the edit content bounds
+     * @param max The maximum coordinate (x or y) of the edit content bounds
+     */
+    private static float getTextScrollSpeed(int coordinate, int min, int max) {
+        if (coordinate < min) {
+            return (coordinate - min) * TEXT_SCROLL_RATE;
+        } else if (coordinate >= max) {
+            return (coordinate - max + 1) * TEXT_SCROLL_RATE;
+        } else {
+            return 0.0f;
+        }
+    }
+
+    private void beginScrollEdit() {
+        if (mLastEditScroll == 0) {
+            mLastEditScroll = SystemClock.uptimeMillis() -
+                    TEXT_SCROLL_FIRST_SCROLL_MS;
+            scrollEditWithCursor();
+        }
+    }
+
+    private void endScrollEdit() {
+        mLastEditScroll = 0;
+    }
+
+    private static int getTextScrollDelta(float speed, long deltaT) {
+        float distance = speed * deltaT;
+        int intDistance = (int)Math.floor(distance);
+        float probability = distance - intDistance;
+        if (Math.random() < probability) {
+            intDistance++;
+        }
+        return intDistance;
+    }
+    /**
+     * Scrolls edit text a distance based on the last touch point,
+     * the last scroll time, and the edit text content bounds.
+     */
+    private void scrollEditWithCursor() {
+        if (mLastEditScroll != 0) {
+            int x = viewToContentX(mLastTouchX + getScrollX() + mSelectDraggingOffset.x);
+            float scrollSpeedX = getTextScrollSpeed(x, mEditTextContentBounds.left,
+                    mEditTextContentBounds.right);
+            int y = viewToContentY(mLastTouchY + getScrollY() + mSelectDraggingOffset.y);
+            float scrollSpeedY = getTextScrollSpeed(y, mEditTextContentBounds.top,
+                    mEditTextContentBounds.bottom);
+            if (scrollSpeedX == 0.0f && scrollSpeedY == 0.0f) {
+                endScrollEdit();
+            } else {
+                long currentTime = SystemClock.uptimeMillis();
+                long timeSinceLastUpdate = currentTime - mLastEditScroll;
+                int deltaX = getTextScrollDelta(scrollSpeedX, timeSinceLastUpdate);
+                int deltaY = getTextScrollDelta(scrollSpeedY, timeSinceLastUpdate);
+                mLastEditScroll = currentTime;
+                if (deltaX == 0 && deltaY == 0) {
+                    // By probability no text scroll this time. Try again later.
+                    mPrivateHandler.sendEmptyMessageDelayed(SCROLL_EDIT_TEXT,
+                            TEXT_SCROLL_FIRST_SCROLL_MS);
+                } else {
+                    int scrollX = getTextScrollX() + deltaX;
+                    scrollX = Math.min(getMaxTextScrollX(), scrollX);
+                    scrollX = Math.max(0, scrollX);
+                    int scrollY = getTextScrollY() + deltaY;
+                    scrollY = Math.min(getMaxTextScrollY(), scrollY);
+                    scrollY = Math.max(0, scrollY);
+                    scrollEditText(scrollX, scrollY);
+                    int cursorX = mSelectDraggingCursor.x;
+                    int cursorY = mSelectDraggingCursor.y;
+                    mSelectDraggingCursor.set(x - deltaX, y - deltaY);
+                    updateWebkitSelection();
+                    mSelectDraggingCursor.set(cursorX, cursorY);
+                }
+            }
+        }
+    }
+
     private void startTouch(float x, float y, long eventTime) {
         // Remember where the motion event started
         mStartTouchX = mLastTouchX = Math.round(x);
@@ -7053,7 +7144,8 @@
         if (mFindIsUp) return false;
         boolean result = false;
         result = mWebViewPrivate.super_requestFocus(direction, previouslyFocusedRect);
-        if (mWebViewCore.getSettings().getNeedInitialFocus() && !mWebView.isInTouchMode()) {
+        if (mWebViewCore.getSettings().getNeedInitialFocus()
+                && !mWebView.isInTouchMode()) {
             // For cases such as GMail, where we gain focus from a direction,
             // we want to move to the first available link.
             // FIXME: If there are no visible links, we may not want to
@@ -7074,7 +7166,7 @@
                 default:
                     return result;
             }
-            // TODO: Send initial focus request to webkit (b/6108927)
+            mWebViewCore.sendMessage(EventHub.SET_INITIAL_FOCUS, fakeKeyDirection);
         }
         return result;
     }
@@ -7673,10 +7765,6 @@
                             msg.arg1, /* unused */0);
                     break;
 
-                case ANIMATE_TEXT_SCROLL:
-                    computeEditTextScroll();
-                    break;
-
                 case EDIT_TEXT_SIZE_CHANGED:
                     if (msg.arg1 == mFieldPointer) {
                         mEditTextContent.set((Rect)msg.obj);
@@ -7695,6 +7783,10 @@
                     mEditTextContentBounds.set((Rect) msg.obj);
                     break;
 
+                case SCROLL_EDIT_TEXT:
+                    scrollEditWithCursor();
+                    break;
+
                 default:
                     super.handleMessage(msg);
                     break;
@@ -7777,13 +7869,16 @@
         if (mFocusedNode.mHasFocus && mFocusedNode.mEditable) {
             return false;
         }
-        long delay = System.currentTimeMillis() - mTouchHighlightRequested;
+        long delay = SystemClock.uptimeMillis() - mTouchHighlightRequested;
         if (delay < ViewConfiguration.getTapTimeout()) {
             Rect r = mTouchHighlightRegion.getBounds();
             mWebView.postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom);
             return false;
         }
-        return true;
+        if (mInputDispatcher == null) {
+            return false;
+        }
+        return mInputDispatcher.shouldShowTapHighlight();
     }
 
 
@@ -7893,8 +7988,8 @@
                 if (viewRect.width() < getWidth() >> 1
                         || viewRect.height() < getHeight() >> 1) {
                     mTouchHighlightRegion.union(viewRect);
-                } else {
-                    Log.w(LOGTAG, "Skip the huge selection rect:"
+                } else if (DebugFlags.WEB_VIEW) {
+                    Log.d(LOGTAG, "Skip the huge selection rect:"
                             + viewRect);
                 }
             }
@@ -7995,16 +8090,17 @@
             mWebView.invalidate();
         }
 
-        if (mPictureListener != null) {
-            mPictureListener.onNewPicture(getWebView(), capturePicture());
-        }
-
         // update the zoom information based on the new picture
         mZoomManager.onNewPicture(draw);
 
         if (isPictureAfterFirstLayout) {
             mViewManager.postReadyToDrawAll();
         }
+        scrollEditWithCursor();
+
+        if (mPictureListener != null) {
+            mPictureListener.onNewPicture(getWebView(), capturePicture());
+        }
     }
 
     /**
@@ -8047,13 +8143,6 @@
         invalidate();
     }
 
-    private void computeEditTextScroll() {
-        if (mEditTextScroller.computeScrollOffset()) {
-            scrollEditText(mEditTextScroller.getCurrX(),
-                    mEditTextScroller.getCurrY());
-        }
-    }
-
     private void scrollEditText(int scrollX, int scrollY) {
         // Scrollable edit text. Scroll it.
         float maxScrollX = getMaxTextScrollX();
@@ -8061,8 +8150,6 @@
         mEditTextContent.offsetTo(-scrollX, -scrollY);
         mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SCROLL_TEXT_INPUT, 0,
                 scrollY, (Float)scrollPercentX);
-        mPrivateHandler.sendEmptyMessageDelayed(ANIMATE_TEXT_SCROLL,
-                TEXT_SCROLL_ANIMATION_DELAY_MS);
     }
 
     private void beginTextBatch() {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 6390ffe..7652417 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -394,10 +394,12 @@
      * Called by JNI.  Open a file chooser to upload a file.
      * @param acceptType The value of the 'accept' attribute of the
      *         input tag associated with this file picker.
+     * @param capture The value of the 'capture' attribute of the
+     *         input tag associated with this file picker.
      * @return String version of the URI.
      */
-    private String openFileChooser(String acceptType) {
-        Uri uri = mCallbackProxy.openFileChooser(acceptType);
+    private String openFileChooser(String acceptType, String capture) {
+        Uri uri = mCallbackProxy.openFileChooser(acceptType, capture);
         if (uri != null) {
             String filePath = "";
             // Note - querying for MediaStore.Images.Media.DATA
@@ -616,9 +618,6 @@
             int unichar, int repeatCount, boolean isShift, boolean isAlt,
             boolean isSym, boolean isDown);
 
-    private native void nativeClick(int nativeClass, int framePtr, int nodePtr,
-            boolean fake);
-
     private native void nativeSendListBoxChoices(int nativeClass,
             boolean[] choices, int size);
 
@@ -661,8 +660,6 @@
             int x, int y);
     private native String nativeRetrieveImageSource(int nativeClass,
             int x, int y);
-    private native void nativeTouchUp(int nativeClass,
-            int touchGeneration, int framePtr, int nodePtr, int x, int y);
     private native boolean nativeMouseClick(int nativeClass);
 
     private native boolean nativeHandleTouchEvent(int nativeClass, int action,
@@ -1073,10 +1070,8 @@
         static final int REPLACE_TEXT = 114;
         static final int PASS_TO_JS = 115;
         static final int SET_GLOBAL_BOUNDS = 116;
-        static final int CLICK = 118;
         static final int SET_NETWORK_STATE = 119;
         static final int DOC_HAS_IMAGES = 120;
-        static final int FAKE_CLICK = 121;
         static final int DELETE_SELECTION = 122;
         static final int LISTBOX_CHOICES = 123;
         static final int SINGLE_LISTBOX_CHOICE = 124;
@@ -1145,7 +1140,7 @@
         // accessibility support
         static final int MODIFY_SELECTION = 190;
 
-        static final int USE_MOCK_DEVICE_ORIENTATION = 191;
+        static final int SET_USE_MOCK_DEVICE_ORIENTATION = 191;
 
         static final int AUTOFILL_FORM = 192;
 
@@ -1181,6 +1176,7 @@
 
         // key was pressed (down and up)
         static final int KEY_PRESS = 223;
+        static final int SET_INITIAL_FOCUS = 224;
 
         // Private handler for WebCore messages.
         private Handler mHandler;
@@ -1371,14 +1367,6 @@
                             keyPress(msg.arg1);
                             break;
 
-                        case FAKE_CLICK:
-                            nativeClick(mNativeClass, msg.arg1, msg.arg2, true);
-                            break;
-
-                        case CLICK:
-                            nativeClick(mNativeClass, msg.arg1, msg.arg2, false);
-                            break;
-
                         case VIEW_SIZE_CHANGED: {
                             viewSizeChanged((WebViewClassic.ViewSizeData) msg.obj);
                             break;
@@ -1665,8 +1653,8 @@
                                     .sendToTarget();
                             break;
 
-                        case USE_MOCK_DEVICE_ORIENTATION:
-                            useMockDeviceOrientation();
+                        case SET_USE_MOCK_DEVICE_ORIENTATION:
+                            setUseMockDeviceOrientation();
                             break;
 
                         case AUTOFILL_FORM:
@@ -1761,6 +1749,9 @@
                                     WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget();
                             break;
                         }
+                        case SET_INITIAL_FOCUS:
+                            nativeSetInitialFocus(mNativeClass, msg.arg1);
+                            break;
                     }
                 }
             };
@@ -3008,8 +2999,8 @@
         // TODO: Figure out what to do with this (b/6111818)
     }
 
-    private void useMockDeviceOrientation() {
-        mDeviceMotionAndOrientationManager.useMock();
+    private void setUseMockDeviceOrientation() {
+        mDeviceMotionAndOrientationManager.setUseMock();
     }
 
     public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
@@ -3084,6 +3075,7 @@
     private native void nativeClearTextSelection(int nativeClass);
     private native boolean nativeSelectWordAt(int nativeClass, int x, int y);
     private native void nativeSelectAll(int nativeClass);
+    private native void nativeSetInitialFocus(int nativeClass, int keyDirection);
 
     private static native void nativeCertTrustChanged();
 }
diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java
index e7024d9..0a0afaa 100644
--- a/core/java/android/webkit/WebViewInputDispatcher.java
+++ b/core/java/android/webkit/WebViewInputDispatcher.java
@@ -269,9 +269,8 @@
      */
     public boolean postPointerEvent(MotionEvent event,
             int webKitXOffset, int webKitYOffset, float webKitScale) {
-        if (event == null
-                || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
-            throw new IllegalArgumentException("event must be a pointer event");
+        if (event == null) {
+            throw new IllegalArgumentException("event cannot be null");
         }
 
         if (DEBUG) {
@@ -349,6 +348,12 @@
         }
     }
 
+    public boolean shouldShowTapHighlight() {
+        synchronized (mLock) {
+            return mPostLongPressScheduled || mPostClickScheduled;
+        }
+    }
+
     private void postLongPress() {
         synchronized (mLock) {
             if (!mPostLongPressScheduled) {
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index 0685eea..858c415 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -21,8 +21,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
-import com.android.internal.R;
-
 
 /**
  * <p>
@@ -71,16 +69,6 @@
     }
 
     @Override
-    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
-        super.onPopulateAccessibilityEvent(event);
-        if (isChecked()) {
-            event.getText().add(mContext.getString(R.string.checkbox_checked));
-        } else {
-            event.getText().add(mContext.getString(R.string.checkbox_not_checked));
-        }
-    }
-
-    @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
         event.setClassName(CheckBox.class.getName());
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index d897a39..b2321d9 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -579,7 +579,7 @@
             throw new IllegalArgumentException("minWidth > maxWidth");
         }
 
-        mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
+        mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);
 
         attributesArray.recycle();
 
@@ -771,6 +771,8 @@
                 mLastDownEventTime = event.getEventTime();
                 mIngonreMoveEvents = false;
                 mShowSoftInputOnTap = false;
+                // Make sure we wupport flinging inside scrollables.
+                getParent().requestDisallowInterceptTouchEvent(true);
                 if (!mFlingScroller.isFinished()) {
                     mFlingScroller.forceFinished(true);
                     mAdjustScroller.forceFinished(true);
@@ -1096,12 +1098,7 @@
      * @see #setMaxValue(int)
      */
     public void setValue(int value) {
-        if (mValue == value) {
-            return;
-        }
         setValueInternal(value, false);
-        initializeSelectorWheelIndices();
-        invalidate();
     }
 
     /**
@@ -1498,6 +1495,8 @@
         if (notifyChange) {
             notifyChange(previous, current);
         }
+        initializeSelectorWheelIndices();
+        invalidate();
     }
 
     /**
diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java
index 22e9ef1..080b87d 100644
--- a/core/java/android/widget/ShareActionProvider.java
+++ b/core/java/android/widget/ShareActionProvider.java
@@ -80,16 +80,22 @@
 
         /**
          * Called when a share target has been selected. The client can
-         * decide whether to handle the intent or rely on the default
-         * behavior which is launching it.
+         * decide whether to perform some action before the sharing is
+         * actually performed.
          * <p>
          * <strong>Note:</strong> Modifying the intent is not permitted and
          *     any changes to the latter will be ignored.
          * </p>
+         * <p>
+         * <strong>Note:</strong> You should <strong>not</strong> handle the
+         *     intent here. This callback aims to notify the client that a
+         *     sharing is being performed, so the client can update the UI
+         *     if necessary.
+         * </p>
          *
          * @param source The source of the notification.
          * @param intent The intent for launching the chosen share target.
-         * @return Whether the client has handled the intent.
+         * @return The return result is ignored. Always return false for consistency.
          */
         public boolean onShareTargetSelected(ShareActionProvider source, Intent intent);
     }
@@ -308,7 +314,7 @@
         @Override
         public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
             if (mOnShareTargetSelectedListener != null) {
-                return mOnShareTargetSelectedListener.onShareTargetSelected(
+                mOnShareTargetSelectedListener.onShareTargetSelected(
                         ShareActionProvider.this, intent);
             }
             return false;
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index aef8a34..36d1ee0 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -907,7 +907,7 @@
                 public void onItemClick(AdapterView parent, View v, int position, long id) {
                     Spinner.this.setSelection(position);
                     if (mOnItemClickListener != null) {
-                        Spinner.this.performItemClick(null, position, mAdapter.getItemId(position));
+                        Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
                     }
                     dismiss();
                 }
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index a897cc3..0786909 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -806,5 +806,16 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         info.setClassName(Switch.class.getName());
+        CharSequence switchText = isChecked() ? mTextOn : mTextOff;
+        if (!TextUtils.isEmpty(switchText)) {
+            CharSequence oldText = info.getText();
+            if (TextUtils.isEmpty(oldText)) {
+                info.setText(switchText);
+            } else {
+                StringBuilder newText = new StringBuilder();
+                newText.append(oldText).append(' ').append(switchText);
+                info.setText(newText);
+            }
+        }
     }
 }
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 3dd2284..699e9b30 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -46,6 +46,10 @@
         mCurrent = mBuilder.toString();
     }
 
+    public void printPair(String key, Object value) {
+        print(key + "=" + String.valueOf(value) + " ");
+    }
+
     @Override
     public void println() {
         super.println();
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 0524c25..1d6af90 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -21,6 +21,8 @@
 import android.graphics.Paint;
 import android.graphics.RectF;
 import android.graphics.Paint.FontMetricsInt;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManager.InputDeviceListener;
 import android.util.Log;
 import android.view.InputDevice;
 import android.view.KeyEvent;
@@ -32,7 +34,7 @@
 
 import java.util.ArrayList;
 
-public class PointerLocationView extends View {
+public class PointerLocationView extends View implements InputDeviceListener {
     private static final String TAG = "Pointer";
     
     public static class PointerState {
@@ -82,6 +84,8 @@
     private final int ESTIMATE_FUTURE_POINTS = 2;
     private final float ESTIMATE_INTERVAL = 0.02f;
 
+    private final InputManager mIm;
+
     private final ViewConfiguration mVC;
     private final Paint mTextPaint;
     private final Paint mTextBackgroundPaint;
@@ -108,6 +112,8 @@
         super(c);
         setFocusableInTouchMode(true);
 
+        mIm = (InputManager)c.getSystemService(Context.INPUT_SERVICE);
+
         mVC = ViewConfiguration.get(c);
         mTextPaint = new Paint();
         mTextPaint.setAntiAlias(true);
@@ -139,18 +145,6 @@
         mActivePointerId = 0;
         
         mVelocity = VelocityTracker.obtain();
-        
-        logInputDeviceCapabilities();
-    }
-    
-    private void logInputDeviceCapabilities() {
-        int[] deviceIds = InputDevice.getDeviceIds();
-        for (int i = 0; i < deviceIds.length; i++) {
-            InputDevice device = InputDevice.getDevice(deviceIds[i]);
-            if (device != null) {
-                Log.i(TAG, device.toString());
-            }
-        }
     }
 
     public void setPrintCoords(boolean state) {
@@ -631,7 +625,53 @@
         logMotionEvent("Trackball", event);
         return true;
     }
-    
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mIm.registerInputDeviceListener(this, getHandler());
+        logInputDevices();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        mIm.unregisterInputDeviceListener(this);
+    }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        logInputDeviceState(deviceId, "Device Added");
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        logInputDeviceState(deviceId, "Device Changed");
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        logInputDeviceState(deviceId, "Device Removed");
+    }
+
+    private void logInputDevices() {
+        int[] deviceIds = InputDevice.getDeviceIds();
+        for (int i = 0; i < deviceIds.length; i++) {
+            logInputDeviceState(deviceIds[i], "Device Enumerated");
+        }
+    }
+
+    private void logInputDeviceState(int deviceId, String state) {
+        InputDevice device = mIm.getInputDevice(deviceId);
+        if (device != null) {
+            Log.i(TAG, state + ": " + device);
+        } else {
+            Log.i(TAG, state + ": " + deviceId);
+        }
+    }
+
     // HACK
     // A quick and dirty string builder implementation optimized for GC.
     // Using String.format causes the application grind to a halt when
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 879b9d2..b877071 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -549,6 +549,10 @@
     opt.optionString = heapsizeOptsBuf;
     mOptions.add(opt);
 
+    // Increase the main thread's interpreter stack size for bug 6315322.
+    opt.optionString = "-XX:mainThreadStackSize=24K";
+    mOptions.add(opt);
+
     strcpy(heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit=");
     property_get("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf+20, "");
     if (heapgrowthlimitOptsBuf[20] != '\0') {
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 376c841..a65262c 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -254,6 +254,13 @@
         obj->setTextAlign(align);
     }
 
+    static void setTextLocale(JNIEnv* env, jobject clazz, SkPaint* obj, jstring locale) {
+        const char* localeArray = env->GetStringUTFChars(locale, NULL);
+        SkString skLocale(localeArray);
+        obj->setTextLocale(skLocale);
+        env->ReleaseStringUTFChars(locale, localeArray);
+    }
+
     static jfloat getTextSize(JNIEnv* env, jobject paint) {
         NPE_CHECK_RETURN_ZERO(env, paint);
         return SkScalarToFloat(GraphicsJNI::getNativePaint(env, paint)->getTextSize());
@@ -817,6 +824,7 @@
     {"native_setRasterizer","(II)I", (void*) SkPaintGlue::setRasterizer},
     {"native_getTextAlign","(I)I", (void*) SkPaintGlue::getTextAlign},
     {"native_setTextAlign","(II)V", (void*) SkPaintGlue::setTextAlign},
+    {"native_setTextLocale","(ILjava/lang/String;)V", (void*) SkPaintGlue::setTextLocale},
     {"getTextSize","()F", (void*) SkPaintGlue::getTextSize},
     {"setTextSize","(F)V", (void*) SkPaintGlue::setTextSize},
     {"getTextScaleX","()F", (void*) SkPaintGlue::getTextScaleX},
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index a7abbfa..a97c710 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -29,10 +29,7 @@
 namespace android {
 
 //--------------------------------------------------------------------------------------------------
-// Using DroidSansArabic for shaping Arabic with Harfbuzz because its metrics are more compatible
-// with the "Roboto" metrics (compared to DroidNaskh-Regular). When we will have an Arabic font
-// whose metrics are similar to the Roboto ones, then we will need to use it for shaping.
-#define TYPEFACE_ARABIC "/system/fonts/DroidSansArabic.ttf"
+#define TYPEFACE_ARABIC "/system/fonts/DroidNaskh-Regular-Shift.ttf"
 #define TYPE_FACE_HEBREW_REGULAR "/system/fonts/DroidSansHebrew-Regular.ttf"
 #define TYPE_FACE_HEBREW_BOLD "/system/fonts/DroidSansHebrew-Bold.ttf"
 #define TYPEFACE_BENGALI "/system/fonts/Lohit-Bengali.ttf"
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index c5ff16e..e7c4c23 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -27,6 +27,7 @@
 
 #define WIFI_PKG_NAME "android/net/wifi/WifiNative"
 #define BUF_SIZE 256
+#define EVENT_BUF_SIZE 2048
 
 namespace android {
 
@@ -140,7 +141,7 @@
 
 static jstring android_net_wifi_waitForEvent(JNIEnv* env, jobject, jstring jIface)
 {
-    char buf[BUF_SIZE];
+    char buf[EVENT_BUF_SIZE];
     ScopedUtfChars ifname(env, jIface);
     int nread = ::wifi_wait_for_event(ifname.c_str(), buf, sizeof buf);
     if (nread > 0) {
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index d7d476a..e8a3a3b 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -54,8 +54,9 @@
     }
 
     ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz,
-            gInputDeviceClassInfo.ctor, deviceInfo.getId(), nameObj.get(),
-            descriptorObj.get(), deviceInfo.getSources(), deviceInfo.getKeyboardType(),
+            gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(),
+            nameObj.get(), descriptorObj.get(),
+            deviceInfo.getSources(), deviceInfo.getKeyboardType(),
             kcmObj.get()));
 
     const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
@@ -86,7 +87,7 @@
     gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz));
 
     GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz,
-            "<init>", "(ILjava/lang/String;Ljava/lang/String;IILandroid/view/KeyCharacterMap;)V");
+            "<init>", "(IILjava/lang/String;Ljava/lang/String;IILandroid/view/KeyCharacterMap;)V");
 
     GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz,
             "addMotionRange", "(IIFFFF)V");
diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index 4f58be4..6b06456 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
-** Copyright 2007, The Android Open Source Project
+** Copyright 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.
@@ -18,7 +18,7 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:detachWallpaper="true" android:shareInterpolator="false">
+    android:background="#ff000000" android:shareInterpolator="false">
     <scale
         android:fromXScale="0.95" android:toXScale="1.0"
         android:fromYScale="0.95" android:toYScale="1.0"
diff --git a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml b/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
new file mode 100644
index 0000000..a354fae
--- /dev/null
+++ b/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:detachWallpaper="true" android:shareInterpolator="false">
+    <scale
+        android:fromXScale="0.95" android:toXScale="1.0"
+        android:fromYScale="0.95" android:toYScale="1.0"
+        android:pivotX="50%p" android:pivotY="50%p"
+        android:fillEnabled="true" android:fillBefore="true"
+        android:interpolator="@interpolator/decelerate_cubic"
+        android:startOffset="@android:integer/config_shortAnimTime"
+        android:duration="@android:integer/config_shortAnimTime" />
+    <alpha
+        android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:fillEnabled="true" android:fillBefore="true"
+        android:interpolator="@interpolator/decelerate_quad"
+        android:startOffset="@android:integer/config_shortAnimTime"
+        android:duration="@android:integer/config_shortAnimTime"/>
+</set>
diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml
index e2f3ce2..688a8d5 100644
--- a/core/res/res/anim/screen_rotate_180_enter.xml
+++ b/core/res/res/anim/screen_rotate_180_enter.xml
@@ -24,5 +24,5 @@
             android:interpolator="@interpolator/decelerate_quint"
             android:fillEnabled="true"
             android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_longAnimTime" />
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml
index fe4a950..1eb6361 100644
--- a/core/res/res/anim/screen_rotate_180_exit.xml
+++ b/core/res/res/anim/screen_rotate_180_exit.xml
@@ -24,5 +24,8 @@
             android:interpolator="@interpolator/decelerate_quint"
             android:fillEnabled="true"
             android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_longAnimTime" />
+            android:duration="@android:integer/config_mediumAnimTime" />
+    <alpha android:fromAlpha="1.0" android:toAlpha="0"
+            android:interpolator="@interpolator/decelerate_cubic"
+            android:duration="@android:integer/config_mediumAnimTime"/>
 </set>
\ No newline at end of file
diff --git a/core/res/res/anim/screen_rotate_180_frame.xml b/core/res/res/anim/screen_rotate_180_frame.xml
index 1a3ee67..19dade1 100644
--- a/core/res/res/anim/screen_rotate_180_frame.xml
+++ b/core/res/res/anim/screen_rotate_180_frame.xml
@@ -24,5 +24,5 @@
             android:interpolator="@interpolator/decelerate_quint"
             android:fillEnabled="true"
             android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_longAnimTime" />
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml
index 38a674d..b16d5fc 100644
--- a/core/res/res/anim/screen_rotate_minus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml
@@ -19,10 +19,18 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:shareInterpolator="false">
+    <!-- Version for two-phase anim
     <rotate android:fromDegrees="-90" android:toDegrees="0"
             android:pivotX="50%" android:pivotY="50%"
             android:interpolator="@interpolator/decelerate_quint"
             android:fillEnabled="true"
             android:fillBefore="true" android:fillAfter="true"
             android:duration="@android:integer/config_longAnimTime" />
+    -->
+    <rotate android:fromDegrees="-90" android:toDegrees="0"
+            android:pivotX="50%" android:pivotY="50%"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml
index a75aca7..9b38939 100644
--- a/core/res/res/anim/screen_rotate_minus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml
@@ -19,10 +19,30 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:shareInterpolator="false">
+    <!-- Version for two-phase animation
     <rotate android:fromDegrees="0" android:toDegrees="90"
             android:pivotX="50%" android:pivotY="50%"
             android:interpolator="@interpolator/decelerate_quint"
             android:fillEnabled="true"
             android:fillBefore="true" android:fillAfter="true"
             android:duration="@android:integer/config_longAnimTime" />
+    -->
+    <scale android:fromXScale="100%" android:toXScale="100%p"
+            android:fromYScale="100%" android:toYScale="100%p"
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
+    <rotate android:fromDegrees="0" android:toDegrees="90"
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
+    <alpha android:fromAlpha="1.0" android:toAlpha="0"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml
index 583d2ba..86a8d24 100644
--- a/core/res/res/anim/screen_rotate_plus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml
@@ -19,10 +19,18 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:shareInterpolator="false">
+    <!-- Version for two-phase animation
     <rotate android:fromDegrees="90" android:toDegrees="0"
             android:pivotX="50%" android:pivotY="50%"
             android:interpolator="@interpolator/decelerate_quint"
             android:fillEnabled="true"
             android:fillBefore="true" android:fillAfter="true"
             android:duration="@android:integer/config_longAnimTime" />
+    -->
+    <rotate android:fromDegrees="90" android:toDegrees="0"
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml
index a2bef41..fa34533 100644
--- a/core/res/res/anim/screen_rotate_plus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml
@@ -19,10 +19,30 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:shareInterpolator="false">
+    <!-- Version for two-phase animation
     <rotate android:fromDegrees="0" android:toDegrees="-90"
             android:pivotX="50%" android:pivotY="50%"
             android:interpolator="@interpolator/decelerate_quint"
             android:fillEnabled="true"
             android:fillBefore="true" android:fillAfter="true"
             android:duration="@android:integer/config_longAnimTime" />
+    -->
+    <scale android:fromXScale="100%" android:toXScale="100%p"
+            android:fromYScale="100%" android:toYScale="100%p"
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
+    <rotate android:fromDegrees="0" android:toDegrees="-90"
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
+    <alpha android:fromAlpha="1.0" android:toAlpha="0"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index aaef701..2006548 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -400,8 +400,6 @@
   <java-symbol type="string" name="cfTemplateNotForwarded" />
   <java-symbol type="string" name="cfTemplateRegistered" />
   <java-symbol type="string" name="cfTemplateRegisteredTime" />
-  <java-symbol type="string" name="checkbox_checked" />
-  <java-symbol type="string" name="checkbox_not_checked" />
   <java-symbol type="string" name="chooseActivity" />
   <java-symbol type="string" name="config_default_dns_server" />
   <java-symbol type="string" name="config_ethernet_iface_regex" />
@@ -701,8 +699,6 @@
   <java-symbol type="string" name="preposition_for_time" />
   <java-symbol type="string" name="progress_erasing" />
   <java-symbol type="string" name="progress_unmounting" />
-  <java-symbol type="string" name="radiobutton_not_selected" />
-  <java-symbol type="string" name="radiobutton_selected" />
   <java-symbol type="string" name="relationTypeAssistant" />
   <java-symbol type="string" name="relationTypeBrother" />
   <java-symbol type="string" name="relationTypeChild" />
@@ -791,14 +787,18 @@
   <java-symbol type="string" name="sipAddressTypeHome" />
   <java-symbol type="string" name="sipAddressTypeOther" />
   <java-symbol type="string" name="sipAddressTypeWork" />
-  <java-symbol type="string" name="sms_control_default_app_name" />
   <java-symbol type="string" name="sms_control_message" />
-  <java-symbol type="string" name="sms_control_no" />
   <java-symbol type="string" name="sms_control_title" />
+  <java-symbol type="string" name="sms_control_no" />
   <java-symbol type="string" name="sms_control_yes" />
+  <java-symbol type="string" name="sms_premium_short_code_confirm_message" />
+  <java-symbol type="string" name="sms_premium_short_code_confirm_title" />
+  <java-symbol type="string" name="sms_short_code_confirm_allow" />
+  <java-symbol type="string" name="sms_short_code_confirm_deny" />
+  <java-symbol type="string" name="sms_short_code_confirm_message" />
+  <java-symbol type="string" name="sms_short_code_confirm_report" />
+  <java-symbol type="string" name="sms_short_code_confirm_title" />
   <java-symbol type="string" name="submit" />
-  <java-symbol type="string" name="switch_off" />
-  <java-symbol type="string" name="switch_on" />
   <java-symbol type="string" name="sync_binding_label" />
   <java-symbol type="string" name="sync_do_nothing" />
   <java-symbol type="string" name="sync_really_delete" />
@@ -820,8 +820,6 @@
   <java-symbol type="string" name="time_wday" />
   <java-symbol type="string" name="time_wday_date" />
   <java-symbol type="string" name="today" />
-  <java-symbol type="string" name="togglebutton_not_pressed" />
-  <java-symbol type="string" name="togglebutton_pressed" />
   <java-symbol type="string" name="tomorrow" />
   <java-symbol type="string" name="twelve_hour_time_format" />
   <java-symbol type="string" name="twenty_four_hour_time_format" />
@@ -1103,6 +1101,7 @@
   <java-symbol type="xml" name="password_kbd_symbols_shift" />
   <java-symbol type="xml" name="power_profile" />
   <java-symbol type="xml" name="time_zones_by_country" />
+  <java-symbol type="xml" name="sms_short_codes" />
 
   <java-symbol type="raw" name="incognito_mode_start_page" />
   <java-symbol type="raw" name="loaderror" />
@@ -1133,6 +1132,7 @@
   <!-- From android.policy -->
   <java-symbol type="anim" name="app_starting_exit" />
   <java-symbol type="anim" name="lock_screen_behind_enter" />
+  <java-symbol type="anim" name="lock_screen_wallpaper_behind_enter" />
   <java-symbol type="anim" name="dock_top_enter" />
   <java-symbol type="anim" name="dock_top_exit" />
   <java-symbol type="anim" name="dock_bottom_enter" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0eb46bd..e00986c 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2789,16 +2789,30 @@
     <string name="select_character">Insert character</string>
 
     <!-- SMS per-application rate control Dialog --> <skip />
-    <!-- See SMS_DIALOG.  This is shown if the current application's name cannot be figuerd out. -->
-    <string name="sms_control_default_app_name">Unknown app</string>
     <!-- SMS_DIALOG: An SMS dialog is shown if an application tries to send too many SMSes.  This is the title of that dialog. -->
     <string name="sms_control_title">Sending SMS messages</string>
-    <!-- See SMS_DIALOG.  This is the message shown in that dialog. -->
-    <string name="sms_control_message">A large number of SMS messages are being sent. Touch OK to continue, or Cancel to stop sending.</string>
-    <!-- See SMS_DIALOG.  This is a button choice to allow sending the SMSes. -->
-    <string name="sms_control_yes">OK</string>
-    <!-- See SMS_DIALOG.  This is a button choice to disallow sending the SMSes.. -->
-    <string name="sms_control_no">Cancel</string>
+    <!-- See SMS_DIALOG.  This is the message shown in that dialog. [CHAR LIMIT=NONE] -->
+    <string name="sms_control_message">&lt;b><xliff:g id="app_name">%1$s</xliff:g>&lt;/b> is sending a large number of SMS messages. Do you want to allow this app to continue sending messages?</string>
+    <!-- See SMS_DIALOG.  This is a button choice to allow sending the SMSes. [CHAR LIMIT=30] -->
+    <string name="sms_control_yes">Allow</string>
+    <!-- See SMS_DIALOG.  This is a button choice to disallow sending the SMSes. [CHAR LIMIT=30] -->
+    <string name="sms_control_no">Deny</string>
+
+    <!-- SMS short code verification dialog. --> <skip />
+    <!-- The dialog title for the SMS short code confirmation dialog. [CHAR LIMIT=30] -->
+    <string name="sms_short_code_confirm_title">Send SMS to short code?</string>
+    <!-- The dialog title for the SMS premium short code confirmation dialog. [CHAR LIMIT=30] -->
+    <string name="sms_premium_short_code_confirm_title">Send premium SMS?</string>
+    <!-- The message text for the SMS short code confirmation dialog. [CHAR LIMIT=NONE] -->
+    <string name="sms_short_code_confirm_message">&lt;b><xliff:g id="app_name">%1$s</xliff:g>&lt;/b> would like to send a text message to &lt;b><xliff:g id="dest_address">%2$s</xliff:g>&lt;/b>, which appears to be an SMS short code.&lt;p>Sending text messages to some short codes may cause your mobile account to be billed for premium services.&lt;p>Do you want to allow this app to send the message?</string>
+    <!-- The message text for the SMS short code confirmation dialog. [CHAR LIMIT=NONE] -->
+    <string name="sms_premium_short_code_confirm_message">&lt;b><xliff:g id="app_name">%1$s</xliff:g>&lt;/b> would like to send a text message to &lt;b><xliff:g id="dest_address">%2$s</xliff:g>&lt;/b>, which is a premium SMS short code.&lt;p>&lt;b>Sending a message to this destination will cause your mobile account to be billed for premium services.&lt;/b>&lt;p>Do you want to allow this app to send the message?</string>
+    <!-- Text of the approval button for the SMS short code confirmation dialog. [CHAR LIMIT=50] -->
+    <string name="sms_short_code_confirm_allow">Send message</string>
+    <!-- Text of the cancel button for the SMS short code confirmation dialog. [CHAR LIMIT=30] -->
+    <string name="sms_short_code_confirm_deny">Don\'t send</string>
+    <!-- Text of the button for the SMS short code confirmation dialog to report a malicious app. [CHAR LIMIT=30] -->
+    <string name="sms_short_code_confirm_report">Report malicious app</string>
 
     <!-- SIM swap and device reboot Dialog --> <skip />
     <!-- See SIM_REMOVED_DIALOG.  This is the title of that dialog. -->
@@ -3241,30 +3255,6 @@
     <!-- Description of the button to decrease the DatePicker's year value. [CHAR LIMIT=NONE] -->
     <string name="date_picker_decrement_year_button">Decrease year</string>
 
-    <!-- CheckBox - accessibility support -->
-    <!-- Description of the checked state of a CheckBox. [CHAR LIMIT=NONE] -->
-    <string name="checkbox_checked">checked</string>
-    <!-- Description of the not checked state of a CheckBox. [CHAR LIMIT=NONE] -->
-    <string name="checkbox_not_checked">not checked</string>
-
-    <!-- RadioButton/CheckedTextView - accessibility support -->
-    <!-- Description of the selected state of a RadioButton. [CHAR LIMIT=NONE] -->
-    <string name="radiobutton_selected">selected</string>
-    <!-- Description of the not selected state of a RadioButton. [CHAR LIMIT=NONE] -->
-    <string name="radiobutton_not_selected">not selected</string>
-
-    <!-- Switch - accessibility support -->
-    <!-- Description of the on state of a Switch. [CHAR LIMIT=NONE] -->
-    <string name="switch_on">on</string>
-    <!-- Description of the off state of a Switch. [CHAR LIMIT=NONE] -->
-    <string name="switch_off">off</string>
-
-    <!-- ToggleButton - accessibility support -->
-    <!-- Description of the pressed state of a ToggleButton. [CHAR LIMIT=NONE] -->
-    <string name="togglebutton_pressed">pressed</string>
-    <!-- Description of the not pressed state of a ToggleButton. [CHAR LIMIT=NONE] -->
-    <string name="togglebutton_not_pressed">not pressed</string>
-
     <!-- KeyboardView - accessibility support -->
     <!-- Description of the Alt button in a KeyboardView. [CHAR LIMIT=NONE] -->
     <string name="keyboardview_keycode_alt">Alt</string>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
new file mode 100644
index 0000000..8b395af
--- /dev/null
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<!-- Regex patterns for SMS short codes by country. -->
+<shortcodes>
+
+    <!-- The country attribute is the ISO country code of the user's account (from SIM card or NV).
+         The pattern attribute is a regex that matches all SMS short codes for the country.
+         The premium attribute is a regex that matches premium rate SMS short codes.
+         The free attribute matches short codes that we know will not cost the user, such as
+         emergency numbers. The standard attribute matches short codes that are billed at the
+         standard SMS rate. The user is warned when the destination phone number matches the
+         "pattern" or "premium" regexes, and does not match the "free" or "standard" regexes. -->
+
+    <!-- Harmonised European Short Codes are 6 digit numbers starting with 116 (free helplines).
+         Premium patterns include short codes from: http://aonebill.com/coverage&tariffs
+         and http://mobilcent.com/info-worldwide.asp and extracted from:
+         http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ -->
+
+    <!-- Albania: 5 digits, known short codes listed -->
+    <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
+
+    <!-- Armenia: 3-4 digits, emergency numbers 10[123] -->
+    <shortcode country="am" pattern="\\d{3,4}" premium="11[2456]1|3024" free="10[123]" />
+
+    <!-- Austria: 10 digits, premium prefix 09xx, plus EU -->
+    <shortcode country="at" pattern="11\\d{4}" premium="09.*" free="116\\d{3}" />
+
+    <!-- Australia: 6 or 8 digits starting with "19" -->
+    <shortcode country="au" pattern="19(?:\\d{4}|\\d{6})" premium="19998882" />
+
+    <!-- Azerbaijan: 4-5 digits, known premium codes listed -->
+    <shortcode country="az" pattern="\\d{4,5}" premium="330[12]|87744|901[234]|93(?:94|101)|9426|9525" />
+
+    <!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp -->
+    <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" />
+
+    <!-- Bulgaria: 4-5 digits, plus EU -->
+    <shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}" />
+
+    <!-- Belarus: 4 digits -->
+    <shortcode country="by" pattern="\\d{4}" premium="3336|4161|444[4689]|501[34]|7781" />
+
+    <!-- Canada: 5-6 digits -->
+    <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188" />
+
+    <!-- Switzerland: 3-5 digits: http://www.swisscom.ch/fxres/kmu/thirdpartybusiness_code_of_conduct_en.pdf -->
+    <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111" />
+
+    <!-- China: premium shortcodes start with "1066", free shortcodes start with "1065":
+         http://clients.txtnation.com/entries/197192-china-premium-sms-short-code-requirements -->
+    <shortcode country="cn" premium="1066.*" free="1065.*" />
+
+    <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
+    <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
+
+    <!-- Czech Republic: 7-8 digits, starting with 9, plus EU:
+         http://www.o2.cz/osobni/en/services-by-alphabet/91670-premium_sms.html -->
+    <shortcode country="cz" premium="9\\d{6,7}" free="116\\d{3}" />
+
+    <!-- Germany: 4-5 digits plus 1232xxx (premium codes from http://www.vodafone.de/infofaxe/537.pdf and http://premiumdienste.eplus.de/pdf/kodex.pdf), plus EU. To keep the premium regex from being too large, it only includes payment processors that have been used by SMS malware, with the regular pattern matching the other premium short codes. -->
+    <shortcode country="de" pattern="\\d{4,5}|1232\\d{3}" premium="11(?:111|833)|1232(?:013|021|060|075|286|358)|118(?:44|80|86)|20[25]00|220(?:21|22|88|99)|221(?:14|21)|223(?:44|53|77)|224[13]0|225(?:20|59|90)|226(?:06|10|20|26|30|40|56|70)|227(?:07|33|39|66|76|78|79|88|99)|228(?:08|11|66|77)|23300|30030|3[12347]000|330(?:33|55|66)|33(?:233|331|366|533)|34(?:34|567)|37000|40(?:040|123|444|[3568]00)|41(?:010|414)|44(?:000|044|344|44[24]|544)|50005|50100|50123|50555|51000|52(?:255|783)|54(?:100|2542)|55(?:077|[24]00|222|333|55|[12369]55)|56(?:789|886)|60800|6[13]000|66(?:[12348]66|566|766|777|88|999)|68888|70(?:07|123|777)|76766|77(?:007|070|222|444|[567]77)|80(?:008|123|888)|82(?:002|[378]00|323|444|472|474|488|727)|83(?:005|[169]00|333|830)|84(?:141|300|32[34]|343|488|499|777|888)|85888|86(?:188|566|640|644|650|677|868|888)|870[24]9|871(?:23|[49]9)|872(?:1[0-8]|49|99)|87499|875(?:49|55|99)|876(?:0[1367]|1[1245678]|54|99)|877(?:00|99)|878(?:15|25|3[567]|8[12])|87999|880(?:08|44|55|77|99)|88688|888(?:03|10|8|89)|8899|90(?:009|999)|99999" free="116\\d{3}" />
+
+    <!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers -->
+    <shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}" />
+
+    <!-- Estonia: short codes 3-5 digits starting with 1, plus premium 7 digit numbers starting with 90, plus EU.
+         http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht -->
+    <shortcode country="ee" pattern="1\\d{2,4}" premium="90\\d{5}|15330|1701[0-3]" free="116\\d{3}" />
+
+    <!-- Spain: 5-6 digits: 25xxx, 27xxx, 280xx, 35xxx, 37xxx, 795xxx, 797xxx, 995xxx, 997xxx, plus EU.
+         http://www.legallink.es/?q=en/content/which-current-regulatory-status-premium-rate-services-spain -->
+    <shortcode country="es" premium="[23][57]\\d{3}|280\\d{2}|[79]9[57]\\d{3}" free="116\\d{3}" />
+
+    <!-- Finland: 5-6 digits, premium 0600, 0700: http://en.wikipedia.org/wiki/Telephone_numbers_in_Finland -->
+    <shortcode country="fi" pattern="\\d{5,6}" premium="0600.*|0700.*|171(?:59|63)" free="116\\d{3}" />
+
+    <!-- France: 5 digits, free: 3xxxx, premium [4-8]xxxx, plus EU:
+         http://clients.txtnation.com/entries/161972-france-premium-sms-short-code-requirements -->
+    <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}" />
+
+    <!-- United Kingdom (Great Britain): 4-6 digits, common codes [5-8]xxxx, plus EU:
+         http://www.short-codes.com/media/Co-regulatoryCodeofPracticeforcommonshortcodes170206.pdf -->
+    <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}" />
+
+    <!-- Georgia: 4 digits, known premium codes listed -->
+    <shortcode country="ge" pattern="\\d{4}" premium="801[234]|888[239]" />
+
+    <!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece -->
+    <shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}" />
+
+    <!-- Hungary: 4 or 10 digits starting with 1 or 0, plus EU:
+         http://clients.txtnation.com/entries/209633-hungary-premium-sms-short-code-regulations -->
+    <shortcode country="hu" pattern="[01](?:\\d{3}|\\d{9})" premium="0691227910|1784" free="116\\d{3}" />
+
+    <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:
+         http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
+    <shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" />
+
+    <!-- Israel: 4 digits, known premium codes listed -->
+    <shortcode country="il" pattern="\\d{4}" premium="4422|4545" />
+
+    <!-- Italy: 5 digits (premium=4xxxx), plus EU:
+         http://clients.txtnation.com/attachments/token/di5kfblvubttvlw/?name=Italy_CASP_EN.pdf -->
+    <shortcode country="it" pattern="\\d{5}" premium="4\\d{4}" free="116\\d{3}" />
+
+    <!-- Kyrgyzstan: 4 digits, known premium codes listed -->
+    <shortcode country="kg" pattern="\\d{4}" premium="415[2367]|444[69]" />
+
+    <!-- Kazakhstan: 4 digits, known premium codes listed: http://smscoin.net/info/pricing-kazakhstan/ -->
+    <shortcode country="kz" pattern="\\d{4}" premium="335[02]|4161|444[469]|77[2359]0|8444|919[3-5]|968[2-5]" />
+
+    <!-- Lithuania: 3-5 digits, known premium codes listed, plus EU -->
+    <shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}" />
+
+    <!-- Luxembourg: 5 digits, 6xxxx, plus EU:
+         http://www.luxgsm.lu/assets/files/filepage/file_1253803400.pdf -->
+    <shortcode country="lu" premium="6\\d{4}" free="116\\d{3}" />
+
+    <!-- Latvia: 4 digits, known premium codes listed, plus EU -->
+    <shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}" />
+
+    <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
+    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" />
+
+    <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
+    <shortcode country="my" pattern="\\d{5}" premium="32298|33776" />
+
+    <!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
+    <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}" />
+
+    <!-- Norway: 4-5 digits (not confirmed), known premium codes listed -->
+    <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" />
+
+    <!-- New Zealand: 3-4 digits, known premium codes listed -->
+    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995" />
+
+    <!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
+    <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}" />
+
+    <!-- Portugal: 5 digits, plus EU:
+         http://clients.txtnation.com/entries/158326-portugal-premium-sms-short-code-regulations -->
+    <shortcode country="pt" premium="6[1289]\\d{3}" free="116\\d{3}" />
+
+    <!-- Romania: 4 digits, plus EU: http://www.simplus.ro/en/resources/glossary-of-terms/ -->
+    <shortcode country="ro" pattern="\\d{4}" premium="12(?:63|66|88)|13(?:14|80)" free="116\\d{3}" />
+
+    <!-- Russia: 4 digits, known premium codes listed: http://smscoin.net/info/pricing-russia/ -->
+    <shortcode country="ru" pattern="\\d{4}" premium="1(?:1[56]1|899)|2(?:09[57]|322|47[46]|880|990)|3[589]33|4161|44(?:4[3-9]|81)|77(?:33|81)" />
+
+    <!-- Sweden: 5 digits (72xxx), plus EU: http://www.viatel.se/en/premium-sms/ -->
+    <shortcode country="se" premium="72\\d{3}" free="116\\d{3}" />
+
+    <!-- Singapore: 5 digits: http://clients.txtnation.com/entries/306442-singapore-premium-sms-short-code-requirements
+         Free government directory info at 74688: http://app.sgdi.gov.sg/sms_help.asp -->
+    <shortcode country="sg" pattern="7\\d{4}" premium="73800" standard="74688" />
+
+    <!-- Slovenia: 4 digits (premium=3xxx, 6xxx, 8xxx), plus EU: http://www.cmtelecom.com/premium-sms/slovenia -->
+    <shortcode country="si" pattern="\\d{4}" premium="[368]\\d{3}" free="116\\d{3}" />
+
+    <!-- Slovakia: 4 digits (premium), plus EU: http://www.cmtelecom.com/premium-sms/slovakia -->
+    <shortcode country="sk" premium="\\d{4}" free="116\\d{3}" />
+
+    <!-- Tajikistan: 4 digits, known premium codes listed -->
+    <shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" />
+
+    <!-- Ukraine: 4 digits, known premium codes listed -->
+    <shortcode country="ua" pattern="\\d{4}" premium="444[3-9]|70[579]4|7540" />
+
+    <!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm) -->
+    <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" />
+
+</shortcodes>
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
index 5ba6bf9..85a77d6 100644
--- a/data/fonts/Android.mk
+++ b/data/fonts/Android.mk
@@ -124,8 +124,8 @@
     Roboto-Bold.ttf \
     Roboto-Italic.ttf \
     Roboto-BoldItalic.ttf \
-    DroidSansArabic.ttf \
     DroidNaskh-Regular.ttf \
+    DroidNaskh-Regular-Shift.ttf \
     DroidSansHebrew-Regular.ttf \
     DroidSansHebrew-Bold.ttf \
     DroidSansThai.ttf \
diff --git a/data/fonts/DroidNaskh-Regular-Shift.ttf b/data/fonts/DroidNaskh-Regular-Shift.ttf
new file mode 100644
index 0000000..0cb843d
--- /dev/null
+++ b/data/fonts/DroidNaskh-Regular-Shift.ttf
Binary files differ
diff --git a/data/fonts/fallback_fonts-ja.xml b/data/fonts/fallback_fonts-ja.xml
index 62491d8..db998d3 100644
--- a/data/fonts/fallback_fonts-ja.xml
+++ b/data/fonts/fallback_fonts-ja.xml
@@ -34,7 +34,7 @@
 <familyset>
     <family>
         <fileset>
-            <file>DroidSansArabic.ttf</file>
+            <file>DroidNaskh-Regular-Shift.ttf</file>
         </fileset>
     </family>
     <family>
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
index ba01947..a4b5212 100644
--- a/data/fonts/fallback_fonts.xml
+++ b/data/fonts/fallback_fonts.xml
@@ -34,7 +34,7 @@
 <familyset>
     <family>
         <fileset>
-            <file>DroidSansArabic.ttf</file>
+            <file>DroidNaskh-Regular-Shift.ttf</file>
         </fileset>
     </family>
     <family>
diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk
index db26765..702b069 100644
--- a/data/fonts/fonts.mk
+++ b/data/fonts/fonts.mk
@@ -24,8 +24,8 @@
     Roboto-Bold.ttf \
     Roboto-Italic.ttf \
     Roboto-BoldItalic.ttf \
-    DroidSansArabic.ttf \
     DroidNaskh-Regular.ttf \
+    DroidNaskh-Regular-Shift.ttf \
     DroidSansHebrew-Regular.ttf \
     DroidSansHebrew-Bold.ttf \
     DroidSansThai.ttf \
diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
index 2e18a10..e403205 100644
--- a/data/sounds/AllAudio.mk
+++ b/data/sounds/AllAudio.mk
@@ -20,3 +20,5 @@
 $(call inherit-product, frameworks/base/data/sounds/AudioPackage4.mk)
 $(call inherit-product, frameworks/base/data/sounds/AudioPackage5.mk)
 $(call inherit-product, frameworks/base/data/sounds/AudioPackage6.mk)
+$(call inherit-product, frameworks/base/data/sounds/AudioPackage7.mk)
+$(call inherit-product, frameworks/base/data/sounds/AudioPackage7alt.mk)
diff --git a/docs/html/guide/developing/debugging/ddms.jd b/docs/html/guide/developing/debugging/ddms.jd
index 80b1e47..9892e49 100644
--- a/docs/html/guide/developing/debugging/ddms.jd
+++ b/docs/html/guide/developing/debugging/ddms.jd
@@ -128,9 +128,7 @@
   classes and threads are allocating the objects. This allows you to track, in real time, where
   objects are being allocated when you perform certain actions in your application. This
   information is valuable for assessing memory usage that can affect application performance.
-  If you want more granular control over where allocation data is collected, use the
-  {@link android.os.Debug#startAllocCounting()} and {@link android.os.Debug#stopAllocCounting()}
-  methods.</p>
+  </p>
   
   <p>To track memory allocation of objects:</p>
   <ol>
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 7269a71..b3a8fd7 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -25,34 +25,38 @@
     public static final int UNKNOWN = 0;
 
     /**
-     * RGB format used for pictures encoded as RGB_565 see
+     * RGB format used for pictures encoded as RGB_565. See
      * {@link android.hardware.Camera.Parameters#setPictureFormat(int)}.
      */
     public static final int RGB_565 = 4;
 
     /**
-     * Android YUV format:
+     * <p>Android YUV format.</p>
      *
-     * This format is exposed to software decoders and applications.
+     * <p>This format is exposed to software decoders and applications.</p>
      *
-     * YV12 is a 4:2:0 YCrCb planar format comprised of a WxH Y plane followed
-     * by (W/2) x (H/2) Cr and Cb planes.
+     * <p>YV12 is a 4:2:0 YCrCb planar format comprised of a WxH Y plane followed
+     * by (W/2) x (H/2) Cr and Cb planes.</p>
      *
-     * This format assumes
-     * - an even width
-     * - an even height
-     * - a horizontal stride multiple of 16 pixels
-     * - a vertical stride equal to the height
+     * <p>This format assumes
+     * <ul>
+     * <li>an even width</li>
+     * <li>an even height</li>
+     * <li>a horizontal stride multiple of 16 pixels</li>
+     * <li>a vertical stride equal to the height</li>
+     * </ul>
+     * </p>
      *
-     *   y_size = stride * height
-     *   c_size = ALIGN(stride/2, 16) * height/2
-     *   size = y_size + c_size * 2
-     *   cr_offset = y_size
-     *   cb_offset = y_size + c_size
+     * <pre> y_size = stride * height
+     * c_size = ALIGN(stride/2, 16) * height/2
+     * size = y_size + c_size * 2
+     * cr_offset = y_size
+     * cb_offset = y_size + c_size</pre>
      *
-     * Whether this format is supported by the camera hardware can be determined
-     * by
+     * This format is guaranteed to be supported for camera preview images since
+     * API level 12; for earlier API versions, check
      * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+     * </p>
      */
     public static final int YV12 = 0x32315659;
 
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index c97785e..f68f9dc 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -21,6 +21,8 @@
 import android.text.SpannedString;
 import android.text.TextUtils;
 
+import java.util.Locale;
+
 /**
  * The Paint class holds the style and color information about how to draw
  * geometries, text and bitmaps.
@@ -44,6 +46,8 @@
     private float       mCompatScaling;
     private float       mInvCompatScaling;
 
+    private Locale      mLocale;
+
     /**
      * @hide
      */
@@ -348,6 +352,7 @@
         // setHinting(DisplayMetrics.DENSITY_DEVICE >= DisplayMetrics.DENSITY_TV
         //        ? HINTING_OFF : HINTING_ON);
         mCompatScaling = mInvCompatScaling = 1;
+        setTextLocale(Locale.getDefault());
     }
 
     /**
@@ -373,6 +378,7 @@
         mHasCompatScaling = false;
         mCompatScaling = mInvCompatScaling = 1;
         mBidiFlags = BIDI_DEFAULT_LTR;
+        setTextLocale(Locale.getDefault());
     }
     
     /**
@@ -412,6 +418,7 @@
         shadowColor = paint.shadowColor;
 
         mBidiFlags = paint.mBidiFlags;
+        mLocale = paint.mLocale;
     }
 
     /** @hide */
@@ -1045,6 +1052,50 @@
     }
 
     /**
+     * Get the text Locale.
+     *
+     * @return the paint's Locale used for drawing text, never null.
+     */
+    public Locale getTextLocale() {
+        return mLocale;
+    }
+
+    /**
+     * Set the text locale.
+     *
+     * The text locale affects how the text is drawn for some languages.
+     *
+     * For example, if the locale is {@link Locale#CHINESE} or {@link Locale#CHINA},
+     * then the text renderer will prefer to draw text using a Chinese font. Likewise,
+     * if the locale is {@link Locale#JAPANESE} or {@link Locale#JAPAN}, then the text
+     * renderer will prefer to draw text using a Japanese font.
+     *
+     * This distinction is important because Chinese and Japanese text both use many
+     * of the same Unicode code points but their appearance is subtly different for
+     * each language.
+     *
+     * By default, the text locale is initialized to the system locale (as returned
+     * by {@link Locale#getDefault}). This assumes that the text to be rendered will
+     * most likely be in the user's preferred language.
+     *
+     * If the actual language of the text is known, then it can be provided to the
+     * text renderer using this method. The text renderer may attempt to guess the
+     * language script based on the contents of the text to be drawn independent of
+     * the text locale here. Specifying the text locale just helps it do a better
+     * job in certain ambiguous cases
+     *
+     * @param locale the paint's locale value for drawing text, must not be null.
+     */
+    public void setTextLocale(Locale locale) {
+        if (locale == null) {
+            throw new IllegalArgumentException("locale cannot be null");
+        }
+        if (locale.equals(mLocale)) return;
+        mLocale = locale;
+        native_setTextLocale(mNativePaint, locale.toString());
+    }
+
+    /**
      * Return the paint's text size.
      *
      * @return the paint's text size.
@@ -2144,6 +2195,9 @@
     private static native void native_setTextAlign(int native_object,
                                                    int align);
 
+    private static native void native_setTextLocale(int native_object,
+                                                    String locale);
+
     private static native int native_getTextWidths(int native_object,
                             char[] text, int index, int count, float[] widths);
     private static native int native_getTextWidths(int native_object,
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index cd5300d..10ccb87 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -136,7 +136,6 @@
      * consumer.  This usage will cause the allocation to be created
      * read only.
      *
-     * @hide
      */
     public static final int USAGE_IO_INPUT = 0x0020;
 
@@ -145,7 +144,6 @@
      * SurfaceTexture producer.  The dimensions and format of the
      * SurfaceTexture will be forced to those of the allocation.
      *
-     * @hide
      */
     public static final int USAGE_IO_OUTPUT = 0x0040;
 
@@ -193,8 +191,8 @@
    /**
      * Get the element of the type of the Allocation.
      *
-     * @hide
-     * @return Element
+     * @return Element that describes the structure of data in the
+     *         allocation
      *
      */
     public Element getElement() {
@@ -204,8 +202,8 @@
     /**
      * Get the usage flags of the Allocation.
      *
-     * @hide
-     * @return usage
+     * @return usage flags associated with the allocation. e.g.
+     *         script, texture, etc.
      *
      */
     public int getUsage() {
@@ -215,12 +213,11 @@
     /**
      * Get the size of the Allocation in bytes.
      *
-     * @hide
-     * @return sizeInBytes
+     * @return size of the Allocation in bytes.
      *
      */
-    public int getSizeBytes() {
-        return mType.getCount() * mType.getElement().getSizeBytes();
+    public int getBytesSize() {
+        return mType.getCount() * mType.getElement().getBytesSize();
     }
 
     private void updateCacheInfo(Type t) {
@@ -362,8 +359,6 @@
      * Send a buffer to the output stream.  The contents of the
      * Allocation will be undefined after this operation.
      *
-     * @hide
-     *
      */
     public void ioSend() {
         if ((mUsage & USAGE_IO_OUTPUT) == 0) {
@@ -385,8 +380,6 @@
     /**
      * Receive the latest input into the Allocation.
      *
-     * @hide
-     *
      */
     public void ioReceive() {
         if ((mUsage & USAGE_IO_INPUT) == 0) {
@@ -424,37 +417,37 @@
                 throw new RSIllegalArgumentException("Allocation kind is " +
                                                      mType.getElement().mKind + ", type " +
                                                      mType.getElement().mType +
-                                                     " of " + mType.getElement().getSizeBytes() +
+                                                     " of " + mType.getElement().getBytesSize() +
                                                      " bytes, passed bitmap was " + bc);
             }
             break;
         case ARGB_8888:
             if ((mType.getElement().mKind != Element.DataKind.PIXEL_RGBA) ||
-                (mType.getElement().getSizeBytes() != 4)) {
+                (mType.getElement().getBytesSize() != 4)) {
                 throw new RSIllegalArgumentException("Allocation kind is " +
                                                      mType.getElement().mKind + ", type " +
                                                      mType.getElement().mType +
-                                                     " of " + mType.getElement().getSizeBytes() +
+                                                     " of " + mType.getElement().getBytesSize() +
                                                      " bytes, passed bitmap was " + bc);
             }
             break;
         case RGB_565:
             if ((mType.getElement().mKind != Element.DataKind.PIXEL_RGB) ||
-                (mType.getElement().getSizeBytes() != 2)) {
+                (mType.getElement().getBytesSize() != 2)) {
                 throw new RSIllegalArgumentException("Allocation kind is " +
                                                      mType.getElement().mKind + ", type " +
                                                      mType.getElement().mType +
-                                                     " of " + mType.getElement().getSizeBytes() +
+                                                     " of " + mType.getElement().getBytesSize() +
                                                      " bytes, passed bitmap was " + bc);
             }
             break;
         case ARGB_4444:
             if ((mType.getElement().mKind != Element.DataKind.PIXEL_RGBA) ||
-                (mType.getElement().getSizeBytes() != 2)) {
+                (mType.getElement().getBytesSize() != 2)) {
                 throw new RSIllegalArgumentException("Allocation kind is " +
                                                      mType.getElement().mKind + ", type " +
                                                      mType.getElement().mType +
-                                                     " of " + mType.getElement().getSizeBytes() +
+                                                     " of " + mType.getElement().getBytesSize() +
                                                      " bytes, passed bitmap was " + bc);
             }
             break;
@@ -583,7 +576,7 @@
      */
     public void setFromFieldPacker(int xoff, FieldPacker fp) {
         mRS.validate();
-        int eSize = mType.mElement.getSizeBytes();
+        int eSize = mType.mElement.getBytesSize();
         final byte[] data = fp.getData();
 
         int count = data.length / eSize;
@@ -612,7 +605,7 @@
         }
 
         final byte[] data = fp.getData();
-        int eSize = mType.mElement.mElements[component_number].getSizeBytes();
+        int eSize = mType.mElement.mElements[component_number].getBytesSize();
         eSize *= mType.mElement.mArraySizes[component_number];
 
         if (data.length != eSize) {
@@ -665,7 +658,7 @@
      * @param d the source data array
      */
     public void copy1DRangeFromUnchecked(int off, int count, int[] d) {
-        int dataSize = mType.mElement.getSizeBytes() * count;
+        int dataSize = mType.mElement.getBytesSize() * count;
         data1DChecks(off, count, d.length * 4, dataSize);
         mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize);
     }
@@ -679,7 +672,7 @@
      * @param d the source data array
      */
     public void copy1DRangeFromUnchecked(int off, int count, short[] d) {
-        int dataSize = mType.mElement.getSizeBytes() * count;
+        int dataSize = mType.mElement.getBytesSize() * count;
         data1DChecks(off, count, d.length * 2, dataSize);
         mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize);
     }
@@ -693,7 +686,7 @@
      * @param d the source data array
      */
     public void copy1DRangeFromUnchecked(int off, int count, byte[] d) {
-        int dataSize = mType.mElement.getSizeBytes() * count;
+        int dataSize = mType.mElement.getBytesSize() * count;
         data1DChecks(off, count, d.length, dataSize);
         mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize);
     }
@@ -707,7 +700,7 @@
      * @param d the source data array
      */
     public void copy1DRangeFromUnchecked(int off, int count, float[] d) {
-        int dataSize = mType.mElement.getSizeBytes() * count;
+        int dataSize = mType.mElement.getBytesSize() * count;
         data1DChecks(off, count, d.length * 4, dataSize);
         mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize);
     }
@@ -1030,30 +1023,6 @@
     }
 
     /**
-     * @hide
-     * This API is hidden and only intended to be used for
-     * transitional purposes.
-     *
-     * @param type renderscript type describing data layout
-     * @param mips specifies desired mipmap behaviour for the
-     *             allocation
-     * @param usage bit field specifying how the allocation is
-     *              utilized
-     */
-    static public Allocation createTyped(RenderScript rs, Type type, MipmapControl mips,
-                                         int usage, int pointer) {
-        rs.validate();
-        if (type.getID(rs) == 0) {
-            throw new RSInvalidStateException("Bad Type");
-        }
-        int id = rs.nAllocationCreateTyped(type.getID(rs), mips.mID, usage, pointer);
-        if (id == 0) {
-            throw new RSRuntimeException("Allocation creation failed.");
-        }
-        return new Allocation(id, rs, type, usage);
-    }
-
-    /**
      * Creates a renderscript allocation with the size specified by
      * the type and no mipmaps generated by default
      *
@@ -1194,8 +1163,11 @@
     }
 
     /**
+     * For allocations used with io operations, returns the handle
+     * onto a raw buffer that is being managed by the screen
+     * compositor.
      *
-     * @hide
+     * @return Surface object associated with allocation
      *
      */
     public Surface getSurface() {
@@ -1203,7 +1175,9 @@
     }
 
     /**
-     * @hide
+     * Associate a surface for io output with this allocation
+     *
+     * @param sur Surface to associate with allocation
      */
     public void setSurface(Surface sur) {
         mRS.validate();
diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java
index d75c951..28914ce 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -85,13 +85,13 @@
     }
 
     /**
-    * @hide
     * @return element size in bytes
     */
-    public int getSizeBytes() {return mSize;}
+    public int getBytesSize() {return mSize;}
 
     /**
-    * @hide
+    * Returns the number of vector components. 2 for float2, 4 for
+    * float4, etc.
     * @return element vector size
     */
     public int getVectorSize() {return mVectorSize;}
@@ -114,10 +114,6 @@
      * RS_* objects.  32 bit opaque handles.
      */
     public enum DataType {
-        /**
-        * @hide
-        * new enum
-        */
         NONE (0, 0),
         //FLOAT_16 (1, 2),
         FLOAT_32 (2, 4),
@@ -150,7 +146,8 @@
         RS_PROGRAM_FRAGMENT (1006, 4),
         RS_PROGRAM_VERTEX (1007, 4),
         RS_PROGRAM_RASTER (1008, 4),
-        RS_PROGRAM_STORE (1009, 4);
+        RS_PROGRAM_STORE (1009, 4),
+        RS_FONT (1010, 4);
 
         int mID;
         int mSize;
@@ -201,7 +198,10 @@
     }
 
     /**
-    * @hide
+    * Elements could be simple, such as an int or a float, or a
+    * structure with multiple sub elements, such as a collection of
+    * floats, float2, float4. This function returns zero for simple
+    * elements or the number of sub-elements otherwise.
     * @return number of sub-elements in this element
     */
     public int getSubElementCount() {
@@ -212,7 +212,8 @@
     }
 
     /**
-    * @hide
+    * For complex elements, this function will return the
+    * sub-element at index
     * @param index index of the sub-element to return
     * @return sub-element in this element at given index
     */
@@ -227,7 +228,8 @@
     }
 
     /**
-    * @hide
+    * For complex elements, this function will return the
+    * sub-element name at index
     * @param index index of the sub-element
     * @return sub-element in this element at given index
     */
@@ -242,7 +244,9 @@
     }
 
     /**
-    * @hide
+    * For complex elements, some sub-elements could be statically
+    * sized arrays. This function will return the array size for
+    * sub-element at index
     * @param index index of the sub-element
     * @return array size of sub-element in this element at given index
     */
@@ -257,7 +261,8 @@
     }
 
     /**
-    * @hide
+    * This function specifies the location of a sub-element within
+    * the element
     * @param index index of the sub-element
     * @return offset in bytes of sub-element in this element at given index
     */
@@ -272,7 +277,6 @@
     }
 
     /**
-    * @hide
     * @return element data type
     */
     public DataType getDataType() {
@@ -280,7 +284,6 @@
     }
 
     /**
-    * @hide
     * @return element data kind
     */
     public DataKind getDataKind() {
@@ -455,6 +458,13 @@
         return rs.mElement_PROGRAM_STORE;
     }
 
+    public static Element FONT(RenderScript rs) {
+        if(rs.mElement_FONT == null) {
+            rs.mElement_FONT = createUser(rs, DataType.RS_FONT);
+        }
+        return rs.mElement_FONT;
+    }
+
 
     public static Element A_8(RenderScript rs) {
         if(rs.mElement_A_8 == null) {
diff --git a/graphics/java/android/renderscript/Program.java b/graphics/java/android/renderscript/Program.java
index 104d1cd..d9f64c6 100644
--- a/graphics/java/android/renderscript/Program.java
+++ b/graphics/java/android/renderscript/Program.java
@@ -78,14 +78,20 @@
     }
 
     /**
-     * @hide
+     * Program object can have zero or more constant allocations
+     * associated with it. This method returns the total count.
+     * @return number of constant input types
      */
     public int getConstantCount() {
         return mConstants != null ? mConstants.length : 0;
     }
 
     /**
-     * @hide
+     * Returns the type of the constant buffer used in the program
+     * object. It could be used to query internal elements or create
+     * an allocation to store constant data.
+     * @param slot index of the constant input type to return
+     * @return constant input type
      */
     public Type getConstant(int slot) {
         if (slot < 0 || slot >= mConstants.length) {
@@ -95,14 +101,17 @@
     }
 
     /**
-     * @hide
+     * Returns the number of textures used in this program object
+     * @return number of texture inputs
      */
     public int getTextureCount() {
         return mTextureCount;
     }
 
     /**
-     * @hide
+     * Returns the type of texture at a given slot. e.g. 2D or Cube
+     * @param slot index of the texture input
+     * @return texture input type
      */
     public TextureType getTextureType(int slot) {
         if ((slot < 0) || (slot >= mTextureCount)) {
@@ -112,7 +121,10 @@
     }
 
     /**
-     * @hide
+     * Returns the name of the texture input at a given slot. e.g.
+     * tex0, diffuse, spec
+     * @param slot index of the texture input
+     * @return texture input name
      */
     public String getTextureName(int slot) {
         if ((slot < 0) || (slot >= mTextureCount)) {
@@ -318,7 +330,6 @@
         }
 
         /**
-         * @hide
          * Adds a texture input to the Program
          *
          * @param texType describes that the texture to append it (2D,
diff --git a/graphics/java/android/renderscript/ProgramRaster.java b/graphics/java/android/renderscript/ProgramRaster.java
index 93ee0ce..e40751f 100644
--- a/graphics/java/android/renderscript/ProgramRaster.java
+++ b/graphics/java/android/renderscript/ProgramRaster.java
@@ -48,15 +48,16 @@
     }
 
     /**
-     * @hide
+     * Specifies whether vertices are rendered as screen aligned
+     * elements of a specified size
      * @return whether point sprites are enabled
      */
-    public boolean getPointSpriteEnabled() {
+    public boolean isPointSpriteEnabled() {
         return mPointSprite;
     }
 
     /**
-     * @hide
+     * Specifies how triangles are culled based on their orientation
      * @return cull mode
      */
     public CullMode getCullMode() {
diff --git a/graphics/java/android/renderscript/ProgramStore.java b/graphics/java/android/renderscript/ProgramStore.java
index 677dadd..d0fd6e5 100644
--- a/graphics/java/android/renderscript/ProgramStore.java
+++ b/graphics/java/android/renderscript/ProgramStore.java
@@ -150,7 +150,8 @@
     }
 
     /**
-    * @hide
+    * Returns the function used to test writing into the depth
+    * buffer
     * @return depth function
     */
     public DepthFunc getDepthFunc() {
@@ -158,47 +159,47 @@
     }
 
     /**
-    * @hide
-    * @return whether depth writes are enabled
+    * Queries whether writes are enabled into the depth buffer
+    * @return depth mask
     */
-    public boolean getDepthMaskEnabled() {
+    public boolean isDepthMaskEnabled() {
         return mDepthMask;
     }
 
     /**
-    * @hide
+    * Queries whether red channel is written
     * @return red color channel mask
     */
-    public boolean getColorMaskREnabled() {
+    public boolean isColorMaskRedEnabled() {
         return mColorMaskR;
     }
 
     /**
-    * @hide
+    * Queries whether green channel is written
     * @return green color channel mask
     */
-    public boolean getColorMaskGEnabled() {
+    public boolean isColorMaskGreenEnabled() {
         return mColorMaskG;
     }
 
     /**
-    * @hide
+    * Queries whether blue channel is written
     * @return blue color channel mask
     */
-    public boolean getColorMaskBEnabled() {
+    public boolean isColorMaskBlueEnabled() {
         return mColorMaskB;
     }
 
     /**
-    * @hide
+    * Queries whether alpha channel is written
     * @return alpha channel mask
     */
-    public boolean getColorMaskAEnabled() {
+    public boolean isColorMaskAlphaEnabled() {
         return mColorMaskA;
     }
 
     /**
-    * @hide
+    * Specifies how the source blending factor is computed
     * @return source blend function
     */
     public BlendSrcFunc getBlendSrcFunc() {
@@ -206,7 +207,7 @@
     }
 
     /**
-    * @hide
+    * Specifies how the destination blending factor is computed
     * @return destination blend function
     */
     public BlendDstFunc getBlendDstFunc() {
@@ -214,10 +215,11 @@
     }
 
     /**
-    * @hide
+    * Specifies whether colors are dithered before writing into the
+    * framebuffer
     * @return whether dither is enabled
     */
-    public boolean getDitherEnabled() {
+    public boolean isDitherEnabled() {
         return mDither;
     }
 
diff --git a/graphics/java/android/renderscript/ProgramVertex.java b/graphics/java/android/renderscript/ProgramVertex.java
index 32c908e..74d666b 100644
--- a/graphics/java/android/renderscript/ProgramVertex.java
+++ b/graphics/java/android/renderscript/ProgramVertex.java
@@ -55,14 +55,15 @@
     }
 
     /**
-     * @hide
+     * @return number of input attribute elements
      */
     public int getInputCount() {
         return mInputs != null ? mInputs.length : 0;
     }
 
     /**
-     * @hide
+     * @param slot location of the input to return
+     * @return input attribute element
      */
     public Element getInput(int slot) {
         if (slot < 0 || slot >= mInputs.length) {
diff --git a/graphics/java/android/renderscript/ProgramVertexFixedFunction.java b/graphics/java/android/renderscript/ProgramVertexFixedFunction.java
index fac4c3d..54f21b8 100644
--- a/graphics/java/android/renderscript/ProgramVertexFixedFunction.java
+++ b/graphics/java/android/renderscript/ProgramVertexFixedFunction.java
@@ -204,7 +204,7 @@
         public Constants(RenderScript rs) {
             Type constInputType = ProgramVertexFixedFunction.Builder.getConstantInputType(rs);
             mAlloc = Allocation.createTyped(rs, constInputType);
-            int bufferSize = constInputType.getElement().getSizeBytes()*
+            int bufferSize = constInputType.getElement().getBytesSize()*
                              constInputType.getCount();
             mIOBuffer = new FieldPacker(bufferSize);
             mModel = new Matrix4f();
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 03294b5..abbcdd9 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -664,6 +664,7 @@
     Element mElement_PROGRAM_VERTEX;
     Element mElement_PROGRAM_RASTER;
     Element mElement_PROGRAM_STORE;
+    Element mElement_FONT;
 
     Element mElement_A_8;
     Element mElement_RGB_565;
diff --git a/graphics/java/android/renderscript/Sampler.java b/graphics/java/android/renderscript/Sampler.java
index 0a3c91d..0df1012 100644
--- a/graphics/java/android/renderscript/Sampler.java
+++ b/graphics/java/android/renderscript/Sampler.java
@@ -59,7 +59,6 @@
     }
 
     /**
-     * @hide
      * @return minification setting for the sampler
      */
     public Value getMinification() {
@@ -67,7 +66,6 @@
     }
 
     /**
-     * @hide
      * @return magnification setting for the sampler
      */
     public Value getMagnification() {
@@ -75,7 +73,6 @@
     }
 
     /**
-     * @hide
      * @return S wrapping mode for the sampler
      */
     public Value getWrapS() {
@@ -83,7 +80,6 @@
     }
 
     /**
-     * @hide
      * @return T wrapping mode for the sampler
      */
     public Value getWrapT() {
@@ -91,7 +87,6 @@
     }
 
     /**
-     * @hide
      * @return anisotropy setting for the sampler
      */
     public float getAnisotropy() {
@@ -288,7 +283,7 @@
 
         public Sampler create() {
             mRS.validate();
-            int id = mRS.nSamplerCreate(mMag.mID, mMin.mID, 
+            int id = mRS.nSamplerCreate(mMag.mID, mMin.mID,
                                         mWrapS.mID, mWrapT.mID, mWrapR.mID, mAniso);
             Sampler sampler = new Sampler(id, mRS);
             sampler.mMin = mMin;
diff --git a/include/androidfw/InputDevice.h b/include/androidfw/InputDevice.h
index c9554dc..2eac544 100644
--- a/include/androidfw/InputDevice.h
+++ b/include/androidfw/InputDevice.h
@@ -66,9 +66,11 @@
         float fuzz;
     };
 
-    void initialize(int32_t id, const String8& name, const String8& descriptor);
+    void initialize(int32_t id, int32_t generation,
+            const String8& name, const String8& descriptor);
 
     inline int32_t getId() const { return mId; }
+    inline int32_t getGeneration() const { return mGeneration; }
     inline const String8 getName() const { return mName; }
     inline const String8 getDescriptor() const { return mDescriptor; }
     inline uint32_t getSources() const { return mSources; }
@@ -97,6 +99,7 @@
 
 private:
     int32_t mId;
+    int32_t mGeneration;
     String8 mName;
     String8 mDescriptor;
     uint32_t mSources;
diff --git a/include/androidfw/KeyLayoutMap.h b/include/androidfw/KeyLayoutMap.h
index 5408680..e7f22a2 100644
--- a/include/androidfw/KeyLayoutMap.h
+++ b/include/androidfw/KeyLayoutMap.h
@@ -64,7 +64,8 @@
 public:
     static status_t load(const String8& filename, sp<KeyLayoutMap>* outMap);
 
-    status_t mapKey(int32_t scanCode, int32_t* keyCode, uint32_t* flags) const;
+    status_t mapKey(int32_t scanCode, int32_t usageCode,
+            int32_t* outKeyCode, uint32_t* outFlags) const;
     status_t findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const;
 
     status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const;
@@ -78,11 +79,14 @@
         uint32_t flags;
     };
 
-    KeyedVector<int32_t, Key> mKeys;
+    KeyedVector<int32_t, Key> mKeysByScanCode;
+    KeyedVector<int32_t, Key> mKeysByUsageCode;
     KeyedVector<int32_t, AxisInfo> mAxes;
 
     KeyLayoutMap();
 
+    const Key* getKey(int32_t scanCode, int32_t usageCode) const;
+
     class Parser {
         KeyLayoutMap* mMap;
         Tokenizer* mTokenizer;
diff --git a/libs/androidfw/InputDevice.cpp b/libs/androidfw/InputDevice.cpp
index 698feb6..6bb06a9 100644
--- a/libs/androidfw/InputDevice.cpp
+++ b/libs/androidfw/InputDevice.cpp
@@ -127,11 +127,12 @@
 // --- InputDeviceInfo ---
 
 InputDeviceInfo::InputDeviceInfo() {
-    initialize(-1, String8("uninitialized device info"), String8("unknown"));
+    initialize(-1, -1, String8("uninitialized device info"), String8("unknown"));
 }
 
 InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) :
-        mId(other.mId), mName(other.mName), mDescriptor(other.mDescriptor),
+        mId(other.mId), mGeneration(other.mGeneration),
+        mName(other.mName), mDescriptor(other.mDescriptor),
         mSources(other.mSources),
         mKeyboardType(other.mKeyboardType),
         mKeyCharacterMap(other.mKeyCharacterMap),
@@ -141,8 +142,10 @@
 InputDeviceInfo::~InputDeviceInfo() {
 }
 
-void InputDeviceInfo::initialize(int32_t id, const String8& name, const String8& descriptor) {
+void InputDeviceInfo::initialize(int32_t id, int32_t generation,
+        const String8& name, const String8& descriptor) {
     mId = id;
+    mGeneration = generation;
     mName = name;
     mDescriptor = descriptor;
     mSources = 0;
diff --git a/libs/androidfw/KeyLayoutMap.cpp b/libs/androidfw/KeyLayoutMap.cpp
index 1809412..a7c2199 100644
--- a/libs/androidfw/KeyLayoutMap.cpp
+++ b/libs/androidfw/KeyLayoutMap.cpp
@@ -80,32 +80,49 @@
     return status;
 }
 
-status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t* keyCode, uint32_t* flags) const {
-    ssize_t index = mKeys.indexOfKey(scanCode);
-    if (index < 0) {
+status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t usageCode,
+        int32_t* outKeyCode, uint32_t* outFlags) const {
+    const Key* key = getKey(scanCode, usageCode);
+    if (!key) {
 #if DEBUG_MAPPING
-        ALOGD("mapKey: scanCode=%d ~ Failed.", scanCode);
+        ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode);
 #endif
-        *keyCode = AKEYCODE_UNKNOWN;
-        *flags = 0;
+        *outKeyCode = AKEYCODE_UNKNOWN;
+        *outFlags = 0;
         return NAME_NOT_FOUND;
     }
 
-    const Key& k = mKeys.valueAt(index);
-    *keyCode = k.keyCode;
-    *flags = k.flags;
+    *outKeyCode = key->keyCode;
+    *outFlags = key->flags;
 
 #if DEBUG_MAPPING
-    ALOGD("mapKey: scanCode=%d ~ Result keyCode=%d, flags=0x%08x.", scanCode, *keyCode, *flags);
+    ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d, outFlags=0x%08x.",
+            scanCode, usageCode, *outKeyCode, *outFlags);
 #endif
     return NO_ERROR;
 }
 
+const KeyLayoutMap::Key* KeyLayoutMap::getKey(int32_t scanCode, int32_t usageCode) const {
+    if (usageCode) {
+        ssize_t index = mKeysByUsageCode.indexOfKey(usageCode);
+        if (index >= 0) {
+            return &mKeysByUsageCode.valueAt(index);
+        }
+    }
+    if (scanCode) {
+        ssize_t index = mKeysByScanCode.indexOfKey(scanCode);
+        if (index >= 0) {
+            return &mKeysByScanCode.valueAt(index);
+        }
+    }
+    return NULL;
+}
+
 status_t KeyLayoutMap::findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const {
-    const size_t N = mKeys.size();
+    const size_t N = mKeysByScanCode.size();
     for (size_t i=0; i<N; i++) {
-        if (mKeys.valueAt(i).keyCode == keyCode) {
-            outScanCodes->add(mKeys.keyAt(i));
+        if (mKeysByScanCode.valueAt(i).keyCode == keyCode) {
+            outScanCodes->add(mKeysByScanCode.keyAt(i));
         }
     }
     return NO_ERROR;
@@ -190,7 +207,7 @@
                 scanCodeToken.string());
         return BAD_VALUE;
     }
-    if (mMap->mKeys.indexOfKey(scanCode) >= 0) {
+    if (mMap->mKeysByScanCode.indexOfKey(scanCode) >= 0) {
         ALOGE("%s: Duplicate entry for key scan code '%s'.", mTokenizer->getLocation().string(),
                 scanCodeToken.string());
         return BAD_VALUE;
@@ -231,7 +248,7 @@
     Key key;
     key.keyCode = keyCode;
     key.flags = flags;
-    mMap->mKeys.add(scanCode, key);
+    mMap->mKeysByScanCode.add(scanCode, key);
     return NO_ERROR;
 }
 
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 9f2bacd..3910739 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -818,7 +818,10 @@
         indent[i] = ' ';
     }
     indent[count] = '\0';
-    DISPLAY_LIST_LOGD("%sStart display list (%p, %s)", (char*) indent + 2, this, mName.string());
+    Rect* clipRect = renderer.getClipRect();
+    DISPLAY_LIST_LOGD("%sStart display list (%p, %s), clipRect: %.0f, %.f, %.0f, %.0f",
+            (char*) indent + 2, this, mName.string(), clipRect->left, clipRect->top,
+            clipRect->right, clipRect->bottom);
 #endif
 
     renderer.startMark(mName.string());
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 06928df..ebb8eb7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -520,6 +520,7 @@
     layer->texCoords.set(0.0f, bounds.getHeight() / float(layer->getHeight()),
             bounds.getWidth() / float(layer->getWidth()), 0.0f);
     layer->setColorFilter(mColorFilter);
+    layer->setBlend(true);
 
     // Save the layer in the snapshot
     snapshot->flags |= Snapshot::kFlagIsLayer;
@@ -1058,6 +1059,10 @@
     return !mSnapshot->clipRect->isEmpty();
 }
 
+Rect* OpenGLRenderer::getClipRect() {
+    return mSnapshot->clipRect;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Drawing commands
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index b52d2b0..47927bb 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -101,6 +101,7 @@
     ANDROID_API const Rect& getClipBounds();
     ANDROID_API bool quickReject(float left, float top, float right, float bottom);
     virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
+    virtual Rect* getClipRect();
 
     virtual status_t drawDisplayList(DisplayList* displayList, uint32_t width, uint32_t height,
             Rect& dirty, int32_t flags, uint32_t level = 0);
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 11ecd1f..aef631f 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -23,6 +23,7 @@
 import android.net.Uri;
 
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 
@@ -57,7 +58,24 @@
      * @param path The path of the input media file.
      * @throws IllegalArgumentException If the path is invalid.
      */
-    public native void setDataSource(String path) throws IllegalArgumentException;
+    public void setDataSource(String path) throws IllegalArgumentException {
+        FileInputStream is = null;
+        try {
+            is = new FileInputStream(path);
+            FileDescriptor fd = is.getFD();
+            setDataSource(fd, 0, 0x7ffffffffffffffL);
+        } catch (FileNotFoundException fileEx) {
+            throw new IllegalArgumentException();
+        } catch (IOException ioEx) {
+            throw new IllegalArgumentException();
+        }
+
+        try {
+            if (is != null) {
+                is.close();
+            }
+        } catch (Exception e) {}
+    }
 
     /**
      * Sets the data source (URI) to use. Call this
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index c38f8f3..dd01db6 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -34,7 +34,9 @@
 import android.graphics.SurfaceTexture;
 import android.media.AudioManager;
 
+import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.util.Map;
@@ -847,8 +849,10 @@
      * As an alternative, the application could first open the file for reading,
      * and then use the file descriptor form {@link #setDataSource(FileDescriptor)}.
      */
-    public native void setDataSource(String path)
-            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
+    public void setDataSource(String path)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        setDataSource(path, null, null);
+    }
 
     /**
      * Sets the data source (file-path or http/rtsp URL) to use.
@@ -875,7 +879,20 @@
                 ++i;
             }
         }
-        _setDataSource(path, keys, values);
+        setDataSource(path, keys, values);
+    }
+
+    private void setDataSource(String path, String[] keys, String[] values)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        File file = new File(path);
+        if (file.exists()) {
+            FileInputStream is = new FileInputStream(file);
+            FileDescriptor fd = is.getFD();
+            setDataSource(fd);
+            is.close();
+        } else {
+            _setDataSource(path, keys, values);
+        }
     }
 
     private native void _setDataSource(
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 0dc3b65..297dadf 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -131,13 +131,6 @@
             "setDataSource failed");
 }
 
-
-static void android_media_MediaMetadataRetriever_setDataSource(
-        JNIEnv *env, jobject thiz, jstring path) {
-    android_media_MediaMetadataRetriever_setDataSourceAndHeaders(
-            env, thiz, path, NULL, NULL);
-}
-
 static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
 {
     ALOGV("setDataSource");
@@ -447,8 +440,6 @@
 
 // JNI mapping between Java methods and native methods
 static JNINativeMethod nativeMethods[] = {
-        {"setDataSource",   "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
-
         {
             "_setDataSource",
             "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 2e74ffd..5eadb3a 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -216,12 +216,6 @@
 }
 
 static void
-android_media_MediaPlayer_setDataSource(JNIEnv *env, jobject thiz, jstring path)
-{
-    android_media_MediaPlayer_setDataSourceAndHeaders(env, thiz, path, NULL, NULL);
-}
-
-static void
 android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
 {
     sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
@@ -825,8 +819,6 @@
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
-    {"setDataSource",       "(Ljava/lang/String;)V",            (void *)android_media_MediaPlayer_setDataSource},
-
     {
         "_setDataSource",
         "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
diff --git a/native/android/native_window.cpp b/native/android/native_window.cpp
index c58ee00..99c0fd3 100644
--- a/native/android/native_window.cpp
+++ b/native/android/native_window.cpp
@@ -60,13 +60,16 @@
 
 int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window, int32_t width,
         int32_t height, int32_t format) {
-    int32_t err = native_window_set_buffers_geometry(window, width, height, format);
+    int32_t err = native_window_set_buffers_format(window, format);
     if (!err) {
-        int mode = NATIVE_WINDOW_SCALING_MODE_FREEZE;
-        if (width && height) {
-            mode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
-        }
-        err = native_window_set_scaling_mode(window, mode);
+        err = native_window_set_buffers_user_dimensions(window, width, height);
+        if (!err) {
+            int mode = NATIVE_WINDOW_SCALING_MODE_FREEZE;
+            if (width && height) {
+                mode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
+            }
+            err = native_window_set_scaling_mode(window, mode);
+         }
     }
     return err;
 }
diff --git a/packages/BackupRestoreConfirmation/AndroidManifest.xml b/packages/BackupRestoreConfirmation/AndroidManifest.xml
index f3feee8..4fb26ae4 100644
--- a/packages/BackupRestoreConfirmation/AndroidManifest.xml
+++ b/packages/BackupRestoreConfirmation/AndroidManifest.xml
@@ -19,6 +19,7 @@
     package="com.android.backupconfirm" >
 
     <uses-permission android:name="android.permission.BACKUP" />
+    <uses-permission android:name="android.permission.CRYPT_KEEPER" />
 
     <application android:allowClearUserData="false"
                  android:allowBackup="false"
diff --git a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
index 7f1d059..82ac8cb 100644
--- a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
+++ b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
@@ -265,6 +265,7 @@
         } catch (Exception e) {
             // If we can't talk to the mount service we have a serious problem; fail
             // "secure" i.e. assuming that the device is encrypted.
+            Slog.e(TAG, "Unable to communicate with mount service: " + e.getMessage());
             return true;
         }
     }
diff --git a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java
new file mode 100644
index 0000000..d445d5c
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java
@@ -0,0 +1,52 @@
+/*
+ * 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.internal.policy.impl;
+
+import android.view.View;
+
+interface BiometricSensorUnlock {
+    // Returns 'true' if the biometric sensor is available and is selected by user.
+    public boolean installedAndSelected();
+
+    // Returns 'true' if the biometric sensor has started its unlock procedure but has not yet
+    // accepted or rejected the user.
+    public boolean isRunning();
+
+    // Show the interface, but don't start the unlock procedure.  The interface should disappear
+    // after the specified timeout.  If the timeout is 0, the interface shows until another event,
+    // such as calling hide(), causes it to disappear.
+    public void show(long timeoutMilliseconds);
+
+    // Hide the interface, if any, exposing the lockscreen.
+    public void hide();
+
+    // Stop the unlock procedure if running.  Returns 'true' if it was in fact running.
+    public boolean stop();
+
+    // Start the unlock procedure.  Returns ‘false’ if it can’t be started or if the backup should
+    // be used.
+    public boolean start(boolean suppressBiometricUnlock);
+
+    // Provide a view to work within.
+    public void initializeAreaView(View topView);
+
+    // Clean up any resources used by the biometric unlock.
+    public void cleanUp();
+
+    // Returns the Device Policy Manager quality (e.g. PASSWORD_QUALITY_BIOMETRIC_WEAK).
+    public int getQuality();
+}
diff --git a/policy/src/com/android/internal/policy/impl/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/FaceUnlock.java
index 31fbaafd..7b0a086 100644
--- a/policy/src/com/android/internal/policy/impl/FaceUnlock.java
+++ b/policy/src/com/android/internal/policy/impl/FaceUnlock.java
@@ -21,6 +21,7 @@
 import com.android.internal.policy.IFaceLockInterface;
 import com.android.internal.widget.LockPatternUtils;
 
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -33,7 +34,7 @@
 import android.util.Log;
 import android.view.View;
 
-public class FaceUnlock implements Handler.Callback {
+public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
 
     private static final boolean DEBUG = false;
     private static final String TAG = "FULLockscreen";
@@ -52,10 +53,6 @@
     private boolean mServiceRunning = false;
     private final Object mServiceRunningLock = new Object();
 
-    // Long enough to stay visible while dialer comes up
-    // Short enough to not be visible if the user goes back immediately
-    private final int VIEW_AREA_EMERGENCY_DIALER_TIMEOUT = 1000;
-
     // Long enough to stay visible while the service starts
     // Short enough to not have to wait long for backup if service fails to start or crashes
     // The service can take a couple of seconds to start on the first try after boot
@@ -80,22 +77,65 @@
         mHandler = new Handler(this);
     }
 
-    public void cleanUp() {
-        if (mService != null) {
-            try {
-                mService.unregisterCallback(mFaceLockCallback);
-            } catch (RemoteException e) {
-                // Not much we can do
-            }
-            stop();
-            mService = null;
-        }
+    // Indicates whether FaceLock is in use
+    public boolean installedAndSelected() {
+        return (mLockPatternUtils.usingBiometricWeak() &&
+                mLockPatternUtils.isBiometricWeakInstalled());
     }
 
-    /** When screen is turned on and focused, need to bind to FaceLock service if we are using
-     *  FaceLock, but only if we're not dealing with a call
-    */
-    public void activateIfAble(boolean hasOverlay) {
+    public boolean isRunning() {
+        return mServiceRunning;
+    }
+
+    // Shows the FaceLock area for a period of time
+    public void show(long timeoutMillis) {
+        showArea();
+        if (timeoutMillis > 0)
+            mHandler.sendEmptyMessageDelayed(MSG_HIDE_AREA_VIEW, timeoutMillis);
+    }
+
+    // Hides the FaceLock area immediately
+    public void hide() {
+        // Remove messages to prevent a delayed show message from undo-ing the hide
+        removeAreaDisplayMessages();
+        mHandler.sendEmptyMessage(MSG_HIDE_AREA_VIEW);
+    }
+
+    // Tells FaceLock to stop and then unbinds from the FaceLock service
+    public boolean stop() {
+        boolean wasRunning = false;
+        if (installedAndSelected()) {
+            stopUi();
+
+            if (mBoundToService) {
+                wasRunning = true;
+                if (DEBUG) Log.d(TAG, "before unbind from FaceLock service");
+                if (mService != null) {
+                    try {
+                        mService.unregisterCallback(mFaceLockCallback);
+                    } catch (RemoteException e) {
+                        // Not much we can do
+                    }
+                }
+                mContext.unbindService(mConnection);
+                if (DEBUG) Log.d(TAG, "after unbind from FaceLock service");
+                mBoundToService = false;
+            } else {
+                // This is usually not an error when this happens.  Sometimes we will tell it to
+                // unbind multiple times because it's called from both onWindowFocusChanged and
+                // onDetachedFromWindow.
+                if (DEBUG) Log.d(TAG, "Attempt to unbind from FaceLock when not bound");
+            }
+        }
+
+        return wasRunning;
+    }
+
+    /**
+     * When screen is turned on and focused, need to bind to FaceLock service if we are using
+     * FaceLock, but only if we're not dealing with a call
+     */
+    public boolean start(boolean suppressBiometricUnlock) {
         final boolean tooManyFaceUnlockTries = mUpdateMonitor.getMaxFaceUnlockAttemptsReached();
         final int failedBackupAttempts = mUpdateMonitor.getFailedAttempts();
         final boolean backupIsTimedOut =
@@ -103,42 +143,31 @@
         if (tooManyFaceUnlockTries) Log.i(TAG, "tooManyFaceUnlockTries: " + tooManyFaceUnlockTries);
         if (mUpdateMonitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE
                 && installedAndSelected()
-                && !hasOverlay
+                && !suppressBiometricUnlock
                 && !tooManyFaceUnlockTries
                 && !backupIsTimedOut) {
             bind();
 
             // Show FaceLock area, but only for a little bit so lockpattern will become visible if
             // FaceLock fails to start or crashes
-            showAreaWithTimeout(VIEW_AREA_SERVICE_TIMEOUT);
+            show(VIEW_AREA_SERVICE_TIMEOUT);
 
             // When switching between portrait and landscape view while FaceLock is running, the
             // screen will eventually go dark unless we poke the wakelock when FaceLock is
             // restarted
             mKeyguardScreenCallback.pokeWakelock();
         } else {
-            hideArea();
+            hide();
+            return false;
         }
-    }
 
-    public boolean isServiceRunning() {
-        return mServiceRunning;
-    }
-
-    public int viewAreaEmergencyDialerTimeout() {
-        return VIEW_AREA_EMERGENCY_DIALER_TIMEOUT;
-    }
-
-    // Indicates whether FaceLock is in use
-    public boolean installedAndSelected() {
-        return (mLockPatternUtils.usingBiometricWeak() &&
-                mLockPatternUtils.isBiometricWeakInstalled());
+        return true;
     }
 
     // Takes care of FaceLock area when layout is created
-    public void initializeAreaView(View view) {
+    public void initializeAreaView(View topView) {
         if (installedAndSelected()) {
-            mAreaView = view.findViewById(R.id.faceLockAreaView);
+            mAreaView = topView.findViewById(R.id.faceLockAreaView);
             if (mAreaView == null) {
                 Log.e(TAG, "Layout does not have areaView and FaceLock is enabled");
             }
@@ -147,13 +176,20 @@
         }
     }
 
-    // Stops FaceLock if it is running and reports back whether it was running or not
-    public boolean stopIfRunning() {
-        if (installedAndSelected() && mBoundToService) {
-            stopAndUnbind();
-            return true;
+    public void cleanUp() {
+        if (mService != null) {
+            try {
+                mService.unregisterCallback(mFaceLockCallback);
+            } catch (RemoteException e) {
+                // Not much we can do
+            }
+            stopUi();
+            mService = null;
         }
-        return false;
+    }
+
+    public int getQuality() {
+        return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
     }
 
     // Handles covering or exposing FaceLock area on the client side when FaceLock starts or stops
@@ -186,28 +222,15 @@
     }
 
     // Shows the FaceLock area immediately
-    public void showArea() {
+    private void showArea() {
         // Remove messages to prevent a delayed hide message from undo-ing the show
         removeAreaDisplayMessages();
         mHandler.sendEmptyMessage(MSG_SHOW_AREA_VIEW);
     }
 
-    // Hides the FaceLock area immediately
-    public void hideArea() {
-        // Remove messages to prevent a delayed show message from undo-ing the hide
-        removeAreaDisplayMessages();
-        mHandler.sendEmptyMessage(MSG_HIDE_AREA_VIEW);
-    }
-
-    // Shows the FaceLock area for a period of time
-    public void showAreaWithTimeout(long timeoutMillis) {
-        showArea();
-        mHandler.sendEmptyMessageDelayed(MSG_HIDE_AREA_VIEW, timeoutMillis);
-    }
-
     // Binds to FaceLock service.  This call does not tell it to start, but it causes the service
     // to call the onServiceConnected callback, which then starts FaceLock.
-    public void bind() {
+    private void bind() {
         if (installedAndSelected()) {
             if (!mBoundToService) {
                 if (DEBUG) Log.d(TAG, "before bind to FaceLock service");
@@ -223,32 +246,6 @@
         }
     }
 
-    // Tells FaceLock to stop and then unbinds from the FaceLock service
-    public void stopAndUnbind() {
-        if (installedAndSelected()) {
-            stop();
-
-            if (mBoundToService) {
-                if (DEBUG) Log.d(TAG, "before unbind from FaceLock service");
-                if (mService != null) {
-                    try {
-                        mService.unregisterCallback(mFaceLockCallback);
-                    } catch (RemoteException e) {
-                        // Not much we can do
-                    }
-                }
-                mContext.unbindService(mConnection);
-                if (DEBUG) Log.d(TAG, "after unbind from FaceLock service");
-                mBoundToService = false;
-            } else {
-                // This is usually not an error when this happens.  Sometimes we will tell it to
-                // unbind multiple times because it's called from both onWindowFocusChanged and
-                // onDetachedFromWindow.
-                if (DEBUG) Log.d(TAG, "Attempt to unbind from FaceLock when not bound");
-            }
-        }
-    }
-
     private ServiceConnection mConnection = new ServiceConnection() {
         // Completes connection, registers callback and starts FaceLock when service is bound
         @Override
@@ -268,7 +265,7 @@
                 int[] position;
                 position = new int[2];
                 mAreaView.getLocationInWindow(position);
-                start(mAreaView.getWindowToken(), position[0], position[1],
+                startUi(mAreaView.getWindowToken(), position[0], position[1],
                         mAreaView.getWidth(), mAreaView.getHeight());
             }
         }
@@ -286,7 +283,7 @@
     };
 
     // Tells the FaceLock service to start displaying its UI and perform recognition
-    public void start(IBinder windowToken, int x, int y, int w, int h) {
+    private void startUi(IBinder windowToken, int x, int y, int w, int h) {
         if (installedAndSelected()) {
             synchronized (mServiceRunningLock) {
                 if (!mServiceRunning) {
@@ -300,14 +297,14 @@
                     }
                     mServiceRunning = true;
                 } else {
-                    if (DEBUG) Log.w(TAG, "start() attempted while running");
+                    if (DEBUG) Log.w(TAG, "startUi() attempted while running");
                 }
             }
         }
     }
 
     // Tells the FaceLock service to stop displaying its UI and stop recognition
-    public void stop() {
+    private void stopUi() {
         if (installedAndSelected()) {
             // Note that attempting to stop FaceLock when it's not running is not an issue.
             // FaceLock can return, which stops it and then we try to stop it when the
@@ -333,7 +330,7 @@
         public void unlock() {
             if (DEBUG) Log.d(TAG, "FaceLock unlock()");
             showArea(); // Keep fallback covered
-            stopAndUnbind();
+            stop();
 
             mKeyguardScreenCallback.keyguardDone(true);
             mKeyguardScreenCallback.reportSuccessfulUnlockAttempt();
@@ -344,8 +341,8 @@
         @Override
         public void cancel() {
             if (DEBUG) Log.d(TAG, "FaceLock cancel()");
-            hideArea(); // Expose fallback
-            stopAndUnbind();
+            hide(); // Expose fallback
+            stop();
             mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
         }
 
@@ -355,8 +352,8 @@
         public void reportFailedAttempt() {
             if (DEBUG) Log.d(TAG, "FaceLock reportFailedAttempt()");
             mUpdateMonitor.reportFailedFaceUnlockAttempt();
-            hideArea(); // Expose fallback
-            stopAndUnbind();
+            hide(); // Expose fallback
+            stop();
             mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
         }
 
@@ -364,7 +361,7 @@
         @Override
         public void exposeFallback() {
             if (DEBUG) Log.d(TAG, "FaceLock exposeFallback()");
-            hideArea(); // Expose fallback
+            hide(); // Expose fallback
         }
 
         // Allows the Face Unlock service to poke the wake lock to keep the lockscreen alive
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
index 52fb875..5b9160d 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
@@ -567,6 +567,7 @@
         synchronized (KeyguardViewMediator.this) {
             if (mHidden != isHidden) {
                 mHidden = isHidden;
+                updateActivityLockScreenState();
                 adjustUserActivityLocked();
                 adjustStatusBarLocked();
             }
@@ -1162,6 +1163,14 @@
         }
     }
 
+    private void updateActivityLockScreenState() {
+        try {
+            ActivityManagerNative.getDefault().setLockScreenShown(
+                    mShowing && !mHidden);
+        } catch (RemoteException e) {
+        }
+    }
+
     /**
      * Handle message sent by {@link #showLocked}.
      * @see #SHOW
@@ -1173,6 +1182,7 @@
 
             mKeyguardViewManager.show();
             mShowing = true;
+            updateActivityLockScreenState();
             adjustUserActivityLocked();
             adjustStatusBarLocked();
             try {
@@ -1207,6 +1217,7 @@
 
             mKeyguardViewManager.hide();
             mShowing = false;
+            updateActivityLockScreenState();
             adjustUserActivityLocked();
             adjustStatusBarLocked();
         }
@@ -1324,6 +1335,7 @@
             if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
             mKeyguardViewManager.verifyUnlock();
             mShowing = true;
+            updateActivityLockScreenState();
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 39ede89..c382646 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -101,15 +101,17 @@
 
     private boolean mShowLockBeforeUnlock = false;
 
-    // The following were added to support FaceLock
-    private FaceUnlock mFaceUnlock;
-    private final Object mFaceLockStartupLock = new Object();
+    // Interface to a biometric sensor that can optionally be used to unlock the device
+    private BiometricSensorUnlock mBiometricUnlock;
+    private final Object mBiometricUnlockStartupLock = new Object();
+    // Long enough to stay visible while dialer comes up
+    // Short enough to not be visible if the user goes back immediately
+    private final int BIOMETRIC_AREA_EMERGENCY_DIALER_TIMEOUT = 1000;
 
     private boolean mRequiresSim;
-    //True if we have some sort of overlay on top of the Lockscreen
-    //Also true if we've activated a phone call, either emergency dialing or incoming
-    //This resets when the phone is turned off with no current call
-    private boolean mHasOverlay;
+    // True if the biometric unlock should not be displayed.  For example, if there is an overlay on
+    // lockscreen or the user is plugging in / unplugging the device.
+    private boolean mSupressBiometricUnlock;
     //True if a dialog is currently displaying on top of this window
     //Unlike other overlays, this does not close with a power button cycle
     private boolean mHasDialog = false;
@@ -308,15 +310,15 @@
         }
 
         public void takeEmergencyCallAction() {
-            mHasOverlay = true;
+            mSupressBiometricUnlock = true;
 
-            // Continue showing FaceLock area until dialer comes up or call is resumed
-            if (mFaceUnlock.installedAndSelected() && mFaceUnlock.isServiceRunning()) {
-                mFaceUnlock.showAreaWithTimeout(mFaceUnlock.viewAreaEmergencyDialerTimeout());
+            if (mBiometricUnlock.installedAndSelected() && mBiometricUnlock.isRunning()) {
+                // Continue covering backup lock until dialer comes up or call is resumed
+                mBiometricUnlock.show(BIOMETRIC_AREA_EMERGENCY_DIALER_TIMEOUT);
             }
 
-            // FaceLock must be stopped if it is running when emergency call is pressed
-            mFaceUnlock.stopAndUnbind();
+            // The biometric unlock must be stopped if it is running when emergency call is pressed
+            mBiometricUnlock.stop();
 
             pokeWakelock(EMERGENCY_CALL_TIMEOUT);
             if (TelephonyManager.getDefault().getCallState()
@@ -421,7 +423,7 @@
             LockPatternUtils lockPatternUtils, KeyguardWindowController controller) {
         super(context, callback);
 
-        mFaceUnlock = new FaceUnlock(context, updateMonitor, lockPatternUtils,
+        mBiometricUnlock = new FaceUnlock(context, updateMonitor, lockPatternUtils,
                 mKeyguardScreenCallback);
         mConfiguration = context.getResources().getConfiguration();
         mEnableFallback = false;
@@ -429,7 +431,7 @@
         mUpdateMonitor = updateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mWindowController = controller;
-        mHasOverlay = false;
+        mSupressBiometricUnlock = false;
         mPluggedIn = mUpdateMonitor.isDevicePluggedIn();
         mScreenOn = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)).isScreenOn();
 
@@ -528,8 +530,8 @@
         if (DEBUG) Log.d(TAG, "screen off");
         mScreenOn = false;
         mForgotPattern = false;
-        mHasOverlay = mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE ||
-                mHasDialog;
+        mSupressBiometricUnlock =
+                mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE || mHasDialog;
 
         // Emulate activity life-cycle for both lock and unlock screen.
         if (mLockScreen != null) {
@@ -541,25 +543,25 @@
 
         saveWidgetState();
 
-        // When screen is turned off, need to unbind from FaceLock service if using FaceLock
-        mFaceUnlock.stopAndUnbind();
+        // The biometric unlock must stop when screen turns off.
+        mBiometricUnlock.stop();
     }
 
     @Override
     public void onScreenTurnedOn() {
         if (DEBUG) Log.d(TAG, "screen on");
-        boolean runFaceLock = false;
-        //Make sure to start facelock iff the screen is both on and focused
-        synchronized(mFaceLockStartupLock) {
+        boolean startBiometricUnlock = false;
+        // Start the biometric unlock if and only if the screen is both on and focused
+        synchronized(mBiometricUnlockStartupLock) {
             mScreenOn = true;
-            runFaceLock = mWindowFocused;
+            startBiometricUnlock = mWindowFocused;
         }
 
         show();
 
         restoreWidgetState();
 
-        if (runFaceLock) mFaceUnlock.activateIfAble(mHasOverlay);
+        if (startBiometricUnlock) mBiometricUnlock.start(mSupressBiometricUnlock);
     }
 
     private void saveWidgetState() {
@@ -578,25 +580,26 @@
         }
     }
 
-    /** Unbind from facelock if something covers this window (such as an alarm)
-     * bind to facelock if the lockscreen window just came into focus, and the screen is on
+    /**
+     * Stop the biometric unlock if something covers this window (such as an alarm)
+     * Start the biometric unlock if the lockscreen window just came into focus and the screen is on
      */
     @Override
     public void onWindowFocusChanged (boolean hasWindowFocus) {
         if (DEBUG) Log.d(TAG, hasWindowFocus ? "focused" : "unfocused");
-        boolean runFaceLock = false;
-        //Make sure to start facelock iff the screen is both on and focused
-        synchronized(mFaceLockStartupLock) {
-            if(mScreenOn && !mWindowFocused) runFaceLock = hasWindowFocus;
+        boolean startBiometricUnlock = false;
+        // Start the biometric unlock if and only if the screen is both on and focused
+        synchronized(mBiometricUnlockStartupLock) {
+            if (mScreenOn && !mWindowFocused) startBiometricUnlock = hasWindowFocus;
             mWindowFocused = hasWindowFocus;
         }
         if (!hasWindowFocus) {
-            mHasOverlay = true;
-            mFaceUnlock.stopAndUnbind();
-            mFaceUnlock.hideArea();
+            mSupressBiometricUnlock = true;
+            mBiometricUnlock.stop();
+            mBiometricUnlock.hide();
         } else {
             mHasDialog = false;
-            if (runFaceLock) mFaceUnlock.activateIfAble(mHasOverlay);
+            if (startBiometricUnlock) mBiometricUnlock.start(mSupressBiometricUnlock);
         }
     }
 
@@ -610,14 +613,14 @@
             ((KeyguardScreen) mUnlockScreen).onResume();
         }
 
-        if (mFaceUnlock.installedAndSelected() && !mHasOverlay) {
+        if (mBiometricUnlock.installedAndSelected() && !mSupressBiometricUnlock) {
             // Note that show() gets called before the screen turns off to set it up for next time
-            // it is turned on.  We don't want to set a timeout on the FaceLock area here because it
-            // may be gone by the time the screen is turned on again.  We set the timeout when the
-            // screen turns on instead.
-            mFaceUnlock.showArea();
+            // it is turned on.  We don't want to set a timeout on the biometric unlock here because
+            // it may be gone by the time the screen is turned on again.  We set the timeout when
+            // the screen turns on instead.
+            mBiometricUnlock.show(0);
         } else {
-            mFaceUnlock.hideArea();
+            mBiometricUnlock.hide();
         }
     }
 
@@ -651,9 +654,9 @@
 
         removeCallbacks(mRecreateRunnable);
 
-        // When view is hidden, need to unbind from FaceLock service if we are using FaceLock
+        // When view is hidden, we need to stop the biometric unlock
         // e.g., when device becomes unlocked
-        mFaceUnlock.stopAndUnbind();
+        mBiometricUnlock.stop();
 
         super.onDetachedFromWindow();
     }
@@ -670,16 +673,19 @@
 
     InfoCallbackImpl mInfoCallback = new InfoCallbackImpl() {
 
-        /** When somebody plugs in or unplugs the device, we don't want to display faceunlock */
+        /**
+         * When somebody plugs in or unplugs the device, we don't want to display the biometric
+         * unlock.
+         */
         @Override
         public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn,
                 int batteryLevel) {
-            mHasOverlay |= mPluggedIn != pluggedIn;
+            mSupressBiometricUnlock |= mPluggedIn != pluggedIn;
             mPluggedIn = pluggedIn;
-            //If it's already running, don't close it down: the unplug didn't start it
-            if (!mFaceUnlock.isServiceRunning()) {
-                mFaceUnlock.stopAndUnbind();
-                mFaceUnlock.hideArea();
+            // If it's already running, don't close it down: the unplug didn't start it
+            if (!mBiometricUnlock.isRunning()) {
+                mBiometricUnlock.stop();
+                mBiometricUnlock.hide();
             }
         }
 
@@ -690,20 +696,20 @@
                     | (mUpdateMonitor.isClockVisible() ? View.STATUS_BAR_DISABLE_CLOCK : 0));
         }
 
-        //We need to stop faceunlock when a phonecall comes in
+        // We need to stop the biometric unlock when a phone call comes in
         @Override
         public void onPhoneStateChanged(int phoneState) {
             if (DEBUG) Log.d(TAG, "phone state: " + phoneState);
             if(phoneState == TelephonyManager.CALL_STATE_RINGING) {
-                mHasOverlay = true;
-                mFaceUnlock.stopAndUnbind();
-                mFaceUnlock.hideArea();
+                mSupressBiometricUnlock = true;
+                mBiometricUnlock.stop();
+                mBiometricUnlock.hide();
             }
         }
 
         @Override
         public void onUserChanged(int userId) {
-            mFaceUnlock.stopAndUnbind();
+            mBiometricUnlock.stop();
             mLockPatternUtils.setCurrentUser(userId);
             updateScreen(getInitialMode(), true);
         }
@@ -766,7 +772,7 @@
             mUnlockScreen = null;
         }
         mUpdateMonitor.removeCallback(this);
-        mFaceUnlock.cleanUp();
+        mBiometricUnlock.cleanUp();
     }
 
     private boolean isSecure() {
@@ -816,10 +822,10 @@
         final UnlockMode unlockMode = getUnlockMode();
         if (mode == Mode.UnlockScreen && unlockMode != UnlockMode.Unknown) {
             if (force || mUnlockScreen == null || unlockMode != mUnlockScreenMode) {
-                boolean restartFaceLock = mFaceUnlock.stopIfRunning();
+                boolean restartBiometricUnlock = mBiometricUnlock.stop();
                 recreateUnlockScreen(unlockMode);
-                if (restartFaceLock) {
-                    mFaceUnlock.activateIfAble(mHasOverlay);
+                if (restartBiometricUnlock) {
+                    mBiometricUnlock.start(mSupressBiometricUnlock);
                 }
             }
         }
@@ -933,7 +939,8 @@
             throw new IllegalArgumentException("unknown unlock mode " + unlockMode);
         }
         initializeTransportControlView(unlockView);
-        mFaceUnlock.initializeAreaView(unlockView); // Only shows view if FaceLock is enabled
+        // Only shows view if the biometric unlock is enabled
+        mBiometricUnlock.initializeAreaView(unlockView);
 
         mUnlockScreenMode = unlockMode;
         return unlockView;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 1891146..0a63840 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -1628,9 +1628,10 @@
         return 0;
     }
 
-    public Animation createForceHideEnterAnimation() {
-        return AnimationUtils.loadAnimation(mContext,
-                com.android.internal.R.anim.lock_screen_behind_enter);
+    public Animation createForceHideEnterAnimation(boolean onWallpaper) {
+        return AnimationUtils.loadAnimation(mContext, onWallpaper
+                ? com.android.internal.R.anim.lock_screen_wallpaper_behind_enter
+                : com.android.internal.R.anim.lock_screen_behind_enter);
     }
     
     static ITelephony getTelephonyService() {
@@ -2245,12 +2246,11 @@
             vf.right = mStableRight;
             vf.bottom = mStableBottom;
 
+            // Let the status bar determine its size.
             mStatusBar.computeFrameLw(pf, df, vf, vf);
-            final Rect r = mStatusBar.getFrameLw();
 
-            // Compute the stable dimensions whether or not the status bar is hidden.
-            if (mDockTop == r.top) mStableTop = r.bottom;
-            else if (mDockBottom == r.bottom) mStableBottom = r.top;
+            // For layout, the status bar is always at the top with our fixed height.
+            mStableTop = mUnrestrictedScreenTop + mStatusBarHeight;
 
             // If the status bar is hidden, we don't want to cause
             // windows behind it to scroll.
@@ -2258,8 +2258,7 @@
                 // Status bar may go away, so the screen area it occupies
                 // is available to apps but just covering them when the
                 // status bar is visible.
-                if (mDockTop == r.top) mDockTop = r.bottom;
-                else if (mDockBottom == r.bottom) mDockBottom = r.top;
+                mDockTop = mUnrestrictedScreenTop + mStatusBarHeight;
                 
                 mContentTop = mCurTop = mDockTop;
                 mContentBottom = mCurBottom = mDockBottom;
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index 744f2ad..fbffc94 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -117,6 +117,8 @@
         }
     }
     identifier.descriptor = sha1(rawDescriptor);
+    ALOGV("Created descriptor: raw=%s, cooked=%s", rawDescriptor.string(),
+            identifier.descriptor.string());
 }
 
 // --- Global Functions ---
@@ -434,58 +436,35 @@
     return false;
 }
 
-status_t EventHub::mapKey(int32_t deviceId, int scancode,
-        int32_t* outKeycode, uint32_t* outFlags) const
-{
+status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+        int32_t* outKeycode, uint32_t* outFlags) const {
     AutoMutex _l(mLock);
     Device* device = getDeviceLocked(deviceId);
-    
+
     if (device && device->keyMap.haveKeyLayout()) {
-        status_t err = device->keyMap.keyLayoutMap->mapKey(scancode, outKeycode, outFlags);
+        status_t err = device->keyMap.keyLayoutMap->mapKey(
+                scanCode, usageCode, outKeycode, outFlags);
         if (err == NO_ERROR) {
             return NO_ERROR;
         }
     }
-    
-    if (mBuiltInKeyboardId != NO_BUILT_IN_KEYBOARD) {
-        device = getDeviceLocked(mBuiltInKeyboardId);
-        
-        if (device && device->keyMap.haveKeyLayout()) {
-            status_t err = device->keyMap.keyLayoutMap->mapKey(scancode, outKeycode, outFlags);
-            if (err == NO_ERROR) {
-                return NO_ERROR;
-            }
-        }
-    }
-    
+
     *outKeycode = 0;
     *outFlags = 0;
     return NAME_NOT_FOUND;
 }
 
-status_t EventHub::mapAxis(int32_t deviceId, int scancode, AxisInfo* outAxisInfo) const
-{
+status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const {
     AutoMutex _l(mLock);
     Device* device = getDeviceLocked(deviceId);
 
     if (device && device->keyMap.haveKeyLayout()) {
-        status_t err = device->keyMap.keyLayoutMap->mapAxis(scancode, outAxisInfo);
+        status_t err = device->keyMap.keyLayoutMap->mapAxis(scanCode, outAxisInfo);
         if (err == NO_ERROR) {
             return NO_ERROR;
         }
     }
 
-    if (mBuiltInKeyboardId != NO_BUILT_IN_KEYBOARD) {
-        device = getDeviceLocked(mBuiltInKeyboardId);
-
-        if (device && device->keyMap.haveKeyLayout()) {
-            status_t err = device->keyMap.keyLayoutMap->mapAxis(scancode, outAxisInfo);
-            if (err == NO_ERROR) {
-                return NO_ERROR;
-            }
-        }
-    }
-
     return NAME_NOT_FOUND;
 }
 
@@ -729,16 +708,8 @@
 #endif
                         event->deviceId = deviceId;
                         event->type = iev.type;
-                        event->scanCode = iev.code;
+                        event->code = iev.code;
                         event->value = iev.value;
-                        event->keyCode = AKEYCODE_UNKNOWN;
-                        event->flags = 0;
-                        if (iev.type == EV_KEY && device->keyMap.haveKeyLayout()) {
-                            status_t err = device->keyMap.keyLayoutMap->mapKey(iev.code,
-                                        &event->keyCode, &event->flags);
-                            ALOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n",
-                                    iev.code, event->keyCode, event->flags, err);
-                        }
                         event += 1;
                     }
                     capacity -= count;
@@ -749,6 +720,11 @@
                         break;
                     }
                 }
+            } else if (eventItem.events & EPOLLHUP) {
+                ALOGI("Removing device %s due to epoll hang-up event.",
+                        device->identifier.name.string());
+                deviceChanged = true;
+                closeDeviceLocked(device);
             } else {
                 ALOGW("Received unexpected epoll event 0x%08x for device %s.",
                         eventItem.events, device->identifier.name.string());
@@ -960,7 +936,7 @@
     ALOGV("  name:       \"%s\"\n", identifier.name.string());
     ALOGV("  location:   \"%s\"\n", identifier.location.string());
     ALOGV("  unique id:  \"%s\"\n", identifier.uniqueId.string());
-    ALOGV("  descriptor: \"%s\" (%s)\n", identifier.descriptor.string(), rawDescriptor.string());
+    ALOGV("  descriptor: \"%s\"\n", identifier.descriptor.string());
     ALOGV("  driver:     v%d.%d.%d\n",
         driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff);
 
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index c35df109..88159e7 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -39,8 +39,8 @@
 
 /* Convenience constants. */
 
-#define BTN_FIRST 0x100  // first button scancode
-#define BTN_LAST 0x15f   // last button scancode
+#define BTN_FIRST 0x100  // first button code
+#define BTN_LAST 0x15f   // last button code
 
 namespace android {
 
@@ -58,10 +58,8 @@
     nsecs_t when;
     int32_t deviceId;
     int32_t type;
-    int32_t scanCode;
-    int32_t keyCode;
+    int32_t code;
     int32_t value;
-    uint32_t flags;
 };
 
 /* Describes an absolute axis. */
@@ -173,10 +171,10 @@
 
     virtual bool hasInputProperty(int32_t deviceId, int property) const = 0;
 
-    virtual status_t mapKey(int32_t deviceId, int scancode,
+    virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
             int32_t* outKeycode, uint32_t* outFlags) const = 0;
 
-    virtual status_t mapAxis(int32_t deviceId, int scancode,
+    virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
             AxisInfo* outAxisInfo) const = 0;
 
     // Sets devices that are excluded from opening.
@@ -252,10 +250,10 @@
 
     virtual bool hasInputProperty(int32_t deviceId, int property) const;
 
-    virtual status_t mapKey(int32_t deviceId, int scancode,
+    virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
             int32_t* outKeycode, uint32_t* outFlags) const;
 
-    virtual status_t mapAxis(int32_t deviceId, int scancode,
+    virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
             AxisInfo* outAxisInfo) const;
 
     virtual void setExcludedDevices(const Vector<String8>& devices);
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 42512d8..71eba52 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -237,7 +237,8 @@
         const sp<InputReaderPolicyInterface>& policy,
         const sp<InputListenerInterface>& listener) :
         mContext(this), mEventHub(eventHub), mPolicy(policy),
-        mGlobalMetaState(0), mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
+        mGlobalMetaState(0), mGeneration(1),
+        mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
         mConfigurationChangesToRefresh(0) {
     mQueuedListener = new QueuedInputListener(listener);
 
@@ -257,18 +258,24 @@
 }
 
 void InputReader::loopOnce() {
+    int32_t oldGeneration;
     int32_t timeoutMillis;
+    bool inputDevicesChanged = false;
+    Vector<InputDeviceInfo> inputDevices;
     { // acquire lock
         AutoMutex _l(mLock);
 
+        oldGeneration = mGeneration;
+        timeoutMillis = -1;
+
         uint32_t changes = mConfigurationChangesToRefresh;
         if (changes) {
             mConfigurationChangesToRefresh = 0;
+            timeoutMillis = 0;
             refreshConfigurationLocked(changes);
         }
 
-        timeoutMillis = -1;
-        if (mNextTimeout != LLONG_MAX) {
+        if (timeoutMillis < 0 && mNextTimeout != LLONG_MAX) {
             nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
             timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
         }
@@ -283,7 +290,8 @@
         if (count) {
             processEventsLocked(mEventBuffer, count);
         }
-        if (!count || timeoutMillis == 0) {
+
+        if (mNextTimeout != LLONG_MAX) {
             nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
             if (now >= mNextTimeout) {
 #if DEBUG_RAW_EVENTS
@@ -293,8 +301,18 @@
                 timeoutExpiredLocked(now);
             }
         }
+
+        if (oldGeneration != mGeneration) {
+            inputDevicesChanged = true;
+            getInputDevicesLocked(inputDevices);
+        }
     } // release lock
 
+    // Send out a message that the describes the changed input devices.
+    if (inputDevicesChanged) {
+        mPolicy->notifyInputDevicesChanged(inputDevices);
+    }
+
     // Flush queued events out to the listener.
     // This must happen outside of the lock because the listener could potentially call
     // back into the InputReader's methods, such as getScanCodeState, or become blocked
@@ -344,6 +362,12 @@
 }
 
 void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
+    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+    if (deviceIndex >= 0) {
+        ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
+        return;
+    }
+
     InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
     uint32_t classes = mEventHub->getDeviceClasses(deviceId);
 
@@ -359,27 +383,22 @@
                 identifier.name.string(), device->getSources());
     }
 
-    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
-    if (deviceIndex < 0) {
-        mDevices.add(deviceId, device);
-    } else {
-        ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
-        delete device;
-        return;
-    }
+    mDevices.add(deviceId, device);
+    bumpGenerationLocked();
 }
 
 void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) {
     InputDevice* device = NULL;
     ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
-    if (deviceIndex >= 0) {
-        device = mDevices.valueAt(deviceIndex);
-        mDevices.removeItemsAt(deviceIndex, 1);
-    } else {
+    if (deviceIndex < 0) {
         ALOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
         return;
     }
 
+    device = mDevices.valueAt(deviceIndex);
+    mDevices.removeItemsAt(deviceIndex, 1);
+    bumpGenerationLocked();
+
     if (device->isIgnored()) {
         ALOGI("Device removed: id=%d, name='%s' (ignored non-input device)",
                 device->getId(), device->getName().string());
@@ -394,7 +413,8 @@
 
 InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
         const InputDeviceIdentifier& identifier, uint32_t classes) {
-    InputDevice* device = new InputDevice(&mContext, deviceId, identifier, classes);
+    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
+            identifier, classes);
 
     // External devices.
     if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
@@ -577,39 +597,30 @@
     }
 }
 
+int32_t InputReader::bumpGenerationLocked() {
+    return ++mGeneration;
+}
+
 void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) {
     AutoMutex _l(mLock);
 
     *outConfiguration = mInputConfiguration;
 }
 
-status_t InputReader::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) {
+void InputReader::getInputDevices(Vector<InputDeviceInfo>& outInputDevices) {
     AutoMutex _l(mLock);
-
-    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
-    if (deviceIndex < 0) {
-        return NAME_NOT_FOUND;
-    }
-
-    InputDevice* device = mDevices.valueAt(deviceIndex);
-    if (device->isIgnored()) {
-        return NAME_NOT_FOUND;
-    }
-
-    device->getDeviceInfo(outDeviceInfo);
-    return OK;
+    getInputDevicesLocked(outInputDevices);
 }
 
-void InputReader::getInputDeviceIds(Vector<int32_t>& outDeviceIds) {
-    AutoMutex _l(mLock);
-
-    outDeviceIds.clear();
+void InputReader::getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices) {
+    outInputDevices.clear();
 
     size_t numDevices = mDevices.size();
     for (size_t i = 0; i < numDevices; i++) {
         InputDevice* device = mDevices.valueAt(i);
         if (!device->isIgnored()) {
-            outDeviceIds.add(device->getId());
+            outInputDevices.push();
+            device->getDeviceInfo(&outInputDevices.editTop());
         }
     }
 }
@@ -824,6 +835,11 @@
     mReader->requestTimeoutAtTimeLocked(when);
 }
 
+int32_t InputReader::ContextImpl::bumpGeneration() {
+    // lock is already held by the input loop
+    return mReader->bumpGenerationLocked();
+}
+
 InputReaderPolicyInterface* InputReader::ContextImpl::getPolicy() {
     return mReader->mPolicy.get();
 }
@@ -854,9 +870,10 @@
 
 // --- InputDevice ---
 
-InputDevice::InputDevice(InputReaderContext* context, int32_t id,
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
         const InputDeviceIdentifier& identifier, uint32_t classes) :
-        mContext(context), mId(id), mIdentifier(identifier), mClasses(classes),
+        mContext(context), mId(id), mGeneration(generation),
+        mIdentifier(identifier), mClasses(classes),
         mSources(0), mIsExternal(false), mDropUntilNextSync(false) {
 }
 
@@ -874,6 +891,7 @@
 
     dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(),
             deviceInfo.getName().string());
+    dump.appendFormat(INDENT2 "Generation: %d\n", mGeneration);
     dump.appendFormat(INDENT2 "IsExternal: %s\n", toString(mIsExternal));
     dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
     dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
@@ -946,14 +964,12 @@
     size_t numMappers = mMappers.size();
     for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
 #if DEBUG_RAW_EVENTS
-        ALOGD("Input event: device=%d type=0x%04x scancode=0x%04x "
-                "keycode=0x%04x value=0x%08x flags=0x%08x",
-                rawEvent->deviceId, rawEvent->type, rawEvent->scanCode, rawEvent->keyCode,
-                rawEvent->value, rawEvent->flags);
+        ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x",
+                rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value);
 #endif
 
         if (mDropUntilNextSync) {
-            if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
+            if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                 mDropUntilNextSync = false;
 #if DEBUG_RAW_EVENTS
                 ALOGD("Recovered from input event buffer overrun.");
@@ -963,7 +979,7 @@
                 ALOGD("Dropped input event while waiting for next input sync.");
 #endif
             }
-        } else if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_DROPPED) {
+        } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
             ALOGI("Detected input event buffer overrun for device %s.", getName().string());
             mDropUntilNextSync = true;
             reset(rawEvent->when);
@@ -985,7 +1001,7 @@
 }
 
 void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
-    outDeviceInfo->initialize(mId, mIdentifier.name, mIdentifier.descriptor);
+    outDeviceInfo->initialize(mId, mGeneration, mIdentifier.name, mIdentifier.descriptor);
 
     size_t numMappers = mMappers.size();
     for (size_t i = 0; i < numMappers; i++) {
@@ -1056,6 +1072,10 @@
     }
 }
 
+void InputDevice::bumpGeneration() {
+    mGeneration = mContext->bumpGeneration();
+}
+
 void InputDevice::notifyReset(nsecs_t when) {
     NotifyDeviceResetArgs args(when, mId);
     mContext->getListener()->notifyDeviceReset(&args);
@@ -1092,7 +1112,7 @@
 
 void CursorButtonAccumulator::process(const RawEvent* rawEvent) {
     if (rawEvent->type == EV_KEY) {
-        switch (rawEvent->scanCode) {
+        switch (rawEvent->code) {
         case BTN_LEFT:
             mBtnLeft = rawEvent->value;
             break;
@@ -1159,7 +1179,7 @@
 
 void CursorMotionAccumulator::process(const RawEvent* rawEvent) {
     if (rawEvent->type == EV_REL) {
-        switch (rawEvent->scanCode) {
+        switch (rawEvent->code) {
         case REL_X:
             mRelX = rawEvent->value;
             break;
@@ -1198,7 +1218,7 @@
 
 void CursorScrollAccumulator::process(const RawEvent* rawEvent) {
     if (rawEvent->type == EV_REL) {
-        switch (rawEvent->scanCode) {
+        switch (rawEvent->code) {
         case REL_WHEEL:
             mRelWheel = rawEvent->value;
             break;
@@ -1261,7 +1281,7 @@
 
 void TouchButtonAccumulator::process(const RawEvent* rawEvent) {
     if (rawEvent->type == EV_KEY) {
-        switch (rawEvent->scanCode) {
+        switch (rawEvent->code) {
         case BTN_TOUCH:
             mBtnTouch = rawEvent->value;
             break;
@@ -1467,7 +1487,7 @@
 
 void SingleTouchMotionAccumulator::process(const RawEvent* rawEvent) {
     if (rawEvent->type == EV_ABS) {
-        switch (rawEvent->scanCode) {
+        switch (rawEvent->code) {
         case ABS_X:
             mAbsX = rawEvent->value;
             break;
@@ -1551,7 +1571,7 @@
     if (rawEvent->type == EV_ABS) {
         bool newSlot = false;
         if (mUsingSlotsProtocol) {
-            if (rawEvent->scanCode == ABS_MT_SLOT) {
+            if (rawEvent->code == ABS_MT_SLOT) {
                 mCurrentSlot = rawEvent->value;
                 newSlot = true;
             }
@@ -1570,7 +1590,7 @@
         } else {
             Slot* slot = &mSlots[mCurrentSlot];
 
-            switch (rawEvent->scanCode) {
+            switch (rawEvent->code) {
             case ABS_MT_POSITION_X:
                 slot->mInUse = true;
                 slot->mAbsMTPositionX = rawEvent->value;
@@ -1626,7 +1646,7 @@
                 break;
             }
         }
-    } else if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_MT_REPORT) {
+    } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
         // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
         mCurrentSlot += 1;
     }
@@ -1730,6 +1750,10 @@
     return getEventHub()->getAbsoluteAxisInfo(getDeviceId(), axis, axisInfo);
 }
 
+void InputMapper::bumpGeneration() {
+    mDevice->bumpGeneration();
+}
+
 void InputMapper::dumpRawAbsoluteAxisInfo(String8& dump,
         const RawAbsoluteAxisInfo& axis, const char* name) {
     if (axis.valid) {
@@ -1757,7 +1781,7 @@
 void SwitchInputMapper::process(const RawEvent* rawEvent) {
     switch (rawEvent->type) {
     case EV_SW:
-        processSwitch(rawEvent->when, rawEvent->scanCode, rawEvent->value);
+        processSwitch(rawEvent->when, rawEvent->code, rawEvent->value);
         break;
     }
 }
@@ -1849,6 +1873,7 @@
     mMetaState = AMETA_NONE;
     mDownTime = 0;
     mKeyDowns.clear();
+    mCurrentHidUsage = 0;
 
     resetLedState();
 
@@ -1858,13 +1883,32 @@
 void KeyboardInputMapper::process(const RawEvent* rawEvent) {
     switch (rawEvent->type) {
     case EV_KEY: {
-        int32_t scanCode = rawEvent->scanCode;
+        int32_t scanCode = rawEvent->code;
+        int32_t usageCode = mCurrentHidUsage;
+        mCurrentHidUsage = 0;
+
         if (isKeyboardOrGamepadKey(scanCode)) {
-            processKey(rawEvent->when, rawEvent->value != 0, rawEvent->keyCode, scanCode,
-                    rawEvent->flags);
+            int32_t keyCode;
+            uint32_t flags;
+            if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
+                keyCode = AKEYCODE_UNKNOWN;
+                flags = 0;
+            }
+            processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
         }
         break;
     }
+    case EV_MSC: {
+        if (rawEvent->code == MSC_SCAN) {
+            mCurrentHidUsage = rawEvent->value;
+        }
+        break;
+    }
+    case EV_SYN: {
+        if (rawEvent->code == SYN_REPORT) {
+            mCurrentHidUsage = 0;
+        }
+    }
     }
 }
 
@@ -2119,6 +2163,7 @@
         } else {
             mOrientation = DISPLAY_ORIENTATION_0;
         }
+        bumpGeneration();
     }
 }
 
@@ -2183,7 +2228,7 @@
     mCursorMotionAccumulator.process(rawEvent);
     mCursorScrollAccumulator.process(rawEvent);
 
-    if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
+    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
         sync(rawEvent->when);
     }
 }
@@ -2980,6 +3025,7 @@
 
         // Inform the dispatcher about the changes.
         *outResetNeeded = true;
+        bumpGeneration();
     }
 }
 
@@ -3016,8 +3062,7 @@
         virtualKey.scanCode = virtualKeyDefinition.scanCode;
         int32_t keyCode;
         uint32_t flags;
-        if (getEventHub()->mapKey(getDeviceId(), virtualKey.scanCode,
-                & keyCode, & flags)) {
+        if (getEventHub()->mapKey(getDeviceId(), virtualKey.scanCode, 0, &keyCode, &flags)) {
             ALOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring",
                     virtualKey.scanCode);
             mVirtualKeys.pop(); // drop the key
@@ -3311,7 +3356,7 @@
     mCursorScrollAccumulator.process(rawEvent);
     mTouchButtonAccumulator.process(rawEvent);
 
-    if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
+    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
         sync(rawEvent->when);
     }
 }
@@ -5920,7 +5965,7 @@
 void JoystickInputMapper::process(const RawEvent* rawEvent) {
     switch (rawEvent->type) {
     case EV_ABS: {
-        ssize_t index = mAxes.indexOfKey(rawEvent->scanCode);
+        ssize_t index = mAxes.indexOfKey(rawEvent->code);
         if (index >= 0) {
             Axis& axis = mAxes.editValueAt(index);
             float newValue, highNewValue;
@@ -5956,7 +6001,7 @@
     }
 
     case EV_SYN:
-        switch (rawEvent->scanCode) {
+        switch (rawEvent->code) {
         case SYN_REPORT:
             sync(rawEvent->when, false /*force*/);
             break;
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 8520a75..d29776d8 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -209,6 +209,11 @@
 
     /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
+
+    /* Notifies the input reader policy that some input devices have changed
+     * and provides information about all current input devices.
+     */
+    virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) = 0;
 };
 
 
@@ -240,16 +245,11 @@
      */
     virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0;
 
-    /* Gets information about the specified input device.
-     * Returns OK if the device information was obtained or NAME_NOT_FOUND if there
-     * was no such device.
+    /* Gets information about all input devices.
      *
      * This method may be called on any thread (usually by the input manager).
      */
-    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0;
-
-    /* Gets the list of all registered device ids. */
-    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0;
+    virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices) = 0;
 
     /* Query current input state. */
     virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
@@ -288,6 +288,7 @@
     virtual void fadePointer() = 0;
 
     virtual void requestTimeoutAtTime(nsecs_t when) = 0;
+    virtual int32_t bumpGeneration() = 0;
 
     virtual InputReaderPolicyInterface* getPolicy() = 0;
     virtual InputListenerInterface* getListener() = 0;
@@ -319,9 +320,7 @@
     virtual void loopOnce();
 
     virtual void getInputConfiguration(InputConfiguration* outConfiguration);
-
-    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo);
-    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds);
+    virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices);
 
     virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
             int32_t scanCode);
@@ -353,6 +352,7 @@
                 InputDevice* device, int32_t keyCode, int32_t scanCode);
         virtual void fadePointer();
         virtual void requestTimeoutAtTime(nsecs_t when);
+        virtual int32_t bumpGeneration();
         virtual InputReaderPolicyInterface* getPolicy();
         virtual InputListenerInterface* getListener();
         virtual EventHubInterface* getEventHub();
@@ -393,9 +393,14 @@
 
     void fadePointerLocked();
 
+    int32_t mGeneration;
+    int32_t bumpGenerationLocked();
+
     InputConfiguration mInputConfiguration;
     void updateInputConfigurationLocked();
 
+    void getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices);
+
     nsecs_t mDisableVirtualKeysTimeout;
     void disableVirtualKeysUntilLocked(nsecs_t time);
     bool shouldDropVirtualKeyLocked(nsecs_t now,
@@ -432,12 +437,13 @@
 /* Represents the state of a single input device. */
 class InputDevice {
 public:
-    InputDevice(InputReaderContext* context, int32_t id,
+    InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
             const InputDeviceIdentifier& identifier, uint32_t classes);
     ~InputDevice();
 
     inline InputReaderContext* getContext() { return mContext; }
     inline int32_t getId() { return mId; }
+    inline int32_t getGeneration() { return mGeneration; }
     inline const String8& getName() { return mIdentifier.name; }
     inline uint32_t getClasses() { return mClasses; }
     inline uint32_t getSources() { return mSources; }
@@ -465,6 +471,8 @@
 
     void fadePointer();
 
+    void bumpGeneration();
+
     void notifyReset(nsecs_t when);
 
     inline const PropertyMap& getConfiguration() { return mConfiguration; }
@@ -487,6 +495,7 @@
 private:
     InputReaderContext* mContext;
     int32_t mId;
+    int32_t mGeneration;
     InputDeviceIdentifier mIdentifier;
     uint32_t mClasses;
 
@@ -849,6 +858,7 @@
     InputReaderContext* mContext;
 
     status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo);
+    void bumpGeneration();
 
     static void dumpRawAbsoluteAxisInfo(String8& dump,
             const RawAbsoluteAxisInfo& axis, const char* name);
@@ -904,6 +914,8 @@
     int32_t mMetaState;
     nsecs_t mDownTime; // time of most recent key down
 
+    int32_t mCurrentHidUsage; // most recent HID usage seen this packet, or 0 if none
+
     struct LedState {
         bool avail; // led is available
         bool on;    // we think the led is currently on
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 057ad18..e59af4e 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -127,6 +127,7 @@
 class FakeInputReaderPolicy : public InputReaderPolicyInterface {
     InputReaderConfiguration mConfig;
     KeyedVector<int32_t, sp<FakePointerController> > mPointerControllers;
+    Vector<InputDeviceInfo> mInputDevices;
 
 protected:
     virtual ~FakeInputReaderPolicy() { }
@@ -141,10 +142,6 @@
         mConfig.setDisplayInfo(displayId, true /*external*/, width, height, orientation);
     }
 
-    virtual nsecs_t getVirtualKeyQuietTime() {
-        return 0;
-    }
-
     void addExcludedDeviceName(const String8& deviceName) {
         mConfig.excludedDeviceNames.push(deviceName);
     }
@@ -157,6 +154,10 @@
         return &mConfig;
     }
 
+    const Vector<InputDeviceInfo>& getInputDevices() const {
+        return mInputDevices;
+    }
+
 private:
     virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) {
         *outConfig = mConfig;
@@ -165,6 +166,10 @@
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
         return mPointerControllers.valueFor(deviceId);
     }
+
+    virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) {
+        mInputDevices = inputDevices;
+    }
 };
 
 
@@ -283,7 +288,8 @@
         KeyedVector<int32_t, int32_t> scanCodeStates;
         KeyedVector<int32_t, int32_t> switchStates;
         KeyedVector<int32_t, int32_t> absoluteAxisValue;
-        KeyedVector<int32_t, KeyInfo> keys;
+        KeyedVector<int32_t, KeyInfo> keysByScanCode;
+        KeyedVector<int32_t, KeyInfo> keysByUsageCode;
         KeyedVector<int32_t, bool> leds;
         Vector<VirtualKeyDefinition> virtualKeys;
 
@@ -311,18 +317,18 @@
         device->identifier.name = name;
         mDevices.add(deviceId, device);
 
-        enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0, 0, 0);
+        enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0);
     }
 
     void removeDevice(int32_t deviceId) {
         delete mDevices.valueFor(deviceId);
         mDevices.removeItem(deviceId);
 
-        enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0, 0, 0);
+        enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0);
     }
 
     void finishDeviceScan() {
-        enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0, 0, 0);
+        enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0);
     }
 
     void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) {
@@ -374,12 +380,18 @@
         device->absoluteAxisValue.replaceValueFor(axis, value);
     }
 
-    void addKey(int32_t deviceId, int32_t scanCode, int32_t keyCode, uint32_t flags) {
+    void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+            int32_t keyCode, uint32_t flags) {
         Device* device = getDevice(deviceId);
         KeyInfo info;
         info.keyCode = keyCode;
         info.flags = flags;
-        device->keys.add(scanCode, info);
+        if (scanCode) {
+            device->keysByScanCode.add(scanCode, info);
+        }
+        if (usageCode) {
+            device->keysByUsageCode.add(usageCode, info);
+        }
     }
 
     void addLed(int32_t deviceId, int32_t led, bool initialState) {
@@ -402,19 +414,17 @@
     }
 
     void enqueueEvent(nsecs_t when, int32_t deviceId, int32_t type,
-            int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) {
+            int32_t code, int32_t value) {
         RawEvent event;
         event.when = when;
         event.deviceId = deviceId;
         event.type = type;
-        event.scanCode = scanCode;
-        event.keyCode = keyCode;
+        event.code = code;
         event.value = value;
-        event.flags = flags;
         mEvents.push_back(event);
 
         if (type == EV_ABS) {
-            setAbsoluteAxisValue(deviceId, scanCode, value);
+            setAbsoluteAxisValue(deviceId, code, value);
         }
     }
 
@@ -471,17 +481,17 @@
         return false;
     }
 
-    virtual status_t mapKey(int32_t deviceId, int scancode,
+    virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
             int32_t* outKeycode, uint32_t* outFlags) const {
         Device* device = getDevice(deviceId);
         if (device) {
-            ssize_t index = device->keys.indexOfKey(scancode);
-            if (index >= 0) {
+            const KeyInfo* key = getKey(device, scanCode, usageCode);
+            if (key) {
                 if (outKeycode) {
-                    *outKeycode = device->keys.valueAt(index).keyCode;
+                    *outKeycode = key->keyCode;
                 }
                 if (outFlags) {
-                    *outFlags = device->keys.valueAt(index).flags;
+                    *outFlags = key->flags;
                 }
                 return OK;
             }
@@ -489,7 +499,23 @@
         return NAME_NOT_FOUND;
     }
 
-    virtual status_t mapAxis(int32_t deviceId, int scancode,
+    const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const {
+        if (usageCode) {
+            ssize_t index = device->keysByUsageCode.indexOfKey(usageCode);
+            if (index >= 0) {
+                return &device->keysByUsageCode.valueAt(index);
+            }
+        }
+        if (scanCode) {
+            ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
+            if (index >= 0) {
+                return &device->keysByScanCode.valueAt(index);
+            }
+        }
+        return NULL;
+    }
+
+    virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
             AxisInfo* outAxisInfo) const {
         return NAME_NOT_FOUND;
     }
@@ -561,8 +587,14 @@
         Device* device = getDevice(deviceId);
         if (device) {
             for (size_t i = 0; i < numCodes; i++) {
-                for (size_t j = 0; j < device->keys.size(); j++) {
-                    if (keyCodes[i] == device->keys.valueAt(j).keyCode) {
+                for (size_t j = 0; j < device->keysByScanCode.size(); j++) {
+                    if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) {
+                        outFlags[i] = 1;
+                        result = true;
+                    }
+                }
+                for (size_t j = 0; j < device->keysByUsageCode.size(); j++) {
+                    if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) {
                         outFlags[i] = 1;
                         result = true;
                     }
@@ -575,7 +607,7 @@
     virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const {
         Device* device = getDevice(deviceId);
         if (device) {
-            ssize_t index = device->keys.indexOfKey(scanCode);
+            ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
             return index >= 0;
         }
         return false;
@@ -640,6 +672,7 @@
     sp<InputListenerInterface> mListener;
     int32_t mGlobalMetaState;
     bool mUpdateGlobalMetaStateWasCalled;
+    int32_t mGeneration;
 
 public:
     FakeInputReaderContext(const sp<EventHubInterface>& eventHub,
@@ -695,6 +728,10 @@
 
     virtual void requestTimeoutAtTime(nsecs_t when) {
     }
+
+    virtual int32_t bumpGeneration() {
+        return ++mGeneration;
+    }
 };
 
 
@@ -860,7 +897,8 @@
     InputDevice* newDevice(int32_t deviceId, const String8& name, uint32_t classes) {
         InputDeviceIdentifier identifier;
         identifier.name = name;
-        return new InputDevice(&mContext, deviceId, identifier, classes);
+        int32_t generation = deviceId + 1;
+        return new InputDevice(&mContext, deviceId, generation, identifier, classes);
     }
 
 protected:
@@ -1018,52 +1056,30 @@
     ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
 }
 
-TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsValid) {
+TEST_F(InputReaderTest, GetInputDevices) {
     ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
             INPUT_DEVICE_CLASS_KEYBOARD, NULL));
+    ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("ignored"),
+            0, NULL)); // no classes so device will be ignored
 
-    InputDeviceInfo info;
-    status_t result = mReader->getInputDeviceInfo(1, &info);
+    Vector<InputDeviceInfo> inputDevices;
+    mReader->getInputDevices(inputDevices);
 
-    ASSERT_EQ(OK, result);
-    ASSERT_EQ(1, info.getId());
-    ASSERT_STREQ("keyboard", info.getName().string());
-    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, info.getKeyboardType());
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, info.getSources());
-    ASSERT_EQ(size_t(0), info.getMotionRanges().size());
-}
+    ASSERT_EQ(1U, inputDevices.size());
+    ASSERT_EQ(1, inputDevices[0].getId());
+    ASSERT_STREQ("keyboard", inputDevices[0].getName().string());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType());
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources());
+    ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size());
 
-TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsInvalid) {
-    InputDeviceInfo info;
-    status_t result = mReader->getInputDeviceInfo(-1, &info);
-
-    ASSERT_EQ(NAME_NOT_FOUND, result);
-}
-
-TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsIgnored) {
-    addDevice(1, String8("ignored"), 0, NULL); // no classes so device will be ignored
-
-    InputDeviceInfo info;
-    status_t result = mReader->getInputDeviceInfo(1, &info);
-
-    ASSERT_EQ(NAME_NOT_FOUND, result);
-}
-
-TEST_F(InputReaderTest, GetInputDeviceIds) {
-    sp<FakePointerController> controller = new FakePointerController();
-    mFakePolicy->setPointerController(2, controller);
-
-    ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
-            INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY, NULL));
-    ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("mouse"),
-            INPUT_DEVICE_CLASS_CURSOR, NULL));
-
-    Vector<int32_t> ids;
-    mReader->getInputDeviceIds(ids);
-
-    ASSERT_EQ(size_t(2), ids.size());
-    ASSERT_EQ(1, ids[0]);
-    ASSERT_EQ(2, ids[1]);
+    // Should also have received a notification describing the new input devices.
+    inputDevices = mFakePolicy->getInputDevices();
+    ASSERT_EQ(1U, inputDevices.size());
+    ASSERT_EQ(1, inputDevices[0].getId());
+    ASSERT_STREQ("keyboard", inputDevices[0].getName().string());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType());
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources());
+    ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size());
 }
 
 TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) {
@@ -1196,7 +1212,7 @@
     ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
             INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
 
-    mFakeEventHub->enqueueEvent(0, 1, EV_KEY, KEY_A, AKEYCODE_A, 1, POLICY_FLAG_WAKE);
+    mFakeEventHub->enqueueEvent(0, 1, EV_KEY, KEY_A, 1);
     mReader->loopOnce();
     ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
 
@@ -1205,10 +1221,8 @@
     ASSERT_EQ(0, event.when);
     ASSERT_EQ(1, event.deviceId);
     ASSERT_EQ(EV_KEY, event.type);
-    ASSERT_EQ(KEY_A, event.scanCode);
-    ASSERT_EQ(AKEYCODE_A, event.keyCode);
+    ASSERT_EQ(KEY_A, event.code);
     ASSERT_EQ(1, event.value);
-    ASSERT_EQ(POLICY_FLAG_WAKE, event.flags);
 }
 
 
@@ -1218,6 +1232,7 @@
 protected:
     static const char* DEVICE_NAME;
     static const int32_t DEVICE_ID;
+    static const int32_t DEVICE_GENERATION;
     static const uint32_t DEVICE_CLASSES;
 
     sp<FakeEventHub> mFakeEventHub;
@@ -1236,7 +1251,8 @@
         mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
         InputDeviceIdentifier identifier;
         identifier.name = DEVICE_NAME;
-        mDevice = new InputDevice(mFakeContext, DEVICE_ID, identifier, DEVICE_CLASSES);
+        mDevice = new InputDevice(mFakeContext, DEVICE_ID, DEVICE_GENERATION,
+                identifier, DEVICE_CLASSES);
     }
 
     virtual void TearDown() {
@@ -1251,6 +1267,7 @@
 
 const char* InputDeviceTest::DEVICE_NAME = "device";
 const int32_t InputDeviceTest::DEVICE_ID = 1;
+const int32_t InputDeviceTest::DEVICE_GENERATION = 2;
 const uint32_t InputDeviceTest::DEVICE_CLASSES = INPUT_DEVICE_CLASS_KEYBOARD
         | INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_JOYSTICK;
 
@@ -1403,6 +1420,7 @@
 protected:
     static const char* DEVICE_NAME;
     static const int32_t DEVICE_ID;
+    static const int32_t DEVICE_GENERATION;
     static const uint32_t DEVICE_CLASSES;
 
     sp<FakeEventHub> mFakeEventHub;
@@ -1418,7 +1436,8 @@
         mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener);
         InputDeviceIdentifier identifier;
         identifier.name = DEVICE_NAME;
-        mDevice = new InputDevice(mFakeContext, DEVICE_ID, identifier, DEVICE_CLASSES);
+        mDevice = new InputDevice(mFakeContext, DEVICE_ID, DEVICE_GENERATION,
+                identifier, DEVICE_CLASSES);
 
         mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
     }
@@ -1449,15 +1468,13 @@
     }
 
     static void process(InputMapper* mapper, nsecs_t when, int32_t deviceId, int32_t type,
-            int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) {
+            int32_t code, int32_t value) {
         RawEvent event;
         event.when = when;
         event.deviceId = deviceId;
         event.type = type;
-        event.scanCode = scanCode;
-        event.keyCode = keyCode;
+        event.code = code;
         event.value = value;
-        event.flags = flags;
         mapper->process(&event);
     }
 
@@ -1499,6 +1516,7 @@
 
 const char* InputMapperTest::DEVICE_NAME = "device";
 const int32_t InputMapperTest::DEVICE_ID = 1;
+const int32_t InputMapperTest::DEVICE_GENERATION = 2;
 const uint32_t InputMapperTest::DEVICE_CLASSES = 0; // not needed for current tests
 
 
@@ -1530,7 +1548,7 @@
     SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
     addMapperAndConfigure(mapper);
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_LID, 0, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_LID, 1);
 
     NotifySwitchArgs args;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySwitchWasCalled(&args));
@@ -1553,13 +1571,13 @@
         int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode) {
     NotifyKeyArgs args;
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
     ASSERT_EQ(originalScanCode, args.scanCode);
     ASSERT_EQ(rotatedKeyCode, args.keyCode);
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
     ASSERT_EQ(originalScanCode, args.scanCode);
@@ -1576,13 +1594,18 @@
 }
 
 TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) {
+    const int32_t USAGE_A = 0x070004;
+    const int32_t USAGE_UNKNOWN = 0x07ffff;
+    mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+    mFakeEventHub->addKey(DEVICE_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
+
     KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
             AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     addMapperAndConfigure(mapper);
 
-    // Key down.
+    // Key down by scan code.
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_HOME, AKEYCODE_HOME, 1, POLICY_FLAG_WAKE);
+            EV_KEY, KEY_HOME, 1);
     NotifyKeyArgs args;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -1596,9 +1619,9 @@
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
 
-    // Key up.
+    // Key up by scan code.
     process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
-            EV_KEY, KEY_HOME, AKEYCODE_HOME, 0, POLICY_FLAG_WAKE);
+            EV_KEY, KEY_HOME, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DEVICE_ID, args.deviceId);
     ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
@@ -1610,9 +1633,80 @@
     ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Key down by usage code.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_MSC, MSC_SCAN, USAGE_A);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, 0, 1);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(AKEYCODE_A, args.keyCode);
+    ASSERT_EQ(0, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Key up by usage code.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_MSC, MSC_SCAN, USAGE_A);
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+            EV_KEY, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(AKEYCODE_A, args.keyCode);
+    ASSERT_EQ(0, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Key down with unknown scan code or usage code.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_KEY, KEY_UNKNOWN, 1);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+    ASSERT_EQ(0, args.keyCode);
+    ASSERT_EQ(KEY_UNKNOWN, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(0U, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+    // Key up with unknown scan code or usage code.
+    process(mapper, ARBITRARY_TIME, DEVICE_ID,
+            EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+            EV_KEY, KEY_UNKNOWN, 0);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+    ASSERT_EQ(DEVICE_ID, args.deviceId);
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+    ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+    ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+    ASSERT_EQ(0, args.keyCode);
+    ASSERT_EQ(KEY_UNKNOWN, args.scanCode);
+    ASSERT_EQ(AMETA_NONE, args.metaState);
+    ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+    ASSERT_EQ(0U, args.policyFlags);
+    ASSERT_EQ(ARBITRARY_TIME, args.downTime);
 }
 
 TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) {
+    mFakeEventHub->addKey(DEVICE_ID, KEY_LEFTSHIFT, 0, AKEYCODE_SHIFT_LEFT, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_A, 0, AKEYCODE_A, 0);
+
     KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
             AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     addMapperAndConfigure(mapper);
@@ -1622,7 +1716,7 @@
 
     // Metakey down.
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 1, 0);
+            EV_KEY, KEY_LEFTSHIFT, 1);
     NotifyKeyArgs args;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
@@ -1631,21 +1725,21 @@
 
     // Key down.
     process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
-            EV_KEY, KEY_A, AKEYCODE_A, 1, 0);
+            EV_KEY, KEY_A, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
 
     // Key up.
     process(mapper, ARBITRARY_TIME + 2, DEVICE_ID,
-            EV_KEY, KEY_A, AKEYCODE_A, 0, 0);
+            EV_KEY, KEY_A, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
 
     // Metakey up.
     process(mapper, ARBITRARY_TIME + 3, DEVICE_ID,
-            EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 0, 0);
+            EV_KEY, KEY_LEFTSHIFT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AMETA_NONE, args.metaState);
     ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
@@ -1653,6 +1747,11 @@
 }
 
 TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateDPad) {
+    mFakeEventHub->addKey(DEVICE_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
+
     KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
             AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     addMapperAndConfigure(mapper);
@@ -1671,6 +1770,11 @@
 }
 
 TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
+    mFakeEventHub->addKey(DEVICE_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
+
     KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
             AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     addConfigurationProperty("keyboard.orientationAware", "1");
@@ -1731,7 +1835,7 @@
     setDisplayInfoAndReconfigure(DISPLAY_ID,
             DISPLAY_WIDTH, DISPLAY_HEIGHT,
             DISPLAY_ORIENTATION_270);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 1, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
     ASSERT_EQ(KEY_UP, args.scanCode);
@@ -1740,7 +1844,7 @@
     setDisplayInfoAndReconfigure(DISPLAY_ID,
             DISPLAY_WIDTH, DISPLAY_HEIGHT,
             DISPLAY_ORIENTATION_180);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
     ASSERT_EQ(KEY_UP, args.scanCode);
@@ -1776,7 +1880,7 @@
             AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
     addMapperAndConfigure(mapper);
 
-    mFakeEventHub->addKey(DEVICE_ID, KEY_A, AKEYCODE_A, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_A, 0, AKEYCODE_A, 0);
 
     const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
     uint8_t flags[2] = { 0, 0 };
@@ -1789,6 +1893,9 @@
     mFakeEventHub->addLed(DEVICE_ID, LED_CAPSL, true /*initially on*/);
     mFakeEventHub->addLed(DEVICE_ID, LED_NUML, false /*initially off*/);
     mFakeEventHub->addLed(DEVICE_ID, LED_SCROLLL, false /*initially off*/);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
             AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
@@ -1801,9 +1908,9 @@
 
     // Toggle caps lock on.
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0);
+            EV_KEY, KEY_CAPSLOCK, 1);
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0);
+            EV_KEY, KEY_CAPSLOCK, 0);
     ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1811,9 +1918,9 @@
 
     // Toggle num lock on.
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0);
+            EV_KEY, KEY_NUMLOCK, 1);
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0);
+            EV_KEY, KEY_NUMLOCK, 0);
     ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
     ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1821,9 +1928,9 @@
 
     // Toggle caps lock off.
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0);
+            EV_KEY, KEY_CAPSLOCK, 1);
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0);
+            EV_KEY, KEY_CAPSLOCK, 0);
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
     ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1831,9 +1938,9 @@
 
     // Toggle scroll lock on.
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0);
+            EV_KEY, KEY_SCROLLLOCK, 1);
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0);
+            EV_KEY, KEY_SCROLLLOCK, 0);
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
     ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
     ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1841,9 +1948,9 @@
 
     // Toggle num lock off.
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0);
+            EV_KEY, KEY_NUMLOCK, 1);
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0);
+            EV_KEY, KEY_NUMLOCK, 0);
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
     ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1851,9 +1958,9 @@
 
     // Toggle scroll lock off.
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0);
+            EV_KEY, KEY_SCROLLLOCK, 1);
     process(mapper, ARBITRARY_TIME, DEVICE_ID,
-            EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0);
+            EV_KEY, KEY_SCROLLLOCK, 0);
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
     ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1886,9 +1993,9 @@
         int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY) {
     NotifyMotionArgs args;
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, originalX, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, originalY, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, originalX);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, originalY);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -1974,8 +2081,8 @@
 
     // Button press.
     // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
     ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -1996,8 +2103,8 @@
     ASSERT_EQ(ARBITRARY_TIME, args.downTime);
 
     // Button release.  Should have same down time.
-    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
-    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_KEY, BTN_MOUSE, 0);
+    process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
     ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -2026,16 +2133,16 @@
     NotifyMotionArgs args;
 
     // Motion in X but not Y.
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // Motion in Y but not X.
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, -2);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2050,16 +2157,16 @@
     NotifyMotionArgs args;
 
     // Button press.
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // Button release.
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2074,10 +2181,10 @@
     NotifyMotionArgs args;
 
     // Combined X, Y and Button.
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, -2);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2085,9 +2192,9 @@
             1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // Move X, Y a bit while pressed.
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 2, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 2);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2095,8 +2202,8 @@
             1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // Release Button.
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2185,8 +2292,8 @@
     NotifyKeyArgs keyArgs;
 
     // press BTN_LEFT, release BTN_LEFT
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
@@ -2194,8 +2301,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 0, 0, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2211,9 +2318,9 @@
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
@@ -2223,8 +2330,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 0, 0, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
     ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState());
@@ -2232,8 +2339,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 0, 0, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2248,8 +2355,8 @@
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
     // press BTN_BACK, release BTN_BACK
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
     ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
@@ -2260,8 +2367,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 0, 0, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2273,8 +2380,8 @@
     ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
 
     // press BTN_SIDE, release BTN_SIDE
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
     ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
@@ -2285,8 +2392,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 0, 0, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2298,8 +2405,8 @@
     ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
 
     // press BTN_FORWARD, release BTN_FORWARD
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
     ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
@@ -2310,8 +2417,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 0, 0, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2323,8 +2430,8 @@
     ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
 
     // press BTN_EXTRA, release BTN_EXTRA
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
     ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
@@ -2335,8 +2442,8 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
             100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 0, 0, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(0, motionArgs.buttonState);
     ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2359,9 +2466,9 @@
 
     NotifyMotionArgs args;
 
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 10, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, 20, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 10);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 20);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2462,8 +2569,8 @@
 void TouchInputMapperTest::prepareVirtualKeys() {
     mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[0]);
     mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[1]);
-    mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, AKEYCODE_HOME, POLICY_FLAG_WAKE);
-    mFakeEventHub->addKey(DEVICE_ID, KEY_MENU, AKEYCODE_MENU, POLICY_FLAG_WAKE);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+    mFakeEventHub->addKey(DEVICE_ID, KEY_MENU, 0, AKEYCODE_MENU, POLICY_FLAG_WAKE);
 }
 
 int32_t TouchInputMapperTest::toRawX(float displayX) {
@@ -2502,7 +2609,7 @@
 };
 
 void SingleTouchInputMapperTest::prepareButtons() {
-    mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, AKEYCODE_UNKNOWN, 0);
+    mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
 }
 
 void SingleTouchInputMapperTest::prepareAxes(int axes) {
@@ -2533,48 +2640,48 @@
 }
 
 void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 1, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 1);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, x);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, y);
 }
 
 void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, x);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, y);
 }
 
 void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper* mapper) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0);
 }
 
 void SingleTouchInputMapperTest::processPressure(
         SingleTouchInputMapper* mapper, int32_t pressure) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_PRESSURE, 0, pressure, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_PRESSURE, pressure);
 }
 
 void SingleTouchInputMapperTest::processToolMajor(
         SingleTouchInputMapper* mapper, int32_t toolMajor) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TOOL_WIDTH, 0, toolMajor, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TOOL_WIDTH, toolMajor);
 }
 
 void SingleTouchInputMapperTest::processDistance(
         SingleTouchInputMapper* mapper, int32_t distance) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_DISTANCE, 0, distance, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_DISTANCE, distance);
 }
 
 void SingleTouchInputMapperTest::processTilt(
         SingleTouchInputMapper* mapper, int32_t tiltX, int32_t tiltY) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_X, 0, tiltX, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_Y, 0, tiltY, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_X, tiltX);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_Y, tiltY);
 }
 
 void SingleTouchInputMapperTest::processKey(
         SingleTouchInputMapper* mapper, int32_t code, int32_t value) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, 0, value, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, value);
 }
 
 void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
 }
 
 
@@ -3464,7 +3571,7 @@
     prepareDisplay(DISPLAY_ORIENTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
-    mFakeEventHub->addKey(DEVICE_ID, BTN_TOOL_FINGER, AKEYCODE_UNKNOWN, 0);
+    mFakeEventHub->addKey(DEVICE_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0);
     addMapperAndConfigure(mapper);
 
     NotifyMotionArgs motionArgs;
@@ -3678,71 +3785,71 @@
 
 void MultiTouchInputMapperTest::processPosition(
         MultiTouchInputMapper* mapper, int32_t x, int32_t y) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_X, 0, x, 0);
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_Y, 0, y, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_X, x);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_Y, y);
 }
 
 void MultiTouchInputMapperTest::processTouchMajor(
         MultiTouchInputMapper* mapper, int32_t touchMajor) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MAJOR, 0, touchMajor, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MAJOR, touchMajor);
 }
 
 void MultiTouchInputMapperTest::processTouchMinor(
         MultiTouchInputMapper* mapper, int32_t touchMinor) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MINOR, 0, touchMinor, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MINOR, touchMinor);
 }
 
 void MultiTouchInputMapperTest::processToolMajor(
         MultiTouchInputMapper* mapper, int32_t toolMajor) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MAJOR, 0, toolMajor, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MAJOR, toolMajor);
 }
 
 void MultiTouchInputMapperTest::processToolMinor(
         MultiTouchInputMapper* mapper, int32_t toolMinor) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MINOR, 0, toolMinor, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MINOR, toolMinor);
 }
 
 void MultiTouchInputMapperTest::processOrientation(
         MultiTouchInputMapper* mapper, int32_t orientation) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_ORIENTATION, 0, orientation, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_ORIENTATION, orientation);
 }
 
 void MultiTouchInputMapperTest::processPressure(
         MultiTouchInputMapper* mapper, int32_t pressure) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_PRESSURE, 0, pressure, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_PRESSURE, pressure);
 }
 
 void MultiTouchInputMapperTest::processDistance(
         MultiTouchInputMapper* mapper, int32_t distance) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_DISTANCE, 0, distance, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_DISTANCE, distance);
 }
 
 void MultiTouchInputMapperTest::processId(
         MultiTouchInputMapper* mapper, int32_t id) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TRACKING_ID, 0, id, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TRACKING_ID, id);
 }
 
 void MultiTouchInputMapperTest::processSlot(
         MultiTouchInputMapper* mapper, int32_t slot) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_SLOT, 0, slot, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_SLOT, slot);
 }
 
 void MultiTouchInputMapperTest::processToolType(
         MultiTouchInputMapper* mapper, int32_t toolType) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOOL_TYPE, 0, toolType, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOOL_TYPE, toolType);
 }
 
 void MultiTouchInputMapperTest::processKey(
         MultiTouchInputMapper* mapper, int32_t code, int32_t value) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, 0, value, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, value);
 }
 
 void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper* mapper) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_MT_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_MT_REPORT, 0);
 }
 
 void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper* mapper) {
-    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+    process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
 }
 
 
@@ -4891,7 +4998,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
     prepareAxes(POSITION | ID | SLOT);
-    mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, AKEYCODE_UNKNOWN, 0);
+    mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
     addMapperAndConfigure(mapper);
 
     NotifyMotionArgs motionArgs;
diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java
index 768be7d..a3ac8d0 100644
--- a/services/java/com/android/server/NsdService.java
+++ b/services/java/com/android/server/NsdService.java
@@ -32,9 +32,11 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.net.InetAddress;
 import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
+import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.telephony.TelephonyIntents;
@@ -60,10 +62,13 @@
     /**
      * Clients receiving asynchronous messages
      */
-    private List<AsyncChannel> mClients = new ArrayList<AsyncChannel>();
+    private HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>();
 
     private AsyncChannel mReplyChannel = new AsyncChannel();
 
+    private int INVALID_ID = 0;
+    private int mUniqueId = 1;
+
     /**
      * Handles client(app) connections
      */
@@ -75,13 +80,19 @@
 
         @Override
         public void handleMessage(Message msg) {
+            ClientInfo clientInfo;
+            DnsSdServiceInfo servInfo;
             switch (msg.what) {
                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                     if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
                         AsyncChannel c = (AsyncChannel) msg.obj;
                         if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
                         c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
-                        mClients.add(c);
+                        ClientInfo cInfo = new ClientInfo(c, msg.replyTo);
+                        if (mClients.size() == 0) {
+                            startMDnsDaemon();
+                        }
+                        mClients.put(msg.replyTo, cInfo);
                     } else {
                         Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
                     }
@@ -92,7 +103,10 @@
                     } else {
                         if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
                     }
-                    mClients.remove((AsyncChannel) msg.obj);
+                    mClients.remove(msg.replyTo);
+                    if (mClients.size() == 0) {
+                        stopMDnsDaemon();
+                    }
                     break;
                 case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
                     AsyncChannel ac = new AsyncChannel();
@@ -100,22 +114,98 @@
                     break;
                 case NsdManager.DISCOVER_SERVICES:
                     if (DBG) Slog.d(TAG, "Discover services");
-                    DnsSdServiceInfo s = (DnsSdServiceInfo) msg.obj;
-                    discoverServices(1, s.getServiceType());
-                    mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED);
+                    servInfo = (DnsSdServiceInfo) msg.obj;
+                    clientInfo = mClients.get(msg.replyTo);
+                    if (clientInfo.mDiscoveryId != INVALID_ID) {
+                        //discovery already in progress
+                        if (DBG) Slog.d(TAG, "discovery in progress");
+                        mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
+                                NsdManager.ALREADY_ACTIVE);
+                        break;
+                    }
+                    clientInfo.mDiscoveryId = getUniqueId();
+                    if (discoverServices(clientInfo.mDiscoveryId, servInfo.getServiceType())) {
+                        mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED);
+                    } else {
+                        mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
+                                NsdManager.ERROR);
+                        clientInfo.mDiscoveryId = INVALID_ID;
+                    }
                     break;
                 case NsdManager.STOP_DISCOVERY:
                     if (DBG) Slog.d(TAG, "Stop service discovery");
-                    mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED);
+                    clientInfo = mClients.get(msg.replyTo);
+                    if (clientInfo.mDiscoveryId == INVALID_ID) {
+                        //already stopped
+                        if (DBG) Slog.d(TAG, "discovery already stopped");
+                        mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
+                                NsdManager.ALREADY_ACTIVE);
+                        break;
+                    }
+                    if (stopServiceDiscovery(clientInfo.mDiscoveryId)) {
+                        clientInfo.mDiscoveryId = INVALID_ID;
+                        mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
+                    } else {
+                        mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
+                                NsdManager.ERROR);
+                    }
                     break;
                 case NsdManager.REGISTER_SERVICE:
                     if (DBG) Slog.d(TAG, "Register service");
-                    mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED);
+                    clientInfo = mClients.get(msg.replyTo);
+                    if (clientInfo.mRegisteredIds.size() >= ClientInfo.MAX_REG) {
+                        if (DBG) Slog.d(TAG, "register service exceeds limit");
+                        mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
+                                NsdManager.MAX_REGS_REACHED);
+                    }
+
+                    int id = getUniqueId();
+                    if (registerService(id, (DnsSdServiceInfo) msg.obj)) {
+                        clientInfo.mRegisteredIds.add(id);
+                    } else {
+                        mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
+                                NsdManager.ERROR);
+                    }
                     break;
                 case NsdManager.UPDATE_SERVICE:
                     if (DBG) Slog.d(TAG, "Update service");
+                    //TODO: implement
                     mReplyChannel.replyToMessage(msg, NsdManager.UPDATE_SERVICE_FAILED);
                     break;
+                case NsdManager.RESOLVE_SERVICE:
+                    if (DBG) Slog.d(TAG, "Resolve service");
+                    servInfo = (DnsSdServiceInfo) msg.obj;
+                    clientInfo = mClients.get(msg.replyTo);
+                    if (clientInfo.mResolveId != INVALID_ID) {
+                        //first cancel existing resolve
+                        stopResolveService(clientInfo.mResolveId);
+                    }
+
+                    clientInfo.mResolveId = getUniqueId();
+                    if (!resolveService(clientInfo.mResolveId, servInfo)) {
+                        mReplyChannel.replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
+                                NsdManager.ERROR);
+                        clientInfo.mResolveId = INVALID_ID;
+                    }
+                    break;
+                case NsdManager.STOP_RESOLVE:
+                    if (DBG) Slog.d(TAG, "Stop resolve");
+                    clientInfo = mClients.get(msg.replyTo);
+                    if (clientInfo.mResolveId == INVALID_ID) {
+                        //already stopped
+                        if (DBG) Slog.d(TAG, "resolve already stopped");
+                        mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED,
+                                NsdManager.ALREADY_ACTIVE);
+                        break;
+                    }
+                    if (stopResolveService(clientInfo.mResolveId)) {
+                        clientInfo.mResolveId = INVALID_ID;
+                        mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_SUCCEEDED);
+                    } else {
+                        mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED,
+                                NsdManager.ERROR);
+                    }
+                    break;
                 default:
                     Slog.d(TAG, "NsdServicehandler.handleMessage ignoring msg=" + msg);
                     break;
@@ -134,12 +224,10 @@
         nsdThread.start();
         mAsyncServiceHandler = new AsyncServiceHandler(nsdThread.getLooper());
 
-        /*
         mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
                 MDNS_TAG, 25);
         Thread th = new Thread(mNativeConnector, MDNS_TAG);
         th.start();
-        */
     }
 
     public static NsdService create(Context context) throws InterruptedException {
@@ -152,22 +240,29 @@
         return new Messenger(mAsyncServiceHandler);
     }
 
-    /* These should be in sync with system/netd/mDnsResponseCode.h */
-    class NativeResponseCode {
-        public static final int SERVICE_FOUND               =   101;
-        public static final int SERVICE_LOST                =   102;
-        public static final int SERVICE_DISCOVERY_FAILED    =   103;
-
-        public static final int SERVICE_REGISTERED          =   104;
-        public static final int SERVICE_REGISTRATION_FAILED =   105;
-
-        public static final int SERVICE_UPDATED             =   106;
-        public static final int SERVICE_UPDATE_FAILED       =   107;
-
-        public static final int SERVICE_RESOLVED            =   108;
-        public static final int SERVICE_RESOLUTION_FAILED   =   109;
+    private int getUniqueId() {
+        if (++mUniqueId == INVALID_ID) return ++mUniqueId;
+        return mUniqueId;
     }
 
+    /* These should be in sync with system/netd/mDnsResponseCode.h */
+    class NativeResponseCode {
+        public static final int SERVICE_DISCOVERY_FAILED    =   602;
+        public static final int SERVICE_FOUND               =   603;
+        public static final int SERVICE_LOST                =   604;
+
+        public static final int SERVICE_REGISTRATION_FAILED =   605;
+        public static final int SERVICE_REGISTERED          =   606;
+
+        public static final int SERVICE_RESOLUTION_FAILED   =   607;
+        public static final int SERVICE_RESOLVED            =   608;
+
+        public static final int SERVICE_UPDATED             =   609;
+        public static final int SERVICE_UPDATE_FAILED       =   610;
+
+        public static final int SERVICE_GET_ADDR_FAILED     =   611;
+        public static final int SERVICE_GET_ADDR_SUCCESS    =   612;
+    }
 
     class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
         public void onDaemonConnected() {
@@ -175,21 +270,55 @@
         }
 
         public boolean onEvent(int code, String raw, String[] cooked) {
+            ClientInfo clientInfo;
+            DnsSdServiceInfo servInfo;
+            int id = Integer.parseInt(cooked[1]);
             switch (code) {
                 case NativeResponseCode.SERVICE_FOUND:
-                    /* NNN uniqueId serviceName regType */
+                    /* NNN uniqueId serviceName regType domain */
+                    if (DBG) Slog.d(TAG, "SERVICE_FOUND Raw: " + raw);
+                    clientInfo = getClientByDiscovery(id);
+                    if (clientInfo == null) break;
+
+                    servInfo = new DnsSdServiceInfo(cooked[2], cooked[3], null);
+                    clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, servInfo);
                     break;
                 case NativeResponseCode.SERVICE_LOST:
-                    /* NNN uniqueId serviceName regType */
+                    /* NNN uniqueId serviceName regType domain */
+                    if (DBG) Slog.d(TAG, "SERVICE_LOST Raw: " + raw);
+                    clientInfo = getClientByDiscovery(id);
+                    if (clientInfo == null) break;
+
+                    servInfo = new DnsSdServiceInfo(cooked[2], cooked[3], null);
+                    clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, servInfo);
                     break;
                 case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
                     /* NNN uniqueId errorCode */
+                    if (DBG) Slog.d(TAG, "SERVICE_DISC_FAILED Raw: " + raw);
+                    clientInfo = getClientByDiscovery(id);
+                    if (clientInfo == null) break;
+
+                    clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED,
+                            NsdManager.ERROR);
                     break;
                 case NativeResponseCode.SERVICE_REGISTERED:
                     /* NNN regId serviceName regType */
+                    if (DBG) Slog.d(TAG, "SERVICE_REGISTERED Raw: " + raw);
+                    clientInfo = getClientByRegistration(id);
+                    if (clientInfo == null) break;
+
+                    servInfo = new DnsSdServiceInfo(cooked[2], null, null);
+                    clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
+                            id, 0, servInfo);
                     break;
                 case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
                     /* NNN regId errorCode */
+                    if (DBG) Slog.d(TAG, "SERVICE_REGISTER_FAILED Raw: " + raw);
+                    clientInfo = getClientByRegistration(id);
+                    if (clientInfo == null) break;
+
+                    clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED,
+                            NsdManager.ERROR);
                     break;
                 case NativeResponseCode.SERVICE_UPDATED:
                     /* NNN regId */
@@ -199,9 +328,52 @@
                     break;
                 case NativeResponseCode.SERVICE_RESOLVED:
                     /* NNN resolveId fullName hostName port txtlen txtdata */
+                    if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw);
+                    clientInfo = getClientByResolve(id);
+                    if (clientInfo == null) break;
+
+                    int index = cooked[2].indexOf(".");
+                    if (index == -1) {
+                        Slog.e(TAG, "Invalid service found " + raw);
+                        break;
+                    }
+                    String name = cooked[2].substring(0, index);
+                    String rest = cooked[2].substring(index);
+                    String type = rest.replace(".local.", "");
+
+                    clientInfo.mResolvedService = new DnsSdServiceInfo(name, type, null);
+                    clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
+
+                    stopResolveService(id);
+                    getAddrInfo(id, cooked[3]);
                     break;
                 case NativeResponseCode.SERVICE_RESOLUTION_FAILED:
-                    /* NNN resovleId errorCode */
+                case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
+                    /* NNN resolveId errorCode */
+                    if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw);
+                    clientInfo = getClientByResolve(id);
+                    if (clientInfo == null) break;
+
+                    clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
+                            NsdManager.ERROR);
+                    break;
+                case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
+                    /* NNN resolveId hostname ttl addr */
+                    if (DBG) Slog.d(TAG, "SERVICE_GET_ADDR_SUCCESS Raw: " + raw);
+                    clientInfo = getClientByResolve(id);
+                    if (clientInfo == null || clientInfo.mResolvedService == null) break;
+
+                    try {
+                        clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
+                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED,
+                                clientInfo.mResolvedService);
+                        clientInfo.mResolvedService = null;
+                        clientInfo.mResolveId = INVALID_ID;
+                    } catch (java.net.UnknownHostException e) {
+                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
+                                NsdManager.ERROR);
+                    }
+                    stopGetAddrInfo(id);
                     break;
                 default:
                     break;
@@ -210,48 +382,129 @@
         }
     }
 
-    private void registerService(int regId, DnsSdServiceInfo service) {
+    private boolean startMDnsDaemon() {
+        if (DBG) Slog.d(TAG, "startMDnsDaemon");
+        try {
+            mNativeConnector.execute("mdnssd", "start-service");
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to start daemon" + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean stopMDnsDaemon() {
+        if (DBG) Slog.d(TAG, "stopMDnsDaemon");
+        try {
+            mNativeConnector.execute("mdnssd", "stop-service");
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to start daemon" + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean registerService(int regId, DnsSdServiceInfo service) {
+        if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
         try {
             //Add txtlen and txtdata
             mNativeConnector.execute("mdnssd", "register", regId, service.getServiceName(),
                     service.getServiceType(), service.getPort());
         } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to execute registerService");
+            Slog.e(TAG, "Failed to execute registerService " + e);
+            return false;
         }
+        return true;
     }
 
-    private void updateService(int regId, DnsSdTxtRecord t) {
+    private boolean unregisterService(int regId) {
+        if (DBG) Slog.d(TAG, "unregisterService: " + regId);
         try {
-            if (t == null) return;
+            mNativeConnector.execute("mdnssd", "stop-register", regId);
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to execute unregisterService " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean updateService(int regId, DnsSdTxtRecord t) {
+        if (DBG) Slog.d(TAG, "updateService: " + regId + " " + t);
+        try {
+            if (t == null) return false;
             mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData());
         } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to updateServices");
+            Slog.e(TAG, "Failed to updateServices " + e);
+            return false;
         }
+        return true;
     }
 
-    private void discoverServices(int discoveryId, String serviceType) {
+    private boolean discoverServices(int discoveryId, String serviceType) {
+        if (DBG) Slog.d(TAG, "discoverServices: " + discoveryId + " " + serviceType);
         try {
             mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType);
         } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to discoverServices");
+            Slog.e(TAG, "Failed to discoverServices " + e);
+            return false;
         }
+        return true;
     }
 
-    private void stopServiceDiscovery(int discoveryId) {
+    private boolean stopServiceDiscovery(int discoveryId) {
+        if (DBG) Slog.d(TAG, "stopServiceDiscovery: " + discoveryId);
         try {
-            mNativeConnector.execute("mdnssd", "stopdiscover", discoveryId);
+            mNativeConnector.execute("mdnssd", "stop-discover", discoveryId);
         } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to stopServiceDiscovery");
+            Slog.e(TAG, "Failed to stopServiceDiscovery " + e);
+            return false;
         }
+        return true;
     }
 
-    private void resolveService(DnsSdServiceInfo service) {
+    private boolean resolveService(int resolveId, DnsSdServiceInfo service) {
+        if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service);
         try {
-        mNativeConnector.execute("mdnssd", "resolve", service.getServiceName(),
-                service.getServiceType());
+            mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(),
+                    service.getServiceType(), "local.");
         } catch(NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to resolveService");
+            Slog.e(TAG, "Failed to resolveService " + e);
+            return false;
         }
+        return true;
+    }
+
+    private boolean stopResolveService(int resolveId) {
+        if (DBG) Slog.d(TAG, "stopResolveService: " + resolveId);
+        try {
+            mNativeConnector.execute("mdnssd", "stop-resolve", resolveId);
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to stop resolve " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean getAddrInfo(int resolveId, String hostname) {
+        if (DBG) Slog.d(TAG, "getAdddrInfo: " + resolveId);
+        try {
+            mNativeConnector.execute("mdnssd", "getaddrinfo", resolveId, hostname);
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to getAddrInfo " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean stopGetAddrInfo(int resolveId) {
+        if (DBG) Slog.d(TAG, "stopGetAdddrInfo: " + resolveId);
+        try {
+            mNativeConnector.execute("mdnssd", "stop-getaddrinfo", resolveId);
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to stopGetAddrInfo " + e);
+            return false;
+        }
+        return true;
     }
 
     @Override
@@ -266,4 +519,51 @@
 
         pw.println("Internal state:");
     }
+
+    private ClientInfo getClientByDiscovery(int discoveryId) {
+        for (ClientInfo c: mClients.values()) {
+            if (c.mDiscoveryId == discoveryId) {
+                return c;
+            }
+        }
+        return null;
+    }
+
+    private ClientInfo getClientByResolve(int resolveId) {
+        for (ClientInfo c: mClients.values()) {
+            if (c.mResolveId == resolveId) {
+                return c;
+            }
+        }
+        return null;
+    }
+
+    private ClientInfo getClientByRegistration(int regId) {
+        for (ClientInfo c: mClients.values()) {
+            if (c.mRegisteredIds.contains(regId)) {
+                return c;
+            }
+        }
+        return null;
+    }
+
+    /* Information tracked per client */
+    private class ClientInfo {
+
+        private static final int MAX_REG = 5;
+        private AsyncChannel mChannel;
+        private Messenger mMessenger;
+        private int mDiscoveryId;
+        private int mResolveId;
+        /* Remembers a resolved service until getaddrinfo completes */
+        private DnsSdServiceInfo mResolvedService;
+        private ArrayList<Integer> mRegisteredIds = new ArrayList<Integer>();
+
+        private ClientInfo(AsyncChannel c, Messenger m) {
+            mChannel = c;
+            mMessenger = m;
+            mDiscoveryId = mResolveId = INVALID_ID;
+            if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
+        }
+    }
 }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 78b441a..e37adc7 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -697,6 +697,16 @@
     boolean mSleeping = false;
 
     /**
+     * State of external calls telling us if the device is asleep.
+     */
+    boolean mWentToSleep = false;
+
+    /**
+     * State of external call telling us if the lock screen is shown.
+     */
+    boolean mLockScreenShown = false;
+
+    /**
      * Set if we are shutting down the system, similar to sleeping.
      */
     boolean mShuttingDown = false;
@@ -6656,17 +6666,26 @@
     }
 
     public void goingToSleep() {
+        if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.DEVICE_POWER);
+        }
+
         synchronized(this) {
-            mSleeping = true;
+            mWentToSleep = true;
             mWindowManager.setEventDispatching(false);
 
-            mMainStack.stopIfSleepingLocked();
+            if (!mSleeping) {
+                mSleeping = true;
+                mMainStack.stopIfSleepingLocked();
 
-            // Initialize the wake times of all processes.
-            checkExcessivePowerUsageLocked(false);
-            mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
-            Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
-            mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
+                // Initialize the wake times of all processes.
+                checkExcessivePowerUsageLocked(false);
+                mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
+            }
         }
     }
 
@@ -6726,12 +6745,40 @@
         Binder.restoreCallingIdentity(origId);
     }
 
+    private void comeOutOfSleepIfNeededLocked() {
+        if (!mWentToSleep && !mLockScreenShown) {
+            if (mSleeping) {
+                mSleeping = false;
+                mMainStack.awakeFromSleepingLocked();
+                mMainStack.resumeTopActivityLocked(null);
+            }
+        }
+    }
+
     public void wakingUp() {
+        if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.DEVICE_POWER);
+        }
+
         synchronized(this) {
+            mWentToSleep = false;
             mWindowManager.setEventDispatching(true);
-            mSleeping = false;
-            mMainStack.awakeFromSleepingLocked();
-            mMainStack.resumeTopActivityLocked(null);
+            comeOutOfSleepIfNeededLocked();
+        }
+    }
+
+    public void setLockScreenShown(boolean shown) {
+        if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.DEVICE_POWER);
+        }
+
+        synchronized(this) {
+            mLockScreenShown = shown;
+            comeOutOfSleepIfNeededLocked();
         }
     }
 
@@ -8309,7 +8356,7 @@
                     + android.Manifest.permission.DUMP);
             return;
         }
-        
+
         boolean dumpAll = false;
         boolean dumpClient = false;
         String dumpPackage = null;
@@ -8352,7 +8399,9 @@
                 pw.println("Unknown argument: " + opt + "; use -h for help");
             }
         }
-        
+
+        long origId = Binder.clearCallingIdentity();
+        boolean more = false;
         // Is the caller requesting to dump a particular piece of data?
         if (opti < args.length) {
             String cmd = args[opti];
@@ -8361,7 +8410,6 @@
                 synchronized (this) {
                     dumpActivitiesLocked(fd, pw, args, opti, true, dumpClient, null);
                 }
-                return;
             } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
                 String[] newArgs;
                 String name;
@@ -8378,7 +8426,6 @@
                 synchronized (this) {
                     dumpBroadcastsLocked(fd, pw, args, opti, true, name);
                 }
-                return;
             } else if ("intents".equals(cmd) || "i".equals(cmd)) {
                 String[] newArgs;
                 String name;
@@ -8395,7 +8442,6 @@
                 synchronized (this) {
                     dumpPendingIntentsLocked(fd, pw, args, opti, true, name);
                 }
-                return;
             } else if ("processes".equals(cmd) || "p".equals(cmd)) {
                 String[] newArgs;
                 String name;
@@ -8412,12 +8458,10 @@
                 synchronized (this) {
                     dumpProcessesLocked(fd, pw, args, opti, true, name);
                 }
-                return;
             } else if ("oom".equals(cmd) || "o".equals(cmd)) {
                 synchronized (this) {
                     dumpOomLocked(fd, pw, args, opti, true);
                 }
-                return;
             } else if ("provider".equals(cmd)) {
                 String[] newArgs;
                 String name;
@@ -8434,12 +8478,10 @@
                     pw.println("No providers match: " + name);
                     pw.println("Use -h for help.");
                 }
-                return;
             } else if ("providers".equals(cmd) || "prov".equals(cmd)) {
                 synchronized (this) {
                     dumpProvidersLocked(fd, pw, args, opti, true, null);
                 }
-                return;
             } else if ("service".equals(cmd)) {
                 String[] newArgs;
                 String name;
@@ -8457,13 +8499,11 @@
                     pw.println("No services match: " + name);
                     pw.println("Use -h for help.");
                 }
-                return;
             } else if ("package".equals(cmd)) {
                 String[] newArgs;
                 if (opti >= args.length) {
                     pw.println("package: no package name specified");
                     pw.println("Use -h for help.");
-                    return;
                 } else {
                     dumpPackage = args[opti];
                     opti++;
@@ -8472,22 +8512,25 @@
                             args.length - opti);
                     args = newArgs;
                     opti = 0;
+                    more = true;
                 }
             } else if ("services".equals(cmd) || "s".equals(cmd)) {
                 synchronized (this) {
                     dumpServicesLocked(fd, pw, args, opti, true, dumpClient, null);
                 }
-                return;
             } else {
                 // Dumping a single activity?
                 if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll)) {
                     pw.println("Bad activity command, or no activities match: " + cmd);
                     pw.println("Use -h for help.");
                 }
+            }
+            if (!more) {
+                Binder.restoreCallingIdentity(origId);
                 return;
             }
         }
-        
+
         // No piece of data specified, dump everything.
         synchronized (this) {
             boolean needSep;
@@ -8528,8 +8571,9 @@
             }
             dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
         }
+        Binder.restoreCallingIdentity(origId);
     }
-    
+
     boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
         pw.println("ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)");
@@ -8818,7 +8862,13 @@
                 }
             }
         }
-        pw.println("  mSleeping=" + mSleeping + " mShuttingDown=" + mShuttingDown);
+        if (mSleeping || mWentToSleep || mLockScreenShown) {
+            pw.println("  mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep
+                    + " mLockScreenShown " + mLockScreenShown);
+        }
+        if (mShuttingDown) {
+            pw.println("  mShuttingDown=" + mShuttingDown);
+        }
         if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
                 || mOrigWaitForDebugger) {
             pw.println("  mDebugApp=" + mDebugApp + "/orig=" + mOrigDebugApp
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index 2f25df1..ce7671f 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -34,18 +34,23 @@
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.hardware.input.IInputManager;
+import android.hardware.input.IInputDevicesChangedListener;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
 import android.os.MessageQueue;
 import android.os.Process;
+import android.os.RemoteException;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.Xml;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -77,12 +82,33 @@
 
     private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
 
+    private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
+
     // Pointer to native input manager service object.
     private final int mPtr;
 
     private final Context mContext;
     private final Callbacks mCallbacks;
-    private final Handler mHandler;
+    private final InputManagerHandler mHandler;
+
+    // Used to simulate a persistent data store for keyboard layouts.
+    // TODO: Replace with the real thing.
+    private final HashMap<String, String> mFakeRegistry = new HashMap<String, String>();
+
+    // List of currently registered input devices changed listeners by process id.
+    private Object mInputDevicesLock = new Object();
+    private boolean mInputDevicesChangedPending; // guarded by mInputDevicesLock
+    private InputDevice[] mInputDevices = new InputDevice[0];
+    private final SparseArray<InputDevicesChangedListenerRecord> mInputDevicesChangedListeners =
+            new SparseArray<InputDevicesChangedListenerRecord>(); // guarded by mInputDevicesLock
+    private final ArrayList<InputDevicesChangedListenerRecord>
+            mTempInputDevicesChangedListenersToNotify =
+                    new ArrayList<InputDevicesChangedListenerRecord>(); // handler thread only
+
+    // State for the currently installed input filter.
+    final Object mInputFilterLock = new Object();
+    InputFilter mInputFilter; // guarded by mInputFilterLock
+    InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
 
     private static native int nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
@@ -111,9 +137,7 @@
     private static native void nativeSetSystemUiVisibility(int ptr, int visibility);
     private static native void nativeSetFocusedApplication(int ptr,
             InputApplicationHandle application);
-    private static native InputDevice nativeGetInputDevice(int ptr, int deviceId);
     private static native void nativeGetInputConfiguration(int ptr, Configuration configuration);
-    private static native int[] nativeGetInputDeviceIds(int ptr);
     private static native boolean nativeTransferTouchFocus(int ptr,
             InputChannel fromChannel, InputChannel toChannel);
     private static native void nativeSetPointerSpeed(int ptr, int speed);
@@ -145,19 +169,10 @@
     /** The key is down but is a virtual key press that is being emulated by the system. */
     public static final int KEY_STATE_VIRTUAL = 2;
 
-    // Used to simulate a persistent data store for keyboard layouts.
-    // TODO: Replace with the real thing.
-    private final HashMap<String, String> mFakeRegistry = new HashMap<String, String>();
-
-    // State for the currently installed input filter.
-    final Object mInputFilterLock = new Object();
-    InputFilter mInputFilter;
-    InputFilterHost mInputFilterHost;
-
     public InputManagerService(Context context, Callbacks callbacks) {
         this.mContext = context;
         this.mCallbacks = callbacks;
-        this.mHandler = new Handler();
+        this.mHandler = new InputManagerHandler();
 
         Slog.i(TAG, "Initializing input manager");
         mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
@@ -396,16 +411,98 @@
      */
     @Override // Binder call
     public InputDevice getInputDevice(int deviceId) {
-        return nativeGetInputDevice(mPtr, deviceId);
+        synchronized (mInputDevicesLock) {
+            final int count = mInputDevices.length;
+            for (int i = 0; i < count; i++) {
+                final InputDevice inputDevice = mInputDevices[i];
+                if (inputDevice.getId() == deviceId) {
+                    return inputDevice;
+                }
+            }
+        }
+        return null;
     }
-    
+
     /**
      * Gets the ids of all input devices in the system.
      * @return The input device ids.
      */
     @Override // Binder call
     public int[] getInputDeviceIds() {
-        return nativeGetInputDeviceIds(mPtr);
+        synchronized (mInputDevicesLock) {
+            final int count = mInputDevices.length;
+            int[] ids = new int[count];
+            for (int i = 0; i < count; i++) {
+                ids[i] = mInputDevices[i].getId();
+            }
+            return ids;
+        }
+    }
+
+    @Override // Binder call
+    public void registerInputDevicesChangedListener(IInputDevicesChangedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDevicesLock) {
+            int callingPid = Binder.getCallingPid();
+            if (mInputDevicesChangedListeners.get(callingPid) != null) {
+                throw new SecurityException("The calling process has already "
+                        + "registered an InputDevicesChangedListener.");
+            }
+
+            InputDevicesChangedListenerRecord record =
+                    new InputDevicesChangedListenerRecord(callingPid, listener);
+            try {
+                IBinder binder = listener.asBinder();
+                binder.linkToDeath(record, 0);
+            } catch (RemoteException ex) {
+                // give up
+                throw new RuntimeException(ex);
+            }
+
+            mInputDevicesChangedListeners.put(callingPid, record);
+        }
+    }
+
+    private void onInputDevicesChangedListenerDied(int pid) {
+        synchronized (mInputDevicesLock) {
+            mInputDevicesChangedListeners.remove(pid);
+        }
+    }
+
+    // Must be called on handler.
+    private void deliverInputDevicesChanged() {
+        mTempInputDevicesChangedListenersToNotify.clear();
+
+        final int numListeners;
+        final int[] deviceIdAndGeneration;
+        synchronized (mInputDevicesLock) {
+            if (!mInputDevicesChangedPending) {
+                return;
+            }
+            mInputDevicesChangedPending = false;
+
+            numListeners = mInputDevicesChangedListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                mTempInputDevicesChangedListenersToNotify.add(
+                        mInputDevicesChangedListeners.valueAt(i));
+            }
+
+            final int numDevices = mInputDevices.length;
+            deviceIdAndGeneration = new int[numDevices * 2];
+            for (int i = 0; i < numDevices; i++) {
+                final InputDevice inputDevice = mInputDevices[i];
+                deviceIdAndGeneration[i * 2] = inputDevice.getId();
+                deviceIdAndGeneration[i * 2 + 1] = inputDevice.getGeneration();
+            }
+        }
+
+        for (int i = 0; i < numListeners; i++) {
+            mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged(
+                    deviceIdAndGeneration);
+        }
     }
 
     @Override // Binder call
@@ -741,6 +838,18 @@
     }
 
     // Native callback.
+    private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
+        synchronized (mInputDevicesLock) {
+            mInputDevices = inputDevices;
+
+            if (!mInputDevicesChangedPending) {
+                mInputDevicesChangedPending = true;
+                mHandler.sendEmptyMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED);
+            }
+        }
+    }
+
+    // Native callback.
     private void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
         mCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
     }
@@ -906,6 +1015,20 @@
     }
 
     /**
+     * Private handler for the input manager.
+     */
+    private final class InputManagerHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DELIVER_INPUT_DEVICES_CHANGED:
+                    deliverInputDevicesChanged();
+                    break;
+            }
+        }
+    }
+
+    /**
      * Hosting interface for input filters to call back into the input manager.
      */
     private final class InputFilterHost implements InputFilter.Host {
@@ -957,4 +1080,32 @@
             return result;
         }
     }
+
+    private final class InputDevicesChangedListenerRecord implements DeathRecipient {
+        private final int mPid;
+        private final IInputDevicesChangedListener mListener;
+
+        public InputDevicesChangedListenerRecord(int pid, IInputDevicesChangedListener listener) {
+            mPid = pid;
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Input devices changed listener for pid " + mPid + " died.");
+            }
+            onInputDevicesChangedListenerDied(mPid);
+        }
+
+        public void notifyInputDevicesChanged(int[] info) {
+            try {
+                mListener.onInputDevicesChanged(info);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process "
+                        + mPid + " that input devices changed, assuming it died.", ex);
+                binderDied();
+            }
+        }
+    }
 }
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 1593707..1d02b7a3 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -20,8 +20,6 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.ENFORCEMENT_DEFAULT;
-import static android.content.pm.PackageManager.ENFORCEMENT_YES;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS;
 import static libcore.io.OsConstants.S_ISLNK;
@@ -9030,12 +9028,12 @@
     }
 
     @Override
-    public void setPermissionEnforcement(String permission, int enforcement) {
+    public void setPermissionEnforced(String permission, boolean enforced) {
         mContext.enforceCallingOrSelfPermission(GRANT_REVOKE_PERMISSIONS, null);
         if (READ_EXTERNAL_STORAGE.equals(permission)) {
             synchronized (mPackages) {
-                if (mSettings.mReadExternalStorageEnforcement != enforcement) {
-                    mSettings.mReadExternalStorageEnforcement = enforcement;
+                if (mSettings.mReadExternalStorageEnforced != enforced) {
+                    mSettings.mReadExternalStorageEnforced = enforced;
                     mSettings.writeLPr();
 
                     // kill any non-foreground processes so we restart them and
@@ -9058,27 +9056,18 @@
     }
 
     @Override
-    public int getPermissionEnforcement(String permission) {
+    public boolean isPermissionEnforced(String permission) {
         mContext.enforceCallingOrSelfPermission(GRANT_REVOKE_PERMISSIONS, null);
-        if (READ_EXTERNAL_STORAGE.equals(permission)) {
-            synchronized (mPackages) {
-                return mSettings.mReadExternalStorageEnforcement;
-            }
-        } else {
-            throw new IllegalArgumentException("No selective enforcement for " + permission);
+        synchronized (mPackages) {
+            return isPermissionEnforcedLocked(permission);
         }
     }
 
     private boolean isPermissionEnforcedLocked(String permission) {
         if (READ_EXTERNAL_STORAGE.equals(permission)) {
-            switch (mSettings.mReadExternalStorageEnforcement) {
-                case ENFORCEMENT_DEFAULT:
-                    return false;
-                case ENFORCEMENT_YES:
-                    return true;
-            }
+            return mSettings.mReadExternalStorageEnforced;
+        } else {
+            return true;
         }
-
-        return true;
     }
 }
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index bb7f4fc5c..35b6bde 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -20,7 +20,7 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.ENFORCEMENT_DEFAULT;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
@@ -111,7 +111,7 @@
     int mInternalSdkPlatform;
     int mExternalSdkPlatform;
 
-    int mReadExternalStorageEnforcement = ENFORCEMENT_DEFAULT;
+    boolean mReadExternalStorageEnforced = PackageManager.DEFAULT_ENFORCE_READ_EXTERNAL_STORAGE;
 
     /** Device identity for the purpose of package verification. */
     private VerifierDeviceIdentity mVerifierDeviceIdentity;
@@ -1139,10 +1139,11 @@
                 serializer.endTag(null, "verifier");
             }
 
-            if (mReadExternalStorageEnforcement != ENFORCEMENT_DEFAULT) {
+            if (mReadExternalStorageEnforced
+                    != PackageManager.DEFAULT_ENFORCE_READ_EXTERNAL_STORAGE) {
                 serializer.startTag(null, TAG_READ_EXTERNAL_STORAGE);
                 serializer.attribute(
-                        null, ATTR_ENFORCEMENT, Integer.toString(mReadExternalStorageEnforcement));
+                        null, ATTR_ENFORCEMENT, mReadExternalStorageEnforced ? "1" : "0");
                 serializer.endTag(null, TAG_READ_EXTERNAL_STORAGE);
             }
 
@@ -1547,10 +1548,7 @@
                     }
                 } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
                     final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT);
-                    try {
-                        mReadExternalStorageEnforcement = Integer.parseInt(enforcement);
-                    } catch (NumberFormatException e) {
-                    }
+                    mReadExternalStorageEnforced = "1".equals(enforcement);
                 } else {
                     Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
                             + parser.getName());
@@ -2558,9 +2556,13 @@
             if (p.perm != null) {
                 pw.print("    perm="); pw.println(p.perm);
             }
+            if (READ_EXTERNAL_STORAGE.equals(p.name)) {
+                pw.print("    enforced=");
+                pw.println(mReadExternalStorageEnforced);
+            }
         }
     }
-    
+
     void dumpSharedUsersLPr(PrintWriter pw, String packageName, DumpState dumpState) {
         boolean printedSomething = false;
         for (SharedUserSetting su : mSharedUsers.values()) {
diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java
index e460f7fd..3dcfd3c 100644
--- a/services/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -33,6 +33,7 @@
     static final String TAG = "ScreenRotationAnimation";
     static final boolean DEBUG_STATE = false;
     static final boolean DEBUG_TRANSFORMS = false;
+    static final boolean TWO_PHASE_ANIMATION = false;
     static final boolean USE_CUSTOM_BLACK_FRAME = false;
 
     static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200;
@@ -44,7 +45,6 @@
     BlackFrame mEnteringBlackFrame;
     int mWidth, mHeight;
 
-    int mSnapshotDeltaRotation;
     int mOriginalRotation;
     int mOriginalWidth, mOriginalHeight;
     int mCurRotation;
@@ -138,10 +138,9 @@
         if (mEnteringBlackFrame != null) {
             mEnteringBlackFrame.printTo(prefix + "  ", pw);
         }
-        pw.print(prefix); pw.print(" mSnapshotDeltaRotation="); pw.print(mSnapshotDeltaRotation);
-                pw.print(" mCurRotation="); pw.println(mCurRotation);
-        pw.print(prefix); pw.print("mOriginalRotation="); pw.print(mOriginalRotation);
-                pw.print(" mOriginalWidth="); pw.print(mOriginalWidth);
+        pw.print(prefix); pw.print("mCurRotation="); pw.print(mCurRotation);
+                pw.print(" mOriginalRotation="); pw.println(mOriginalRotation);
+        pw.print(prefix); pw.print("mOriginalWidth="); pw.print(mOriginalWidth);
                 pw.print(" mOriginalHeight="); pw.println(mOriginalHeight);
         pw.print(prefix); pw.print("mStarted="); pw.print(mStarted);
                 pw.print(" mAnimRunning="); pw.print(mAnimRunning);
@@ -306,8 +305,13 @@
     public boolean setRotation(int rotation, SurfaceSession session,
             long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
         setRotation(rotation);
-        return startAnimation(session, maxAnimationDuration, animationScale,
-                finalWidth, finalHeight, false);
+        if (TWO_PHASE_ANIMATION) {
+            return startAnimation(session, maxAnimationDuration, animationScale,
+                    finalWidth, finalHeight, false);
+        } else {
+            // Don't start animation yet.
+            return false;
+        }
     }
 
     /**
@@ -330,7 +334,8 @@
         // Figure out how the screen has moved from the original rotation.
         int delta = deltaRotation(mCurRotation, mOriginalRotation);
 
-        if (mFinishExitAnimation == null && (!dismissing || delta != Surface.ROTATION_0)) {
+        if (TWO_PHASE_ANIMATION && mFinishExitAnimation == null
+                && (!dismissing || delta != Surface.ROTATION_0)) {
             if (DEBUG_STATE) Slog.v(TAG, "Creating start and finish animations");
             firstStart = true;
             mStartExitAnimation = AnimationUtils.loadAnimation(mContext,
@@ -406,7 +411,7 @@
         // means to allow supplying the last and next size.  In this definition
         // "%p" is the original (let's call it "previous") size, and "%" is the
         // screen's current/new size.
-        if (firstStart) {
+        if (TWO_PHASE_ANIMATION && firstStart) {
             if (DEBUG_STATE) Slog.v(TAG, "Initializing start and finish animations");
             mStartEnterAnimation.initialize(finalWidth, finalHeight,
                     halfWidth, halfHeight);
@@ -433,7 +438,7 @@
         mFinishAnimReady = false;
         mFinishAnimStartTime = -1;
 
-        if (firstStart) {
+        if (TWO_PHASE_ANIMATION && firstStart) {
             mStartExitAnimation.restrictDuration(maxAnimationDuration);
             mStartExitAnimation.scaleCurrentDuration(animationScale);
             mStartEnterAnimation.restrictDuration(maxAnimationDuration);
@@ -624,6 +629,14 @@
     }
 
     public boolean isAnimating() {
+        if (TWO_PHASE_ANIMATION) {
+            return hasAnimations() || mFinishAnimReady;
+        } else {
+            return hasAnimations();
+        }
+    }
+
+    private boolean hasAnimations() {
         return mStartEnterAnimation != null || mStartExitAnimation != null
                 || mStartFrameAnimation != null
                 || mFinishEnterAnimation != null || mFinishExitAnimation != null
@@ -633,7 +646,6 @@
     }
 
     private boolean stepAnimation(long now) {
-
         if (mFinishAnimReady && mFinishAnimStartTime < 0) {
             if (DEBUG_STATE) Slog.v(TAG, "Step: finish anim now ready");
             mFinishAnimStartTime = now;
@@ -794,6 +806,10 @@
     }
 
     void updateSurfaces() {
+        if (!mStarted) {
+            return;
+        }
+
         if (mSurface != null) {
             if (!mMoreStartExit && !mMoreFinishExit && !mMoreRotateExit) {
                 if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, hiding screenshot surface");
@@ -834,7 +850,7 @@
     }
     
     public boolean stepAnimationLocked(long now) {
-        if (!isAnimating()) {
+        if (!hasAnimations()) {
             if (DEBUG_STATE) Slog.v(TAG, "Step: no animations running");
             mFinishAnimReady = false;
             return false;
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index 0be6612..00fd7d8 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -22,7 +22,7 @@
 import com.android.internal.policy.impl.PhoneWindowManager;
 
 import java.io.PrintWriter;
-import java.util.HashSet;
+import java.util.ArrayList;
 
 /**
  * Singleton class that carries out the animations and Surface operations in a separate task
@@ -35,8 +35,7 @@
     final Context mContext;
     final WindowManagerPolicy mPolicy;
 
-    HashSet<WindowStateAnimator> mWinAnimators = new HashSet<WindowStateAnimator>();
-    HashSet<WindowStateAnimator> mFinished = new HashSet<WindowStateAnimator>();
+    ArrayList<WindowStateAnimator> mWinAnimators = new ArrayList<WindowStateAnimator>();
 
     boolean mAnimating;
     boolean mTokenMayBeDrawn;
@@ -158,9 +157,7 @@
             }
         }
 
-        if (mScreenRotationAnimation != null &&
-                (mScreenRotationAnimation.isAnimating() ||
-                        mScreenRotationAnimation.mFinishAnimReady)) {
+        if (mScreenRotationAnimation != null && mScreenRotationAnimation.isAnimating()) {
             if (mScreenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
                 mAnimating = true;
             } else {
@@ -174,6 +171,9 @@
     private void updateWindowsAndWallpaperLocked() {
         ++mTransactionSequence;
 
+        ArrayList<WindowStateAnimator> unForceHiding = null;
+        boolean wallpaperInUnForceHiding = false;
+
         for (int i = mService.mWindows.size() - 1; i >= 0; i--) {
             WindowState win = mService.mWindows.get(i);
             WindowStateAnimator winAnimator = win.mWinAnimator;
@@ -269,13 +269,12 @@
                         if (changed) {
                             if ((mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0
                                     && win.isVisibleNow() /*w.isReadyForDisplay()*/) {
-                                // Assume we will need to animate.  If
-                                // we don't (because the wallpaper will
-                                // stay with the lock screen), then we will
-                                // clean up later.
-                                Animation a = mPolicy.createForceHideEnterAnimation();
-                                if (a != null) {
-                                    winAnimator.setAnimation(a);
+                                if (unForceHiding == null) {
+                                    unForceHiding = new ArrayList<WindowStateAnimator>();
+                                }
+                                unForceHiding.add(winAnimator);
+                                if ((win.mAttrs.flags&WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
+                                    wallpaperInUnForceHiding = true;
                                 }
                             }
                             if (mCurrentFocus == null || mCurrentFocus.mLayer < win.mLayer) {
@@ -358,6 +357,17 @@
                 }
             }
         } // end forall windows
+
+        // If we have windows that are being show due to them no longer
+        // being force-hidden, apply the appropriate animation to them.
+        if (unForceHiding != null) {
+            for (int i=unForceHiding.size()-1; i>=0; i--) {
+                Animation a = mPolicy.createForceHideEnterAnimation(wallpaperInUnForceHiding);
+                if (a != null) {
+                    unForceHiding.get(i).setAnimation(a);
+                }
+            }
+        }
     }
 
     private void testTokenMayBeDrawnLocked() {
@@ -437,16 +447,9 @@
                 mScreenRotationAnimation.updateSurfaces();
             }
 
-            mFinished.clear();
-            for (final WindowStateAnimator winAnimator : mWinAnimators) {
-                if (winAnimator.mSurface == null) {
-                    mFinished.add(winAnimator);
-                } else {
-                    winAnimator.prepareSurfaceLocked(true);
-                }
-            }
-            for (final WindowStateAnimator winAnimator : mFinished) {
-                mWinAnimators.remove(winAnimator);
+            final int N = mWinAnimators.size();
+            for (int i = 0; i < N; i++) {
+                mWinAnimators.get(i).prepareSurfaceLocked(true);
             }
 
             if (mDimParams != null) {
@@ -465,6 +468,10 @@
                     mService.mBlackFrame.clearMatrix();
                 }
             }
+
+            if (mService.mWatermark != null) {
+                mService.mWatermark.drawIfNeeded();
+            }
         } catch (RuntimeException e) {
             Log.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index d9425aab..82018fe 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -175,6 +175,7 @@
     static final boolean DEBUG_SCREENSHOT = false;
     static final boolean DEBUG_BOOT = false;
     static final boolean DEBUG_LAYOUT_REPEATS = true;
+    static final boolean DEBUG_SURFACE_TRACE = false;
     static final boolean SHOW_SURFACE_ALLOC = false;
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
@@ -425,7 +426,7 @@
 
     IInputMethodManager mInputMethodManager;
 
-    SurfaceSession mFxSession;
+    final SurfaceSession mFxSession;
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
 
@@ -862,6 +863,11 @@
 
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
+        mFxSession = new SurfaceSession();
+
+        Surface.openTransaction();
+        createWatermark();
+        Surface.closeTransaction();
     }
 
     public InputManagerService getInputManagerService() {
@@ -3974,7 +3980,6 @@
 
         wtoken.willBeHidden = false;
         if (wtoken.hidden == visible) {
-            final int N = wtoken.allAppWindows.size();
             boolean changed = false;
             if (DEBUG_APP_TRANSITIONS) Slog.v(
                 TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden
@@ -3986,23 +3991,19 @@
                 if (wtoken.mAppAnimator.animation == sDummyAnimation) {
                     wtoken.mAppAnimator.animation = null;
                 }
-                applyAnimationLocked(wtoken, lp, transit, visible);
-                changed = true;
-                if (wtoken.mAppAnimator.animation != null) {
+                if (applyAnimationLocked(wtoken, lp, transit, visible)) {
                     delayed = runningAppAnimation = true;
                 }
+                changed = true;
             }
 
+            final int N = wtoken.allAppWindows.size();
             for (int i=0; i<N; i++) {
                 WindowState win = wtoken.allAppWindows.get(i);
                 if (win == wtoken.startingWindow) {
                     continue;
                 }
 
-                if (win.mWinAnimator.isAnimating()) {
-                    delayed = true;
-                }
-
                 //Slog.i(TAG, "Window " + win + ": vis=" + win.isVisible());
                 //win.dump("  ");
                 if (visible) {
@@ -4055,6 +4056,12 @@
             delayed = true;
         }
 
+        for (int i = wtoken.allAppWindows.size() - 1; i >= 0 && !delayed; i--) {
+            if (wtoken.allAppWindows.get(i).mWinAnimator.isWindowAnimating()) {
+                delayed = true;
+            }
+        }
+
         return delayed;
     }
 
@@ -4917,7 +4924,11 @@
                 // have been drawn.
                 boolean haveBootMsg = false;
                 boolean haveApp = false;
+                // if the wallpaper service is disabled on the device, we're never going to have
+                // wallpaper, don't bother waiting for it
                 boolean haveWallpaper = false;
+                boolean wallpaperEnabled = mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_enableWallpaperService);
                 boolean haveKeyguard = true;
                 final int N = mWindows.size();
                 for (int i=0; i<N; i++) {
@@ -4953,7 +4964,8 @@
                 if (DEBUG_SCREEN_ON || DEBUG_BOOT) {
                     Slog.i(TAG, "******** booted=" + mSystemBooted + " msg=" + mShowingBootMessages
                             + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp
-                            + " haveWall=" + haveWallpaper + " haveKeyguard=" + haveKeyguard);
+                            + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled
+                            + " haveKeyguard=" + haveKeyguard);
                 }
 
                 // If we are turning on the screen to show the boot message,
@@ -4965,7 +4977,8 @@
                 // If we are turning on the screen after the boot is completed
                 // normally, don't do so until we have the application and
                 // wallpaper.
-                if (mSystemBooted && ((!haveApp && !haveKeyguard) || !haveWallpaper)) {
+                if (mSystemBooted && ((!haveApp && !haveKeyguard) ||
+                        (wallpaperEnabled && !haveWallpaper))) {
                     return;
                 }
             }
@@ -7262,6 +7275,7 @@
                     pw.flush();
                     Slog.w(TAG, "This window was lost: " + ws);
                     Slog.w(TAG, sw.toString());
+                    ws.mWinAnimator.destroySurfaceLocked();
                 }
             }
             Slog.w(TAG, "Current app token list:");
@@ -7584,7 +7598,8 @@
                 if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                         "Check opening app" + wtoken + ": allDrawn="
                         + wtoken.allDrawn + " startingDisplayed="
-                        + wtoken.startingDisplayed);
+                        + wtoken.startingDisplayed + " startingMoved="
+                        + wtoken.startingMoved);
                 if (!wtoken.allDrawn && !wtoken.startingDisplayed
                         && !wtoken.startingMoved) {
                     goodToGo = false;
@@ -7886,28 +7901,6 @@
         if (DEBUG_WALLPAPER) Slog.v(TAG, "****** OLD: " + oldWallpaper
                 + " NEW: " + mWallpaperTarget
                 + " LOWER: " + mLowerWallpaperTarget);
-        if (mLowerWallpaperTarget == null) {
-            // Whoops, we don't need a special wallpaper animation.
-            // Clear them out.
-            mAnimator.mForceHiding = false;
-            for (int i=mWindows.size()-1; i>=0; i--) {
-                WindowState w = mWindows.get(i);
-                if (w.mHasSurface) {
-                    final WindowManager.LayoutParams attrs = w.mAttrs;
-                    if (mPolicy.doesForceHide(w, attrs) && w.isVisibleLw()) {
-                        if (DEBUG_FOCUS) Slog.i(TAG, "win=" + w + " force hides other windows");
-                        mAnimator.mForceHiding = true;
-                    } else if (mPolicy.canBeForceHidden(w, attrs)) {
-                        if (!w.mWinAnimator.mAnimating) {
-                            // We set the animation above so it
-                            // is not yet running.
-                            // TODO(cmautner): We lose the enter animation when this occurs.
-                            w.mWinAnimator.clearAnimation();
-                        }
-                    }
-                }
-            }
-        }
         return changes;
     }
 
@@ -8072,23 +8065,13 @@
         mInnerFields.mHoldScreen = null;
         mInnerFields.mScreenBrightness = -1;
         mInnerFields.mButtonBrightness = -1;
-        boolean focusDisplayed = false;
         mAnimator.mAnimating = false;
-        boolean createWatermark = false;
-
-        if (mFxSession == null) {
-            mFxSession = new SurfaceSession();
-            createWatermark = true;
-        }
 
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                 ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
 
         Surface.openTransaction();
 
-        if (createWatermark) {
-            createWatermark();
-        }
         if (mWatermark != null) {
             mWatermark.positionSurface(dw, dh);
         }
@@ -8159,6 +8142,7 @@
             mInnerFields.mDimming = false;
             mInnerFields.mSyswin = false;
             
+            boolean focusDisplayed = false;
             final int N = mWindows.size();
             for (i=N-1; i>=0; i--) {
                 WindowState w = mWindows.get(i);
@@ -8182,6 +8166,10 @@
                     updateWallpaperVisibilityLocked();
                 }
             }
+            if (focusDisplayed) {
+                mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS);
+            }
+
             if (!mInnerFields.mDimming && mAnimator.isDimming()) {
                 mAnimator.stopDimming();
             }
@@ -8308,15 +8296,18 @@
 
         // Update animations of all applications, including those
         // associated with exiting/removed apps
-        mAnimator.animate();
-        mPendingLayoutChanges |= mAnimator.mPendingLayoutChanges;
-        if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animate()", mPendingLayoutChanges);
-
-        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
-
-        if (mWatermark != null) {
-            mWatermark.drawIfNeeded();
+        synchronized (mAnimator) {
+            final ArrayList<WindowStateAnimator> winAnimators = mAnimator.mWinAnimators;
+            winAnimators.clear();
+            for (i = 0; i < N; i++) {
+                final WindowStateAnimator winAnimator = mWindows.get(i).mWinAnimator;
+                if (winAnimator.mSurface != null) {
+                    winAnimators.add(winAnimator);
+                }
+            }
+            mAnimator.animate();
+            mPendingLayoutChanges |= mAnimator.mPendingLayoutChanges;
+            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animate()", mPendingLayoutChanges);
         }
 
         if (DEBUG_ORIENTATION && mDisplayFrozen) Slog.v(TAG,
@@ -8429,9 +8420,6 @@
             mToBottomApps.clear();
         }
 
-        if (focusDisplayed) {
-            mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS);
-        }
         if (wallpaperDestroyed) {
             mLayoutNeeded |= adjustWallpaperWindowsLocked() != 0;
         }
@@ -8602,7 +8590,6 @@
                         wsa.mSurfaceShown = false;
                         wsa.mSurface = null;
                         ws.mHasSurface = false;
-                        mAnimator.mWinAnimators.remove(wsa);
                         mForceRemoves.add(ws);
                         i--;
                         N--;
@@ -8616,7 +8603,6 @@
                         wsa.mSurfaceShown = false;
                         wsa.mSurface = null;
                         ws.mHasSurface = false;
-                        mAnimator.mWinAnimators.remove(wsa);
                         leakedSurface = true;
                     }
                 }
@@ -8656,7 +8642,6 @@
                     winAnimator.mSurfaceShown = false;
                     winAnimator.mSurface = null;
                     winAnimator.mWin.mHasSurface = false;
-                    mAnimator.mWinAnimators.remove(winAnimator);
                 }
 
                 try {
@@ -9333,7 +9318,7 @@
                     pw.print(" mWaitingForConfig="); pw.println(mWaitingForConfig);
             pw.print("  mRotation="); pw.print(mRotation);
                     pw.print(" mAltOrientation="); pw.println(mAltOrientation);
-            pw.print("  mLastWindowForcedOrientation"); pw.print(mLastWindowForcedOrientation);
+            pw.print("  mLastWindowForcedOrientation="); pw.print(mLastWindowForcedOrientation);
                     pw.print(" mForcedAppOrientation="); pw.println(mForcedAppOrientation);
             pw.print("  mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
             if (mAnimator.mScreenRotationAnimation != null) {
@@ -9535,4 +9520,9 @@
     void bulkSetParameters(final int bulkUpdateParams) {
         mH.sendMessage(mH.obtainMessage(H.BULK_UPDATE_PARAMETERS, bulkUpdateParams, 0));
     }
+
+    static String getCaller() {
+        StackTraceElement caller = Thread.currentThread().getStackTrace()[4];
+        return caller.getClassName() + "." + caller.getMethodName() + ":" + caller.getLineNumber();
+    }
 }
diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java
index 941a5e1..164325b 100644
--- a/services/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -10,11 +10,14 @@
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.Surface;
+import android.view.SurfaceSession;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicy;
 import android.view.WindowManager.LayoutParams;
@@ -25,6 +28,7 @@
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 
 /**
  * Keep track of animations and surface operations for a single WindowState.
@@ -39,6 +43,7 @@
     static final boolean SHOW_SURFACE_ALLOC = WindowManagerService.SHOW_SURFACE_ALLOC;
     static final boolean localLOGV = WindowManagerService.localLOGV;
     static final boolean DEBUG_ORIENTATION = WindowManagerService.DEBUG_ORIENTATION;
+    static final boolean DEBUG_SURFACE_TRACE = WindowManagerService.DEBUG_SURFACE_TRACE;
 
     static final String TAG = "WindowStateAnimator";
 
@@ -398,6 +403,105 @@
         return true;
     }
 
+    static class MySurface extends Surface {
+        final static ArrayList<MySurface> sSurfaces = new ArrayList<MySurface>();
+
+        private float mMySurfaceAlpha = 0xff;
+        private int mLayer;
+        private PointF mPosition = new PointF();
+        private Point mSize = new Point();
+        private boolean mShown = false;
+        private String mName = "Not named";
+
+        public MySurface(SurfaceSession s,
+                       int pid, int display, int w, int h, int format, int flags) throws
+                       OutOfResourcesException {
+            super(s, pid, display, w, h, format, flags);
+            mSize = new Point(w, h);
+            Slog.v("SurfaceTrace", "ctor: " + this);
+        }
+
+        public MySurface(SurfaceSession s,
+                       int pid, String name, int display, int w, int h, int format, int flags)
+                   throws OutOfResourcesException {
+            super(s, pid, name, display, w, h, format, flags);
+            mName = name;
+            mSize = new Point(w, h);
+            Slog.v("SurfaceTrace", "ctor: " + this);
+        }
+
+        @Override
+        public void setAlpha(float alpha) {
+            super.setAlpha(alpha);
+            mMySurfaceAlpha = alpha;
+            Slog.v("SurfaceTrace", "setAlpha: " + this);
+        }
+
+        @Override
+        public void setLayer(int zorder) {
+            super.setLayer(zorder);
+            mLayer = zorder;
+            Slog.v("SurfaceTrace", "setLayer: " + this);
+
+            sSurfaces.remove(this);
+            int i;
+            for (i = sSurfaces.size() - 1; i >= 0; i--) {
+                MySurface s = sSurfaces.get(i);
+                if (s.mLayer < zorder) {
+                    break;
+                }
+            }
+            sSurfaces.add(i + 1, this);
+        }
+
+        @Override
+        public void setPosition(float x, float y) {
+            super.setPosition(x, y);
+            mPosition = new PointF(x, y);
+        }
+
+        @Override
+        public void setSize(int w, int h) {
+            super.setSize(w, h);
+            mSize = new Point(w, h);
+        }
+
+        @Override
+        public void hide() {
+            super.hide();
+            mShown = false;
+            Slog.v("SurfaceTrace", "hide: " + this);
+        }
+        @Override
+        public void show() {
+            super.show();
+            mShown = true;
+            Slog.v("SurfaceTrace", "show: " + this);
+        }
+
+        @Override
+        public void destroy() {
+            super.destroy();
+            Slog.v("SurfaceTrace", "destroy: " + this + ". Called by "
+                    + WindowManagerService.getCaller());
+            sSurfaces.remove(this);
+        }
+
+        static void dumpAllSurfaces() {
+            final int N = sSurfaces.size();
+            for (int i = 0; i < N; i++) {
+                Slog.i(TAG, "SurfaceDump: " + sSurfaces.get(i));
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Surface " + mName + ": shown=" + mShown + " layer=" + mLayer
+                    + " alpha=" + mMySurfaceAlpha + " " + mPosition.x + "," + mPosition.y
+                    + " " + mSize.x + "x" + mSize.y;
+        }
+    }
+
     Surface createSurfaceLocked() {
         if (mSurface == null) {
             mReportDestroySurface = false;
@@ -452,12 +556,18 @@
                 if (!PixelFormat.formatHasAlpha(attrs.format)) {
                     flags |= Surface.OPAQUE;
                 }
-                mSurface = new Surface(
+                if (DEBUG_SURFACE_TRACE) {
+                    mSurface = new MySurface(
+                            mSession.mSurfaceSession, mSession.mPid,
+                            attrs.getTitle().toString(),
+                            0, w, h, format, flags);
+                } else {
+                    mSurface = new Surface(
                         mSession.mSurfaceSession, mSession.mPid,
                         attrs.getTitle().toString(),
                         0, w, h, format, flags);
+                }
                 mWin.mHasSurface = true;
-                mAnimator.mWinAnimators.add(this);
                 if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
                         "  CREATE SURFACE "
                         + mSurface + " IN SESSION "
@@ -468,14 +578,12 @@
                         + " / " + this);
             } catch (Surface.OutOfResourcesException e) {
                 mWin.mHasSurface = false;
-                mAnimator.mWinAnimators.remove(this);
                 Slog.w(TAG, "OutOfResourcesException creating surface");
                 mService.reclaimSomeSurfaceMemoryLocked(this, "create", true);
                 mDrawState = NO_SURFACE;
                 return null;
             } catch (Exception e) {
                 mWin.mHasSurface = false;
-                mAnimator.mWinAnimators.remove(this);
                 Slog.e(TAG, "Exception creating surface", e);
                 mDrawState = NO_SURFACE;
                 return null;
@@ -593,7 +701,6 @@
             mSurfaceShown = false;
             mSurface = null;
             mWin.mHasSurface =false;
-            mAnimator.mWinAnimators.remove(this);
         }
     }
 
diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp
index 85d6e11..f1536fd 100644
--- a/services/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/jni/com_android_server_input_InputManagerService.cpp
@@ -59,6 +59,7 @@
 
 static struct {
     jmethodID notifyConfigurationChanged;
+    jmethodID notifyInputDevicesChanged;
     jmethodID notifyLidSwitchChanged;
     jmethodID notifyInputChannelBroken;
     jmethodID notifyANR;
@@ -82,6 +83,10 @@
 
 static struct {
     jclass clazz;
+} gInputDeviceClassInfo;
+
+static struct {
+    jclass clazz;
 } gKeyEventClassInfo;
 
 static struct {
@@ -179,6 +184,7 @@
 
     virtual void getReaderConfiguration(InputReaderConfiguration* outConfig);
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
+    virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices);
 
     /* --- InputDispatcherPolicyInterface implementation --- */
 
@@ -486,6 +492,36 @@
     }
 }
 
+void NativeInputManager::notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) {
+    JNIEnv* env = jniEnv();
+
+    size_t count = inputDevices.size();
+    jobjectArray inputDevicesObjArray = env->NewObjectArray(
+            count, gInputDeviceClassInfo.clazz, NULL);
+    if (inputDevicesObjArray) {
+        bool error = false;
+        for (size_t i = 0; i < count; i++) {
+            jobject inputDeviceObj = android_view_InputDevice_create(env, inputDevices.itemAt(i));
+            if (!inputDeviceObj) {
+                error = true;
+                break;
+            }
+
+            env->SetObjectArrayElement(inputDevicesObjArray, i, inputDeviceObj);
+            env->DeleteLocalRef(inputDeviceObj);
+        }
+
+        if (!error) {
+            env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputDevicesChanged,
+                    inputDevicesObjArray);
+        }
+
+        env->DeleteLocalRef(inputDevicesObjArray);
+    }
+
+    checkAndClearExceptionFromCallback(env, "notifyInputDevicesChanged");
+}
+
 void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,
         int32_t switchValue, uint32_t policyFlags) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
@@ -1147,36 +1183,6 @@
     im->setSystemUiVisibility(visibility);
 }
 
-static jobject nativeGetInputDevice(JNIEnv* env,
-        jclass clazz, jint ptr, jint deviceId) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
-    InputDeviceInfo deviceInfo;
-    status_t status = im->getInputManager()->getReader()->getInputDeviceInfo(
-            deviceId, & deviceInfo);
-    if (status) {
-        return NULL;
-    }
-
-    return android_view_InputDevice_create(env, deviceInfo);
-}
-
-static jintArray nativeGetInputDeviceIds(JNIEnv* env,
-        jclass clazz, jint ptr) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
-    Vector<int> deviceIds;
-    im->getInputManager()->getReader()->getInputDeviceIds(deviceIds);
-
-    jintArray deviceIdsObj = env->NewIntArray(deviceIds.size());
-    if (! deviceIdsObj) {
-        return NULL;
-    }
-
-    env->SetIntArrayRegion(deviceIdsObj, 0, deviceIds.size(), deviceIds.array());
-    return deviceIdsObj;
-}
-
 static void nativeGetInputConfiguration(JNIEnv* env,
         jclass clazz, jint ptr, jobject configObj) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1273,10 +1279,6 @@
             (void*) nativeSetInputDispatchMode },
     { "nativeSetSystemUiVisibility", "(II)V",
             (void*) nativeSetSystemUiVisibility },
-    { "nativeGetInputDevice", "(II)Landroid/view/InputDevice;",
-            (void*) nativeGetInputDevice },
-    { "nativeGetInputDeviceIds", "(I)[I",
-            (void*) nativeGetInputDeviceIds },
     { "nativeGetInputConfiguration", "(ILandroid/content/res/Configuration;)V",
             (void*) nativeGetInputConfiguration },
     { "nativeTransferTouchFocus", "(ILandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
@@ -1316,6 +1318,9 @@
     GET_METHOD_ID(gServiceClassInfo.notifyConfigurationChanged, clazz,
             "notifyConfigurationChanged", "(J)V");
 
+    GET_METHOD_ID(gServiceClassInfo.notifyInputDevicesChanged, clazz,
+            "notifyInputDevicesChanged", "([Landroid/view/InputDevice;)V");
+
     GET_METHOD_ID(gServiceClassInfo.notifyLidSwitchChanged, clazz,
             "notifyLidSwitchChanged", "(JZ)V");
 
@@ -1377,6 +1382,11 @@
     GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
             "getPointerIcon", "()Landroid/view/PointerIcon;");
 
+    // InputDevice
+
+    FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
+    gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz));
+
     // KeyEvent
 
     FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent");
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
index 1b4cb15..2ac9365 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -250,7 +250,7 @@
 
         // Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
         mSmsStorageMonitor = new SmsStorageMonitor(this);
-        mSmsUsageMonitor = new SmsUsageMonitor(context.getContentResolver());
+        mSmsUsageMonitor = new SmsUsageMonitor(context);
     }
 
     public void dispose() {
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index d6f96ff..28b729d 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -17,34 +17,40 @@
 package com.android.internal.telephony;
 
 import android.app.Activity;
-import android.app.PendingIntent;
 import android.app.AlertDialog;
+import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.Intent;
 import android.content.DialogInterface;
-import android.content.IntentFilter;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.SQLException;
 import android.net.Uri;
 import android.os.AsyncResult;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.SystemProperties;
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
-import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
 import android.telephony.SmsCbMessage;
 import android.telephony.SmsMessage;
-import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.Html;
+import android.text.Spanned;
 import android.util.Log;
 import android.view.WindowManager;
 
+import com.android.internal.R;
 import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
 import com.android.internal.util.HexDump;
 
@@ -54,22 +60,17 @@
 import java.util.HashMap;
 import java.util.Random;
 
-import com.android.internal.R;
-
+import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
 import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
+import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
 import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE;
 import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU;
 import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF;
-import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
-import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
 
 public abstract class SMSDispatcher extends Handler {
     static final String TAG = "SMS";    // accessed from inner class
     private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg";
 
-    /** Default timeout for SMS sent query */
-    private static final int DEFAULT_SMS_TIMEOUT = 6000;
-
     /** Permission required to receive SMS and SMS-CB messages. */
     public static final String RECEIVE_SMS_PERMISSION = "android.permission.RECEIVE_SMS";
 
@@ -102,23 +103,27 @@
     /** Retry sending a previously failed SMS message */
     private static final int EVENT_SEND_RETRY = 3;
 
-    /** SMS confirm required */
-    private static final int EVENT_POST_ALERT = 4;
+    /** Confirmation required for sending a large number of messages. */
+    private static final int EVENT_SEND_LIMIT_REACHED_CONFIRMATION = 4;
 
     /** Send the user confirmed SMS */
     static final int EVENT_SEND_CONFIRMED_SMS = 5;  // accessed from inner class
 
-    /** Alert is timeout */
-    private static final int EVENT_ALERT_TIMEOUT = 6;
-
-    /** Stop the sending */
+    /** Don't send SMS (user did not confirm). */
     static final int EVENT_STOP_SENDING = 7;        // accessed from inner class
 
+    /** Confirmation required for third-party apps sending to an SMS short code. */
+    private static final int EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE = 8;
+
+    /** Confirmation required for third-party apps sending to an SMS short code. */
+    private static final int EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE = 9;
+
     protected final Phone mPhone;
     protected final Context mContext;
     protected final ContentResolver mResolver;
     protected final CommandsInterface mCm;
     protected final SmsStorageMonitor mStorageMonitor;
+    protected final TelephonyManager mTelephonyManager;
 
     protected final WapPushOverSms mWapPush;
 
@@ -144,7 +149,8 @@
     /** Outgoing message counter. Shared by all dispatchers. */
     private final SmsUsageMonitor mUsageMonitor;
 
-    private final ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT);
+    /** Number of outgoing SmsTrackers waiting for user confirmation. */
+    private int mPendingTrackerCount;
 
     /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
     private PowerManager.WakeLock mWakeLock;
@@ -182,6 +188,7 @@
         mCm = phone.mCM;
         mStorageMonitor = storageMonitor;
         mUsageMonitor = usageMonitor;
+        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
 
         createWakelock();
 
@@ -279,51 +286,44 @@
             sendSms((SmsTracker) msg.obj);
             break;
 
-        case EVENT_POST_ALERT:
+        case EVENT_SEND_LIMIT_REACHED_CONFIRMATION:
             handleReachSentLimit((SmsTracker)(msg.obj));
             break;
 
-        case EVENT_ALERT_TIMEOUT:
-            ((AlertDialog)(msg.obj)).dismiss();
-            msg.obj = null;
-            if (mSTrackers.isEmpty() == false) {
-                try {
-                    SmsTracker sTracker = mSTrackers.remove(0);
-                    sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
-                } catch (CanceledException ex) {
-                    Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
-                }
-            }
-            if (false) {
-                Log.d(TAG, "EVENT_ALERT_TIMEOUT, message stop sending");
-            }
+        case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE:
+            handleConfirmShortCode(false, (SmsTracker)(msg.obj));
+            break;
+
+        case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE:
+            handleConfirmShortCode(true, (SmsTracker)(msg.obj));
             break;
 
         case EVENT_SEND_CONFIRMED_SMS:
-            if (mSTrackers.isEmpty() == false) {
-                SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
-                if (sTracker.isMultipart()) {
-                    sendMultipartSms(sTracker);
-                } else {
-                    sendSms(sTracker);
-                }
-                removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
+        {
+            SmsTracker tracker = (SmsTracker) msg.obj;
+            if (tracker.isMultipart()) {
+                sendMultipartSms(tracker);
+            } else {
+                sendSms(tracker);
             }
+            mPendingTrackerCount--;
             break;
+        }
 
         case EVENT_STOP_SENDING:
-            if (mSTrackers.isEmpty() == false) {
-                // Remove the latest one.
+        {
+            SmsTracker tracker = (SmsTracker) msg.obj;
+            if (tracker.mSentIntent != null) {
                 try {
-                    SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
-                    sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
+                    tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
                 } catch (CanceledException ex) {
-                    Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
+                    Log.e(TAG, "failed to send RESULT_ERROR_LIMIT_EXCEEDED");
                 }
-                removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
             }
+            mPendingTrackerCount--;
             break;
         }
+        }
     }
 
     private void createWakelock() {
@@ -397,7 +397,7 @@
             int ss = mPhone.getServiceState().getState();
 
             if (ss != ServiceState.STATE_IN_SERVICE) {
-                handleNotInService(ss, tracker);
+                handleNotInService(ss, tracker.mSentIntent);
             } else if ((((CommandException)(ar.exception)).getCommandError()
                     == CommandException.Error.SMS_FAIL_RETRY) &&
                    tracker.mRetryCount < MAX_SEND_RETRIES) {
@@ -446,15 +446,15 @@
      *                  OUT_OF_SERVICE
      *                  EMERGENCY_ONLY
      *                  POWER_OFF
-     * @param tracker   An SmsTracker for the current message.
+     * @param sentIntent the PendingIntent to send the error to
      */
-    protected static void handleNotInService(int ss, SmsTracker tracker) {
-        if (tracker.mSentIntent != null) {
+    protected static void handleNotInService(int ss, PendingIntent sentIntent) {
+        if (sentIntent != null) {
             try {
                 if (ss == ServiceState.STATE_POWER_OFF) {
-                    tracker.mSentIntent.send(RESULT_ERROR_RADIO_OFF);
+                    sentIntent.send(RESULT_ERROR_RADIO_OFF);
                 } else {
-                    tracker.mSentIntent.send(RESULT_ERROR_NO_SERVICE);
+                    sentIntent.send(RESULT_ERROR_NO_SERVICE);
                 }
             } catch (CanceledException ex) {}
         }
@@ -865,9 +865,10 @@
      * @param deliveryIntent if not NULL this <code>Intent</code> is
      *  broadcast when the message is delivered to the recipient.  The
      *  raw pdu of the status report is in the extended data ("pdu").
+     * @param destAddr the destination phone number (for short code confirmation)
      */
     protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
-            PendingIntent deliveryIntent) {
+            PendingIntent deliveryIntent, String destAddr) {
         if (mSmsSendDisabled) {
             if (sentIntent != null) {
                 try {
@@ -891,61 +892,190 @@
         map.put("smsc", smsc);
         map.put("pdu", pdu);
 
-        SmsTracker tracker = new SmsTracker(map, sentIntent,
-                deliveryIntent);
-        int ss = mPhone.getServiceState().getState();
+        // Get calling app package name via UID from Binder call
+        PackageManager pm = mContext.getPackageManager();
+        String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());
 
-        if (ss != ServiceState.STATE_IN_SERVICE) {
-            handleNotInService(ss, tracker);
-        } else {
-            String appName = getAppNameByIntent(sentIntent);
-            if (mUsageMonitor.check(appName, SINGLE_PART_SMS)) {
-                sendSms(tracker);
+        if (packageNames == null || packageNames.length == 0) {
+            // Refuse to send SMS if we can't get the calling package name.
+            Log.e(TAG, "Can't get calling app package name: refusing to send SMS");
+            if (sentIntent != null) {
+                try {
+                    sentIntent.send(RESULT_ERROR_GENERIC_FAILURE);
+                } catch (CanceledException ex) {
+                    Log.e(TAG, "failed to send error result");
+                }
+            }
+            return;
+        }
+
+        String appPackage = packageNames[0];
+
+        // Strip non-digits from destination phone number before checking for short codes
+        // and before displaying the number to the user if confirmation is required.
+        SmsTracker tracker = new SmsTracker(map, sentIntent, deliveryIntent, appPackage,
+                PhoneNumberUtils.extractNetworkPortion(destAddr));
+
+        // checkDestination() returns true if the destination is not a premium short code or the
+        // sending app is approved to send to short codes. Otherwise, a message is sent to our
+        // handler with the SmsTracker to request user confirmation before sending.
+        if (checkDestination(tracker)) {
+            // check for excessive outgoing SMS usage by this app
+            if (!mUsageMonitor.check(appPackage, SINGLE_PART_SMS)) {
+                sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
+                return;
+            }
+
+            int ss = mPhone.getServiceState().getState();
+
+            if (ss != ServiceState.STATE_IN_SERVICE) {
+                handleNotInService(ss, tracker.mSentIntent);
             } else {
-                sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
+                sendSms(tracker);
             }
         }
     }
 
     /**
-     * Post an alert while SMS needs user confirm.
+     * Check if destination is a potential premium short code and sender is not pre-approved to
+     * send to short codes.
      *
-     * An SmsTracker for the current message.
+     * @param tracker the tracker for the SMS to send
+     * @return true if the destination is approved; false if user confirmation event was sent
      */
-    protected void handleReachSentLimit(SmsTracker tracker) {
-        if (mSTrackers.size() >= MO_MSG_QUEUE_LIMIT) {
-            // Deny the sending when the queue limit is reached.
+    boolean checkDestination(SmsTracker tracker) {
+        if (mUsageMonitor.isApprovedShortCodeSender(tracker.mAppPackage)) {
+            return true;            // app is pre-approved to send to short codes
+        } else {
+            String countryIso = mTelephonyManager.getSimCountryIso();
+            if (countryIso == null || countryIso.length() != 2) {
+                Log.e(TAG, "Can't get SIM country code: trying network country code");
+                countryIso = mTelephonyManager.getNetworkCountryIso();
+            }
+
+            switch (mUsageMonitor.checkDestination(tracker.mDestAddress, countryIso)) {
+                case SmsUsageMonitor.CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE:
+                    sendMessage(obtainMessage(EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE,
+                            tracker));
+                    return false;   // wait for user confirmation before sending
+
+                case SmsUsageMonitor.CATEGORY_PREMIUM_SHORT_CODE:
+                    sendMessage(obtainMessage(EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE,
+                            tracker));
+                    return false;   // wait for user confirmation before sending
+
+                case SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE:
+                case SmsUsageMonitor.CATEGORY_FREE_SHORT_CODE:
+                case SmsUsageMonitor.CATEGORY_STANDARD_SHORT_CODE:
+                default:
+                    return true;    // destination is not a premium short code
+            }
+        }
+    }
+
+    /**
+     * Deny sending an SMS if the outgoing queue limit is reached. Used when the message
+     * must be confirmed by the user due to excessive usage or potential premium SMS detected.
+     * @param tracker the SmsTracker for the message to send
+     * @return true if the message was denied; false to continue with send confirmation
+     */
+    private boolean denyIfQueueLimitReached(SmsTracker tracker) {
+        if (mPendingTrackerCount >= MO_MSG_QUEUE_LIMIT) {
+            // Deny sending message when the queue limit is reached.
             try {
                 tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
             } catch (CanceledException ex) {
                 Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
             }
-            return;
+            return true;
+        }
+        mPendingTrackerCount++;
+        return false;
+    }
+
+    /**
+     * Returns the label for the specified app package name.
+     * @param appPackage the package name of the app requesting to send an SMS
+     * @return the label for the specified app, or the package name if getApplicationInfo() fails
+     */
+    private CharSequence getAppLabel(String appPackage) {
+        PackageManager pm = mContext.getPackageManager();
+        try {
+            ApplicationInfo appInfo = pm.getApplicationInfo(appPackage, 0);
+            return appInfo.loadLabel(pm);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "PackageManager Name Not Found for package " + appPackage);
+            return appPackage;  // fall back to package name if we can't get app label
+        }
+    }
+
+    /**
+     * Post an alert when SMS needs confirmation due to excessive usage.
+     * @param tracker an SmsTracker for the current message.
+     */
+    protected void handleReachSentLimit(SmsTracker tracker) {
+        if (denyIfQueueLimitReached(tracker)) {
+            return;     // queue limit reached; error was returned to caller
         }
 
+        CharSequence appLabel = getAppLabel(tracker.mAppPackage);
         Resources r = Resources.getSystem();
+        Spanned messageText = Html.fromHtml(r.getString(R.string.sms_control_message, appLabel));
 
-        String appName = getAppNameByIntent(tracker.mSentIntent);
+        ConfirmDialogListener listener = new ConfirmDialogListener(tracker);
 
         AlertDialog d = new AlertDialog.Builder(mContext)
-                .setTitle(r.getString(R.string.sms_control_title))
-                .setMessage(appName + " " + r.getString(R.string.sms_control_message))
-                .setPositiveButton(r.getString(R.string.sms_control_yes), mListener)
-                .setNegativeButton(r.getString(R.string.sms_control_no), mListener)
+                .setTitle(R.string.sms_control_title)
+                .setIcon(R.drawable.stat_sys_warning)
+                .setMessage(messageText)
+                .setPositiveButton(r.getString(R.string.sms_control_yes), listener)
+                .setNegativeButton(r.getString(R.string.sms_control_no), listener)
+                .setOnCancelListener(listener)
                 .create();
 
         d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
         d.show();
-
-        mSTrackers.add(tracker);
-        sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d),
-                DEFAULT_SMS_TIMEOUT);
     }
 
-    protected static String getAppNameByIntent(PendingIntent intent) {
+    /**
+     * Post an alert for user confirmation when sending to a potential short code.
+     * @param isPremium true if the destination is known to be a premium short code
+     * @param tracker the SmsTracker for the current message.
+     */
+    protected void handleConfirmShortCode(boolean isPremium, SmsTracker tracker) {
+        if (denyIfQueueLimitReached(tracker)) {
+            return;     // queue limit reached; error was returned to caller
+        }
+
+        int messageId;
+        int titleId;
+        if (isPremium) {
+            messageId = R.string.sms_premium_short_code_confirm_message;
+            titleId = R.string.sms_premium_short_code_confirm_title;
+        } else {
+            messageId = R.string.sms_short_code_confirm_message;
+            titleId = R.string.sms_short_code_confirm_title;
+        }
+
+        CharSequence appLabel = getAppLabel(tracker.mAppPackage);
         Resources r = Resources.getSystem();
-        return (intent != null) ? intent.getTargetPackage()
-            : r.getString(R.string.sms_control_default_app_name);
+        Spanned messageText = Html.fromHtml(r.getString(messageId, appLabel, tracker.mDestAddress));
+
+        ConfirmDialogListener listener = new ConfirmDialogListener(tracker);
+
+        AlertDialog d = new AlertDialog.Builder(mContext)
+                .setTitle(titleId)
+                .setIcon(R.drawable.stat_sys_warning)
+                .setMessage(messageText)
+                .setPositiveButton(r.getString(R.string.sms_short_code_confirm_allow), listener)
+                .setNegativeButton(r.getString(R.string.sms_short_code_confirm_deny), listener)
+// TODO: add third button for "Report malicious app" feature
+//                .setNeutralButton(r.getString(R.string.sms_short_code_confirm_report), listener)
+                .setOnCancelListener(listener)
+                .create();
+
+        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        d.show();
     }
 
     /**
@@ -982,7 +1112,7 @@
                 if (sentIntents != null && sentIntents.size() > i) {
                     sentIntent = sentIntents.get(i);
                 }
-                handleNotInService(ss, new SmsTracker(null, sentIntent, null));
+                handleNotInService(ss, sentIntent);
             }
             return;
         }
@@ -1032,12 +1162,17 @@
         public final PendingIntent mSentIntent;
         public final PendingIntent mDeliveryIntent;
 
+        public final String mAppPackage;
+        public final String mDestAddress;
+
         public SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
-                PendingIntent deliveryIntent) {
+                PendingIntent deliveryIntent, String appPackage, String destAddr) {
             mData = data;
             mSentIntent = sentIntent;
             mDeliveryIntent = deliveryIntent;
             mRetryCount = 0;
+            mAppPackage = appPackage;
+            mDestAddress = destAddr;
         }
 
         /**
@@ -1050,19 +1185,35 @@
         }
     }
 
-    private final DialogInterface.OnClickListener mListener =
-        new DialogInterface.OnClickListener() {
+    /**
+     * Dialog listener for SMS confirmation dialog.
+     */
+    private final class ConfirmDialogListener
+            implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
 
-            public void onClick(DialogInterface dialog, int which) {
-                if (which == DialogInterface.BUTTON_POSITIVE) {
-                    Log.d(TAG, "click YES to send out sms");
-                    sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS));
-                } else if (which == DialogInterface.BUTTON_NEGATIVE) {
-                    Log.d(TAG, "click NO to stop sending");
-                    sendMessage(obtainMessage(EVENT_STOP_SENDING));
-                }
+        private final SmsTracker mTracker;
+
+        ConfirmDialogListener(SmsTracker tracker) {
+            mTracker = tracker;
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            if (which == DialogInterface.BUTTON_POSITIVE) {
+                Log.d(TAG, "CONFIRM sending SMS");
+                sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS, mTracker));
+            } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+                Log.d(TAG, "DENY sending SMS");
+                sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
             }
-        };
+        }
+
+        @Override
+        public void onCancel(DialogInterface dialog) {
+            Log.d(TAG, "dialog dismissed: don't send SMS");
+            sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
+        }
+    }
 
     private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
         @Override
diff --git a/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java b/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java
index bd2ae8b..07a0a28 100644
--- a/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java
+++ b/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java
@@ -17,13 +17,23 @@
 package com.android.internal.telephony;
 
 import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.XmlResourceParser;
 import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
 import android.util.Log;
 
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.regex.Pattern;
 
 /**
  * Implement the per-application based SMS control, which limits the number of
@@ -34,13 +44,28 @@
  * dual-mode devices that require support for both 3GPP and 3GPP2 format messages.
  */
 public class SmsUsageMonitor {
-    private static final String TAG = "SmsStorageMonitor";
+    private static final String TAG = "SmsUsageMonitor";
 
     /** Default checking period for SMS sent without user permission. */
-    private static final int DEFAULT_SMS_CHECK_PERIOD = 3600000;
+    private static final int DEFAULT_SMS_CHECK_PERIOD = 1800000;    // 30 minutes
 
     /** Default number of SMS sent in checking period without user permission. */
-    private static final int DEFAULT_SMS_MAX_COUNT = 100;
+    private static final int DEFAULT_SMS_MAX_COUNT = 30;
+
+    /** Return value from {@link #checkDestination} for regular phone numbers. */
+    static final int CATEGORY_NOT_SHORT_CODE = 0;
+
+    /** Return value from {@link #checkDestination} for free (no cost) short codes. */
+    static final int CATEGORY_FREE_SHORT_CODE = 1;
+
+    /** Return value from {@link #checkDestination} for standard rate (non-premium) short codes. */
+    static final int CATEGORY_STANDARD_SHORT_CODE = 2;
+
+    /** Return value from {@link #checkDestination} for possible premium short codes. */
+    static final int CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3;
+
+    /** Return value from {@link #checkDestination} for premium short codes. */
+    static final int CATEGORY_PREMIUM_SHORT_CODE = 4;
 
     private final int mCheckPeriod;
     private final int mMaxAllowed;
@@ -48,10 +73,89 @@
             new HashMap<String, ArrayList<Long>>();
 
     /**
-     * Create SMS usage monitor.
-     * @param resolver the ContentResolver to use to load from secure settings
+     * Hash of package names that are allowed to send to short codes.
+     * TODO: persist this across reboots.
      */
-    public SmsUsageMonitor(ContentResolver resolver) {
+    private final HashSet<String> mApprovedShortCodeSenders = new HashSet<String>();
+
+    /** Context for retrieving regexes from XML resource. */
+    private final Context mContext;
+
+    /** Country code for the cached short code pattern matcher. */
+    private String mCurrentCountry;
+
+    /** Cached short code pattern matcher for {@link #mCurrentCountry}. */
+    private ShortCodePatternMatcher mCurrentPatternMatcher;
+
+    /** XML tag for root element. */
+    private static final String TAG_SHORTCODES = "shortcodes";
+
+    /** XML tag for short code patterns for a specific country. */
+    private static final String TAG_SHORTCODE = "shortcode";
+
+    /** XML attribute for the country code. */
+    private static final String ATTR_COUNTRY = "country";
+
+    /** XML attribute for the short code regex pattern. */
+    private static final String ATTR_PATTERN = "pattern";
+
+    /** XML attribute for the premium short code regex pattern. */
+    private static final String ATTR_PREMIUM = "premium";
+
+    /** XML attribute for the free short code regex pattern. */
+    private static final String ATTR_FREE = "free";
+
+    /** XML attribute for the standard rate short code regex pattern. */
+    private static final String ATTR_STANDARD = "standard";
+
+    /**
+     * SMS short code regex pattern matcher for a specific country.
+     */
+    private static final class ShortCodePatternMatcher {
+        private final Pattern mShortCodePattern;
+        private final Pattern mPremiumShortCodePattern;
+        private final Pattern mFreeShortCodePattern;
+        private final Pattern mStandardShortCodePattern;
+
+        ShortCodePatternMatcher(String shortCodeRegex, String premiumShortCodeRegex,
+                String freeShortCodeRegex, String standardShortCodeRegex) {
+            mShortCodePattern = (shortCodeRegex != null ? Pattern.compile(shortCodeRegex) : null);
+            mPremiumShortCodePattern = (premiumShortCodeRegex != null ?
+                    Pattern.compile(premiumShortCodeRegex) : null);
+            mFreeShortCodePattern = (freeShortCodeRegex != null ?
+                    Pattern.compile(freeShortCodeRegex) : null);
+            mStandardShortCodePattern = (standardShortCodeRegex != null ?
+                    Pattern.compile(standardShortCodeRegex) : null);
+        }
+
+        int getNumberCategory(String phoneNumber) {
+            if (mFreeShortCodePattern != null && mFreeShortCodePattern.matcher(phoneNumber)
+                    .matches()) {
+                return CATEGORY_FREE_SHORT_CODE;
+            }
+            if (mStandardShortCodePattern != null && mStandardShortCodePattern.matcher(phoneNumber)
+                    .matches()) {
+                return CATEGORY_STANDARD_SHORT_CODE;
+            }
+            if (mPremiumShortCodePattern != null && mPremiumShortCodePattern.matcher(phoneNumber)
+                    .matches()) {
+                return CATEGORY_PREMIUM_SHORT_CODE;
+            }
+            if (mShortCodePattern != null && mShortCodePattern.matcher(phoneNumber).matches()) {
+                return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
+            }
+            return CATEGORY_NOT_SHORT_CODE;
+        }
+    }
+
+    /**
+     * Create SMS usage monitor.
+     * @param context the context to use to load resources and get TelephonyManager service
+     */
+    public SmsUsageMonitor(Context context) {
+        mContext = context;
+        ContentResolver resolver = context.getContentResolver();
+
         mMaxAllowed = Settings.Secure.getInt(resolver,
                 Settings.Secure.SMS_OUTGOING_CHECK_MAX_COUNT,
                 DEFAULT_SMS_MAX_COUNT);
@@ -59,6 +163,50 @@
         mCheckPeriod = Settings.Secure.getInt(resolver,
                 Settings.Secure.SMS_OUTGOING_CHECK_INTERVAL_MS,
                 DEFAULT_SMS_CHECK_PERIOD);
+
+        // system MMS app is always allowed to send to short codes
+        mApprovedShortCodeSenders.add("com.android.mms");
+    }
+
+    /**
+     * Return a pattern matcher object for the specified country.
+     * @param country the country to search for
+     * @return a {@link ShortCodePatternMatcher} for the specified country, or null if not found
+     */
+    private ShortCodePatternMatcher getPatternMatcher(String country) {
+        int id = com.android.internal.R.xml.sms_short_codes;
+        XmlResourceParser parser = mContext.getResources().getXml(id);
+
+        try {
+            XmlUtils.beginDocument(parser, TAG_SHORTCODES);
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+
+                String element = parser.getName();
+                if (element == null) break;
+
+                if (element.equals(TAG_SHORTCODE)) {
+                    String currentCountry = parser.getAttributeValue(null, ATTR_COUNTRY);
+                    if (country.equals(currentCountry)) {
+                        String pattern = parser.getAttributeValue(null, ATTR_PATTERN);
+                        String premium = parser.getAttributeValue(null, ATTR_PREMIUM);
+                        String free = parser.getAttributeValue(null, ATTR_FREE);
+                        String standard = parser.getAttributeValue(null, ATTR_STANDARD);
+                        return new ShortCodePatternMatcher(pattern, premium, free, standard);
+                    }
+                } else {
+                    Log.e(TAG, "Error: skipping unknown XML tag " + element);
+                }
+            }
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "XML parser exception reading short code pattern resource", e);
+        } catch (IOException e) {
+            Log.e(TAG, "I/O exception reading short code pattern resource", e);
+        } finally {
+            parser.close();
+        }
+        return null;    // country not found
     }
 
     /** Clear the SMS application list for disposal. */
@@ -67,9 +215,11 @@
     }
 
     /**
-     * Check to see if an application is allowed to send new SMS messages.
+     * Check to see if an application is allowed to send new SMS messages, and confirm with
+     * user if the send limit was reached or if a non-system app is potentially sending to a
+     * premium SMS short code or number.
      *
-     * @param appName the application sending sms
+     * @param appName the package name of the app requesting to send an SMS
      * @param smsWaiting the number of new messages desired to send
      * @return true if application is allowed to send the requested number
      *  of new sms messages
@@ -89,6 +239,69 @@
     }
 
     /**
+     * Return whether the app is approved to send to any short code.
+     * @param appName the package name of the app requesting to send an SMS
+     * @return true if the app is approved; false if we need to confirm short code destinations
+     */
+    public boolean isApprovedShortCodeSender(String appName) {
+        return mApprovedShortCodeSenders.contains(appName);
+    }
+
+    /**
+     * Add app package name to the list of approved short code senders.
+     * @param appName the package name of the app to add
+     */
+    public void addApprovedShortCodeSender(String appName) {
+        Log.d(TAG, "Adding " + appName + " to list of approved short code senders.");
+        mApprovedShortCodeSenders.add(appName);
+    }
+
+    /**
+     * Check if the destination is a possible premium short code.
+     * NOTE: the caller is expected to strip non-digits from the destination number with
+     * {@link PhoneNumberUtils#extractNetworkPortion} before calling this method.
+     * This happens in {@link SMSDispatcher#sendRawPdu} so that we use the same phone number
+     * for testing and in the user confirmation dialog if the user needs to confirm the number.
+     * This makes it difficult for malware to fool the user or the short code pattern matcher
+     * by using non-ASCII characters to make the number appear to be different from the real
+     * destination phone number.
+     *
+     * @param destAddress the destination address to test for possible short code
+     * @return {@link #CATEGORY_NOT_SHORT_CODE}, {@link #CATEGORY_FREE_SHORT_CODE},
+     *  {@link #CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE}, or {@link #CATEGORY_PREMIUM_SHORT_CODE}.
+     */
+    public int checkDestination(String destAddress, String countryIso) {
+        // always allow emergency numbers
+        if (PhoneNumberUtils.isEmergencyNumber(destAddress, countryIso)) {
+            return CATEGORY_NOT_SHORT_CODE;
+        }
+
+        ShortCodePatternMatcher patternMatcher = null;
+
+        if (countryIso != null) {
+            if (countryIso.equals(mCurrentCountry)) {
+                patternMatcher = mCurrentPatternMatcher;
+            } else {
+                patternMatcher = getPatternMatcher(countryIso);
+                mCurrentCountry = countryIso;
+                mCurrentPatternMatcher = patternMatcher;    // may be null if not found
+            }
+        }
+
+        if (patternMatcher != null) {
+            return patternMatcher.getNumberCategory(destAddress);
+        } else {
+            // Generic rule: numbers of 5 digits or less are considered potential short codes
+            Log.e(TAG, "No patterns for \"" + countryIso + "\": using generic short code rule");
+            if (destAddress.length() <= 5) {
+                return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
+            } else {
+                return CATEGORY_NOT_SHORT_CODE;
+            }
+        }
+    }
+
+    /**
      * Remove keys containing only old timestamps. This can happen if an SMS app is used
      * to send messages and then uninstalled.
      */
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
index 98a106a0..ff7a0810 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
@@ -28,6 +28,7 @@
 import android.telephony.cdma.CdmaCellLocation;
 import android.os.AsyncResult;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.provider.Telephony.Intents;
 
 import android.text.TextUtils;
@@ -370,14 +371,23 @@
             phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
                     ss.getOperatorAlphaLong());
 
+            String prevOperatorNumeric =
+                    SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
             operatorNumeric = ss.getOperatorNumeric();
             phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
 
             if (operatorNumeric == null) {
+                if (DBG) {
+                    log("pollStateDone: operatorNumeric=" + operatorNumeric +
+                            " prevOperatorNumeric=" + prevOperatorNumeric +
+                            " mNeedFixZone=" + mNeedFixZone +
+                            " clear PROPERTY_OPERATOR_ISO_COUNTRY");
+                }
                 phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
                 mGotCountryCode = false;
             } else {
                 String isoCountryCode = "";
+                String mcc = operatorNumeric.substring(0, 3);
                 try {
                     isoCountryCode = MccTable.countryCodeForMcc(Integer.parseInt(operatorNumeric
                             .substring(0, 3)));
@@ -386,11 +396,20 @@
                 } catch (StringIndexOutOfBoundsException ex) {
                     loge("countryCodeForMcc error" + ex);
                 }
+                if (DBG) {
+                    log("pollStateDone: operatorNumeric=" + operatorNumeric +
+                            " prevOperatorNumeric=" + prevOperatorNumeric +
+                            " mNeedFixZone=" + mNeedFixZone +
+                            " mcc=" + mcc + " iso-cc=" + isoCountryCode);
+                }
 
                 phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY,
                         isoCountryCode);
                 mGotCountryCode = true;
-                if (mNeedFixZone) {
+
+                // Fix the time zone If the operator changed or we need to fix it because
+                // when the NITZ time came in we didn't know the country code.
+                if ( ! operatorNumeric.equals(prevOperatorNumeric) || mNeedFixZone) {
                     fixTimeZone(isoCountryCode);
                 }
             }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index bfa23e7..e6b45f6 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -283,7 +283,7 @@
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
                 scAddr, destAddr, destPort, data, (deliveryIntent != null));
-        sendSubmitPdu(pdu, sentIntent, deliveryIntent);
+        sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
     }
 
     /** {@inheritDoc} */
@@ -292,7 +292,7 @@
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
                 scAddr, destAddr, text, (deliveryIntent != null), null);
-        sendSubmitPdu(pdu, sentIntent, deliveryIntent);
+        sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
     }
 
     /** {@inheritDoc} */
@@ -324,11 +324,11 @@
         SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
                 uData, (deliveryIntent != null) && lastPart);
 
-        sendSubmitPdu(submitPdu, sentIntent, deliveryIntent);
+        sendSubmitPdu(submitPdu, sentIntent, deliveryIntent, destinationAddress);
     }
 
     protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
-            PendingIntent sentIntent, PendingIntent deliveryIntent) {
+            PendingIntent sentIntent, PendingIntent deliveryIntent, String destAddr) {
         if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
             if (sentIntent != null) {
                 try {
@@ -340,7 +340,7 @@
             }
             return;
         }
-        sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
+        sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, destAddr);
     }
 
     /** {@inheritDoc} */
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index 9f27696..b694e0a 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -866,6 +866,12 @@
         // If the offset is (0, false) and the time zone property
         // is set, use the time zone property rather than GMT.
         String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
+        if (DBG) {
+            log("fixTimeZone zoneName='" + zoneName +
+                "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
+                " iso-cc='" + isoCountryCode +
+                "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode));
+        }
         if ((mZoneOffset == 0) && (mZoneDst == false) && (zoneName != null)
                 && (zoneName.length() > 0)
                 && (Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) < 0)) {
@@ -880,19 +886,25 @@
                 // Adjust the saved NITZ time to account for tzOffset.
                 mSavedTime = mSavedTime - tzOffset;
             }
+            if (DBG) log("fixTimeZone: using default TimeZone");
         } else if (isoCountryCode.equals("")) {
             // Country code not found. This is likely a test network.
             // Get a TimeZone based only on the NITZ parameters (best guess).
             zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
+            if (DBG) log("fixTimeZone: using NITZ TimeZone");
         } else {
             zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, isoCountryCode);
+            if (DBG) log("fixTimeZone: using getTimeZone(off, dst, time, iso)");
         }
 
         mNeedFixZone = false;
 
         if (zone != null) {
+            log("fixTimeZone: zone != null zone.getID=" + zone.getID());
             if (getAutoTimeZone()) {
                 setAndBroadcastNetworkSetTimeZone(zone.getID());
+            } else {
+                log("fixTimeZone: zone == null");
             }
             saveNitzTimeZone(zone.getID());
         }
@@ -985,14 +997,23 @@
             phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
                     ss.getOperatorAlphaLong());
 
+            String prevOperatorNumeric =
+                    SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
             operatorNumeric = ss.getOperatorNumeric();
             phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
 
             if (operatorNumeric == null) {
+                if (DBG) {
+                    log("pollStateDone: operatorNumeric=" + operatorNumeric +
+                            " prevOperatorNumeric=" + prevOperatorNumeric +
+                            " mNeedFixZone=" + mNeedFixZone +
+                            " clear PROPERTY_OPERATOR_ISO_COUNTRY");
+                }
                 phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
                 mGotCountryCode = false;
             } else {
                 String isoCountryCode = "";
+                String mcc = operatorNumeric.substring(0, 3);
                 try{
                     isoCountryCode = MccTable.countryCodeForMcc(Integer.parseInt(
                             operatorNumeric.substring(0,3)));
@@ -1001,11 +1022,20 @@
                 } catch ( StringIndexOutOfBoundsException ex) {
                     loge("pollStateDone: countryCodeForMcc error" + ex);
                 }
+                if (DBG) {
+                    log("pollStateDone: operatorNumeric=" + operatorNumeric +
+                            " prevOperatorNumeric=" + prevOperatorNumeric +
+                            " mNeedFixZone=" + mNeedFixZone +
+                            " mcc=" + mcc + " iso-cc=" + isoCountryCode);
+                }
 
                 phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY,
                         isoCountryCode);
                 mGotCountryCode = true;
-                if (mNeedFixZone) {
+
+                // Fix the time zone If the operator changed or we need to fix it because
+                // when the NITZ time came in we didn't know the country code.
+                if ( ! operatorNumeric.equals(prevOperatorNumeric) || mNeedFixZone) {
                     fixTimeZone(isoCountryCode);
                 }
             }
@@ -1316,7 +1346,6 @@
             String iso = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY);
 
             if (zone == null) {
-
                 if (mGotCountryCode) {
                     if (iso != null && iso.length() > 0) {
                         zone = TimeUtils.getTimeZone(tzOffset, dst != 0,
@@ -1332,16 +1361,21 @@
                 }
             }
 
-            if (zone == null) {
-                // We got the time before the country, so we don't know
-                // how to identify the DST rules yet.  Save the information
-                // and hope to fix it up later.
+            if ((zone == null) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){
+                // We got the time before the country or the zone has changed
+                // so we don't know how to identify the DST rules yet.  Save
+                // the information and hope to fix it up later.
 
                 mNeedFixZone = true;
                 mZoneOffset  = tzOffset;
                 mZoneDst     = dst != 0;
                 mZoneTime    = c.getTimeInMillis();
             }
+            if (DBG) {
+                log("NITZ: tzOffset=" + tzOffset + " dst=" + dst + " zone=" + zone.getID() +
+                        " iso=" + iso + " mGotCountryCode=" + mGotCountryCode +
+                        " mNeedFixZone=" + mNeedFixZone);
+            }
 
             if (zone != null) {
                 if (getAutoTimeZone()) {
@@ -1461,6 +1495,7 @@
      * @param zoneId timezone set by carrier
      */
     private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
+        if (DBG) log("setAndBroadcastNetworkSetTimeZone: setTimeZone=" + zoneId);
         AlarmManager alarm =
             (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
         alarm.setTimeZone(zoneId);
@@ -1477,6 +1512,7 @@
      * @param time time set by network
      */
     private void setAndBroadcastNetworkSetTime(long time) {
+        if (DBG) log("setAndBroadcastNetworkSetTime: time=" + time + "ms");
         SystemClock.setCurrentTimeMillis(time);
         Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 931c662..bfa2bb1 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -241,7 +241,8 @@
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
                 scAddr, destAddr, destPort, data, (deliveryIntent != null));
         if (pdu != null) {
-            sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
+            sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
+                    destAddr);
         } else {
             Log.e(TAG, "GsmSMSDispatcher.sendData(): getSubmitPdu() returned null");
         }
@@ -254,7 +255,8 @@
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
                 scAddr, destAddr, text, (deliveryIntent != null));
         if (pdu != null) {
-            sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
+            sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
+                    destAddr);
         } else {
             Log.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null");
         }
@@ -276,7 +278,8 @@
                 message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
                 encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
         if (pdu != null) {
-            sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
+            sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
+                    destinationAddress);
         } else {
             Log.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
         }
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 662f1f6..c0acf5b 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -852,12 +852,16 @@
             phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
                 ss.getOperatorAlphaLong());
 
+            String prevOperatorNumeric =
+                    SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
             operatorNumeric = ss.getOperatorNumeric();
             phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
 
             if (operatorNumeric == null) {
                 if (DBG) {
-                    log("pollStateDone: operatorNumeric is null:" +
+                    log("pollStateDone: operatorNumeric=" + operatorNumeric +
+                            " prevOperatorNumeric=" + prevOperatorNumeric +
+                            " mNeedFixZone=" + mNeedFixZone +
                             " clear PROPERTY_OPERATOR_ISO_COUNTRY");
                 }
                 phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
@@ -875,7 +879,9 @@
                 }
                 if (DBG) {
                     log("pollStateDone: operatorNumeric=" + operatorNumeric +
-                            " mcc=" + mcc + " iso=" + iso);
+                            " prevOperatorNumeric=" + prevOperatorNumeric +
+                            " mNeedFixZone=" + mNeedFixZone +
+                            " mcc=" + mcc + " iso-cc=" + iso);
                 }
 
                 phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, iso);
@@ -895,7 +901,7 @@
                     if ((uniqueZones.size() == 1) || testOneUniqueOffsetPath) {
                         zone = uniqueZones.get(0);
                         if (DBG) {
-                           log("pollStateDone: no nitz but one TZ for iso=" + iso +
+                           log("pollStateDone: no nitz but one TZ for iso-cc=" + iso +
                                    " with zone.getID=" + zone.getID() +
                                    " testOneUniqueOffsetPath=" + testOneUniqueOffsetPath);
                         }
@@ -903,22 +909,24 @@
                     } else {
                         if (DBG) {
                             log("pollStateDone: there are " + uniqueZones.size() +
-                                " unique offsets for iso='" + iso +
+                                " unique offsets for iso-cc='" + iso +
                                 " testOneUniqueOffsetPath=" + testOneUniqueOffsetPath +
                                 "', do nothing");
                         }
                     }
                 }
 
-                if (mNeedFixZone) {
+                // Fix the time zone If the operator changed or we need to fix it because
+                // when the NITZ time came in we didn't know the country code.
+                if ( ! operatorNumeric.equals(prevOperatorNumeric) || mNeedFixZone) {
                     // If the offset is (0, false) and the timezone property
                     // is set, use the timezone property rather than
                     // GMT.
                     String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
                     if (DBG) {
-                        log("pollStateDone: mNeedFixZone==true zoneName='" + zoneName +
+                        log("pollStateDone: fix time zone zoneName='" + zoneName +
                             "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
-                            " iso='" + iso +
+                            " iso-cc='" + iso +
                             "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, iso));
                     }
                     if ((mZoneOffset == 0) && (mZoneDst == false) &&
@@ -957,15 +965,6 @@
                     } else {
                         log("pollStateDone: zone == null");
                     }
-                } else {
-                    if (DBG) {
-                        String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
-                        zone = TimeZone.getDefault();
-                        log("pollStateDone: mNeedFixZone==false zoneName='" + zoneName +
-                                "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
-                                " iso='" + iso +
-                                "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, iso));
-                    }
                 }
             }
 
@@ -1442,10 +1441,10 @@
                 }
             }
 
-            if (zone == null) {
-                // We got the time before the country, so we don't know
-                // how to identify the DST rules yet.  Save the information
-                // and hope to fix it up later.
+            if ((zone == null) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){
+                // We got the time before the country or the zone has changed
+                // so we don't know how to identify the DST rules yet.  Save
+                // the information and hope to fix it up later.
 
                 mNeedFixZone = true;
                 mZoneOffset  = tzOffset;
@@ -1556,6 +1555,7 @@
      * @param zoneId timezone set by carrier
      */
     private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
+        if (DBG) log("setAndBroadcastNetworkSetTimeZone: setTimeZone=" + zoneId);
         AlarmManager alarm =
             (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
         alarm.setTimeZone(zoneId);
@@ -1576,6 +1576,7 @@
      * @param time time set by network
      */
     private void setAndBroadcastNetworkSetTime(long time) {
+        if (DBG) log("setAndBroadcastNetworkSetTime: time=" + time + "ms");
         SystemClock.setCurrentTimeMillis(time);
         Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java
new file mode 100644
index 0000000..3bb7c06
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java
@@ -0,0 +1,466 @@
+/*
+ * 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.internal.telephony;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_FREE_SHORT_CODE;
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE;
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_PREMIUM_SHORT_CODE;
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_STANDARD_SHORT_CODE;
+
+/**
+ * Test cases for SMS short code pattern matching in SmsUsageMonitor.
+ */
+public class SmsUsageMonitorShortCodeTest extends AndroidTestCase {
+
+    private static final class ShortCodeTest {
+        final String countryIso;
+        final String address;
+        final int category;
+
+        ShortCodeTest(String countryIso, String destAddress, int category) {
+            this.countryIso = countryIso;
+            this.address = destAddress;
+            this.category = category;
+        }
+    }
+
+    /**
+     * List of short code test cases.
+     */
+    private static final ShortCodeTest[] sShortCodeTests = new ShortCodeTest[] {
+            new ShortCodeTest("al", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("al", "4321", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("al", "54321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("al", "15191", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("al", "55500", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("al", "55600", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("al", "654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("am", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("am", "101", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("am", "102", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("am", "103", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("am", "222", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("am", "1111", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("am", "9999", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("am", "1121", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("am", "1141", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("am", "1161", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("am", "3024", CATEGORY_PREMIUM_SHORT_CODE),
+
+            new ShortCodeTest("at", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("at", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("at", "0901234", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("at", "0900666266", CATEGORY_PREMIUM_SHORT_CODE),
+
+            new ShortCodeTest("au", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("au", "180000", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("au", "190000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("au", "1900000", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("au", "19000000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("au", "19998882", CATEGORY_PREMIUM_SHORT_CODE),
+
+            new ShortCodeTest("az", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("az", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("az", "12345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("az", "87744", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("az", "3301", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("az", "3302", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("az", "9012", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("az", "9014", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("az", "9394", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("az", "87744", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("az", "93101", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("az", "123456", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("be", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("be", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("be", "567890", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("be", "8000", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("be", "6566", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("be", "7777", CATEGORY_PREMIUM_SHORT_CODE),
+
+            new ShortCodeTest("bg", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("bg", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("bg", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("bg", "12345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("bg", "1816", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("bg", "1915", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("bg", "1916", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("bg", "1935", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("bg", "18423", CATEGORY_PREMIUM_SHORT_CODE),
+
+            new ShortCodeTest("by", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("by", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("by", "3336", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("by", "5013", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("by", "5014", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("by", "7781", CATEGORY_PREMIUM_SHORT_CODE),
+
+            new ShortCodeTest("ca", "911", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ca", "+18005551234", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ca", "8005551234", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ca", "20000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ca", "200000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ca", "2000000", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ca", "60999", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ca", "88188", CATEGORY_PREMIUM_SHORT_CODE),
+
+            new ShortCodeTest("ch", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ch", "123", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ch", "234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ch", "3456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ch", "98765", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ch", "543", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ch", "83111", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ch", "234567", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ch", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("cn", "120", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("cn", "1062503000", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("cn", "1065123456", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("cn", "1066335588", CATEGORY_PREMIUM_SHORT_CODE),
+
+            new ShortCodeTest("cy", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("cy", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("cy", "4321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("cy", "54321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("cy", "654321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("cy", "7510", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("cy", "987654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("cz", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("cz", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("cz", "9090150", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("cz", "90901599", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("cz", "987654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("de", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("de", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("de", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "12345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "8888", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "11111", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "11886", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "22022", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "23300", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "3434", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "34567", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "41414", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "55655", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "66766", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "66777", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "77677", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "80888", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "1232286", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("de", "987654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("dk", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("dk", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("dk", "1259", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("dk", "16123", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("dk", "987654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("ee", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ee", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("ee", "123", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ee", "1259", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ee", "15330", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ee", "17999", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ee", "17010", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ee", "17013", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ee", "9034567", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ee", "34567890", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("es", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("es", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("es", "25165", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("es", "27333", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("es", "995399", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("es", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("fi", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("fi", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("fi", "12345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("fi", "123456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("fi", "17159", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("fi", "17163", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("fi", "0600123", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("fi", "070012345", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("fi", "987654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("fr", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("fr", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("fr", "34567", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("fr", "45678", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("fr", "81185", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("fr", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("gb", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("gb", "999", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("gb", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("gb", "4567", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("gb", "45678", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("gb", "56789", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("gb", "79067", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("gb", "80079", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("gb", "654321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("gb", "7654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("ge", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ge", "8765", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ge", "2345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ge", "8012", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ge", "8013", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ge", "8014", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ge", "8889", CATEGORY_PREMIUM_SHORT_CODE),
+
+            new ShortCodeTest("gr", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("gr", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("gr", "54321", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("gr", "19567", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("gr", "19678", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("gr", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("hu", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("hu", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("hu", "012", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("hu", "0123", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("hu", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("hu", "1784", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("hu", "2345", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("hu", "01234", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("hu", "012345678", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("hu", "0123456789", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("hu", "1234567890", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("hu", "0691227910", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("hu", "2345678901", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("hu", "01234567890", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("ie", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ie", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("ie", "50123", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("ie", "51234", CATEGORY_STANDARD_SHORT_CODE),
+            new ShortCodeTest("ie", "52345", CATEGORY_STANDARD_SHORT_CODE),
+            new ShortCodeTest("ie", "57890", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ie", "67890", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ie", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("il", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("il", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("il", "4422", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("il", "4545", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("il", "98765", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("it", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("it", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("it", "4567", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("it", "48000", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("it", "45678", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("it", "56789", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("it", "456789", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("kg", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("kg", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("kg", "4152", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("kg", "4157", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("kg", "4449", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("kg", "98765", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("kz", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("kz", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("kz", "9194", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("kz", "7790", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("kz", "98765", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("lt", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("lt", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("lt", "123", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lt", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lt", "1381", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lt", "1394", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lt", "1645", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lt", "12345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lt", "123456", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("lu", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("lu", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("lu", "1234", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("lu", "12345", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("lu", "64747", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lu", "678901", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("lv", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("lv", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("lv", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lv", "1819", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lv", "1863", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lv", "1874", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("lv", "98765", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("mx", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("mx", "2345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("mx", "7766", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("mx", "23456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("mx", "53035", CATEGORY_PREMIUM_SHORT_CODE),
+
+            new ShortCodeTest("my", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("my", "1234", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("my", "23456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("my", "32298", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("my", "33776", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("my", "345678", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("nl", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("nl", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("nl", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("nl", "4466", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("nl", "5040", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("nl", "23456", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("no", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("no", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("no", "2201", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("no", "2226", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("no", "2227", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("no", "23456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("no", "234567", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("nz", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("nz", "123", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("nz", "2345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("nz", "3903", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("nz", "8995", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("nz", "23456", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("pl", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("pl", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("pl", "7890", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("pl", "34567", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("pl", "7910", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("pl", "74240", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("pl", "79866", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("pl", "92525", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("pl", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("pt", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("pt", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("pt", "61000", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("pt", "62345", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("pt", "68304", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("pt", "69876", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("pt", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("ro", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ro", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("ro", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ro", "1263", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ro", "1288", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ro", "1314", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ro", "1380", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ro", "7890", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ro", "12345", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("ru", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ru", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ru", "1161", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ru", "2097", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ru", "3933", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ru", "7781", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ru", "98765", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("se", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("se", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("se", "1234", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("se", "72345", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("se", "72999", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("se", "123456", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("se", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("sg", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("sg", "1234", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("sg", "70000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("sg", "79999", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("sg", "73800", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("sg", "74688", CATEGORY_STANDARD_SHORT_CODE),
+            new ShortCodeTest("sg", "987654", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("si", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("si", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("si", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("si", "3838", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("si", "72999", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("sk", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("sk", "116117", CATEGORY_FREE_SHORT_CODE),
+            new ShortCodeTest("sk", "1234", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("sk", "6674", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("sk", "7604", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("sk", "72999", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("tj", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("tj", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("tj", "1161", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("tj", "1171", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("tj", "4161", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("tj", "4449", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("tj", "98765", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("ua", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("ua", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ua", "4448", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ua", "7094", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ua", "7540", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("ua", "98765", CATEGORY_NOT_SHORT_CODE),
+
+            new ShortCodeTest("us", "911", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("us", "+18005551234", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("us", "8005551234", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("us", "20000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("us", "200000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("us", "2000000", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("us", "20433", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("us", "21472", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("us", "23333", CATEGORY_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("us", "99807", CATEGORY_PREMIUM_SHORT_CODE),
+
+            // generic rules for other countries: 5 digits or less considered potential short code
+            new ShortCodeTest("zz", "2000000", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest("zz", "54321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("zz", "4321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("zz", "321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest("zz", "112", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest(null, "2000000", CATEGORY_NOT_SHORT_CODE),
+            new ShortCodeTest(null, "54321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest(null, "4321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest(null, "321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+            new ShortCodeTest(null, "112", CATEGORY_NOT_SHORT_CODE),
+    };
+
+    @SmallTest
+    public void testSmsUsageMonitor() {
+        SmsUsageMonitor monitor = new SmsUsageMonitor(getContext());
+        for (ShortCodeTest test : sShortCodeTests) {
+            assertEquals("country: " + test.countryIso + " number: " + test.address,
+                    test.category, monitor.checkDestination(test.address, test.countryIso));
+        }
+    }
+}
diff --git a/test-runner/src/android/test/AssertionFailedError.java b/test-runner/src/android/test/AssertionFailedError.java
index 7af5806..b3ac6d1 100644
--- a/test-runner/src/android/test/AssertionFailedError.java
+++ b/test-runner/src/android/test/AssertionFailedError.java
@@ -19,8 +19,7 @@
 /**
  * Thrown when an assertion failed.
  * 
- * Note:  Most users of this class should simply use junit.framework.AssertionFailedError,
- * which provides the same functionality.
+ * @deprecated use junit.framework.AssertionFailedError
  */
 public class AssertionFailedError extends Error {
     
diff --git a/test-runner/src/android/test/ComparisonFailure.java b/test-runner/src/android/test/ComparisonFailure.java
index e7e96986..3fa76f5 100644
--- a/test-runner/src/android/test/ComparisonFailure.java
+++ b/test-runner/src/android/test/ComparisonFailure.java
@@ -19,8 +19,7 @@
 /**
  * Thrown when an assert equals for Strings failed.
  * 
- * Note:  Most users of this class should simply use junit.framework.ComparisonFailure,
- * which provides the same functionality at a lighter weight.
+ * @deprecated use junit.framework.ComparisonFailure
  */
 public class ComparisonFailure extends AssertionFailedError {
     private junit.framework.ComparisonFailure mComparison;
diff --git a/test-runner/src/junit/runner/BaseTestRunner.java b/test-runner/src/junit/runner/BaseTestRunner.java
index e073ef7..8cfd7fa 100644
--- a/test-runner/src/junit/runner/BaseTestRunner.java
+++ b/test-runner/src/junit/runner/BaseTestRunner.java
@@ -1,10 +1,24 @@
 package junit.runner;
 
-import junit.framework.*;
-import java.lang.reflect.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.text.NumberFormat;
-import java.io.*;
-import java.util.*;
+import java.util.Properties;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestListener;
+import junit.framework.TestSuite;
 
 /**
  * Base class for all test runners.
@@ -19,8 +33,8 @@
     boolean fLoading= true;
 
     /*
-    * Implementation of TestListener
-    */
+     * Implementation of TestListener
+     */
     public synchronized void startTest(Test test) {
         testStarted(test.toString());
     }
@@ -32,9 +46,9 @@
     protected static Properties getPreferences() {
         if (fPreferences == null) {
             fPreferences= new Properties();
-             fPreferences.put("loading", "true");
-             fPreferences.put("filterstack", "true");
-              readPreferences();
+            fPreferences.put("loading", "true");
+            fPreferences.put("filterstack", "true");
+            readPreferences();
         }
         return fPreferences;
     }
@@ -48,8 +62,9 @@
         }
     }
 
+    // android-changed remove 'static' qualifier for API compatibility
     public void setPreference(String key, String value) {
-        getPreferences().setProperty(key, value);
+        getPreferences().put(key, value);
     }
 
     public synchronized void endTest(Test test) {
@@ -97,8 +112,8 @@
         Method suiteMethod= null;
         try {
             suiteMethod= testClass.getMethod(SUITE_METHODNAME, new Class[0]);
-         } catch(Exception e) {
-             // try to extract a test suite automatically
+        } catch(Exception e) {
+            // try to extract a test suite automatically
             clearStatus();
             return new TestSuite(testClass);
         }
@@ -108,7 +123,7 @@
         }
         Test test= null;
         try {
-            test= (Test)suiteMethod.invoke(null); // static method
+            test= (Test)suiteMethod.invoke(null, (Object[])new Class[0]); // static method
             if (test == null)
                 return test;
         }
@@ -163,7 +178,7 @@
         fLoading= enable;
     }
     /**
-     * Extract the class name from a String
+     * Extract the class name from a String in VA/Java style
      */
     public String extractClassName(String className) {
         if(className.startsWith("Default package for"))
@@ -186,11 +201,24 @@
      */
     protected abstract void runFailed(String message);
 
+    // BEGIN android-changed - add back getLoader() for API compatibility
+    /**
+     * Returns the loader to be used.
+     * 
+     * @deprecated not present in JUnit4.10
+     */
+    public TestSuiteLoader getLoader() {
+        if (useReloadingTestSuiteLoader())
+            return new ReloadingTestSuiteLoader();
+        return new StandardTestSuiteLoader();
+    }
+    // END android-changed
+
     /**
      * Returns the loaded Class for a suite name.
      */
-    protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
-        return getLoader().load(suiteClassName);
+    protected Class<?> loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
+        return Class.forName(suiteClassName);
     }
 
     /**
@@ -199,29 +227,20 @@
     protected void clearStatus() { // Belongs in the GUI TestRunner class
     }
 
-    /**
-     * Returns the loader to be used.
-     */
-    public TestSuiteLoader getLoader() {
-        if (useReloadingTestSuiteLoader())
-            return new ReloadingTestSuiteLoader();
-        return new StandardTestSuiteLoader();
-    }
-
     protected boolean useReloadingTestSuiteLoader() {
-        return getPreference("loading").equals("true") && !inVAJava() && fLoading;
+        return getPreference("loading").equals("true") && fLoading;
     }
 
     private static File getPreferencesFile() {
-         String home= System.getProperty("user.home");
-         return new File(home, "junit.properties");
-     }
+        String home= System.getProperty("user.home");
+        return new File(home, "junit.properties");
+    }
 
-     private static void readPreferences() {
-         InputStream is= null;
-         try {
-             is= new FileInputStream(getPreferencesFile());
-             setPreferences(new Properties(getPreferences()));
+    private static void readPreferences() {
+        InputStream is= null;
+        try {
+            is= new FileInputStream(getPreferencesFile());
+            setPreferences(new Properties(getPreferences()));
             getPreferences().load(is);
         } catch (IOException e) {
             try {
@@ -230,32 +249,22 @@
             } catch (IOException e1) {
             }
         }
-     }
+    }
 
-     public static String getPreference(String key) {
-         return getPreferences().getProperty(key);
-     }
+    public static String getPreference(String key) {
+        return getPreferences().getProperty(key);
+    }
 
-     public static int getPreference(String key, int dflt) {
-         String value= getPreference(key);
-         int intValue= dflt;
-         if (value == null)
-             return intValue;
-         try {
-             intValue= Integer.parseInt(value);
-          } catch (NumberFormatException ne) {
-         }
-         return intValue;
-     }
-
-     public static boolean inVAJava() {
+    public static int getPreference(String key, int dflt) {
+        String value= getPreference(key);
+        int intValue= dflt;
+        if (value == null)
+            return intValue;
         try {
-            Class.forName("com.ibm.uvm.tools.DebugSupport");
+            intValue= Integer.parseInt(value);
+        } catch (NumberFormatException ne) {
         }
-        catch (Exception e) {
-            return false;
-        }
-        return true;
+        return intValue;
     }
 
     /**
@@ -270,6 +279,13 @@
         return BaseTestRunner.getFilteredTrace(trace);
     }
 
+    // BEGIN android-changed - add back this method for API compatibility
+    /** @deprecated not present in JUnit4.10 */
+    public static boolean inVAJava() {
+        return false;
+    }
+    // END android-changed
+
     /**
      * Filters stack frames from internal JUnit classes
      */
@@ -303,14 +319,14 @@
 
     static boolean filterLine(String line) {
         String[] patterns= new String[] {
-            "junit.framework.TestCase",
-            "junit.framework.TestResult",
-            "junit.framework.TestSuite",
-            "junit.framework.Assert.", // don't filter AssertionFailure
-            "junit.swingui.TestRunner",
-            "junit.awtui.TestRunner",
-            "junit.textui.TestRunner",
-            "java.lang.reflect.Method.invoke("
+                "junit.framework.TestCase",
+                "junit.framework.TestResult",
+                "junit.framework.TestSuite",
+                "junit.framework.Assert.", // don't filter AssertionFailure
+                "junit.swingui.TestRunner",
+                "junit.awtui.TestRunner",
+                "junit.textui.TestRunner",
+                "java.lang.reflect.Method.invoke("
         };
         for (int i= 0; i < patterns.length; i++) {
             if (line.indexOf(patterns[i]) > 0)
@@ -319,8 +335,8 @@
         return false;
     }
 
-     static {
-         fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
-     }
+    static {
+        fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
+    }
 
 }
diff --git a/test-runner/src/junit/runner/ClassPathTestCollector.java b/test-runner/src/junit/runner/ClassPathTestCollector.java
index 8a3c702..f48ddee 100644
--- a/test-runner/src/junit/runner/ClassPathTestCollector.java
+++ b/test-runner/src/junit/runner/ClassPathTestCollector.java
@@ -13,69 +13,69 @@
  * {@hide} - Not needed for 1.0 SDK
  */
 public abstract class ClassPathTestCollector implements TestCollector {
-	
-	static final int SUFFIX_LENGTH= ".class".length();
-	
-	public ClassPathTestCollector() {
-	}
-	
-	public Enumeration collectTests() {
-		String classPath= System.getProperty("java.class.path");
-		Hashtable result = collectFilesInPath(classPath);
-		return result.elements();
-	}
 
-	public Hashtable collectFilesInPath(String classPath) {
-		Hashtable result= collectFilesInRoots(splitClassPath(classPath));
-		return result;
-	}
-	
-	Hashtable collectFilesInRoots(Vector roots) {
-		Hashtable result= new Hashtable(100);
-		Enumeration e= roots.elements();
-		while (e.hasMoreElements()) 
-			gatherFiles(new File((String)e.nextElement()), "", result);
-		return result;
-	}
+    static final int SUFFIX_LENGTH= ".class".length();
 
-	void gatherFiles(File classRoot, String classFileName, Hashtable result) {
-		File thisRoot= new File(classRoot, classFileName);
-		if (thisRoot.isFile()) {
-			if (isTestClass(classFileName)) {
-				String className= classNameFromFile(classFileName);
-				result.put(className, className);
-			}
-			return;
-		}		
-		String[] contents= thisRoot.list();
-		if (contents != null) { 
-			for (int i= 0; i < contents.length; i++) 
-				gatherFiles(classRoot, classFileName+File.separatorChar+contents[i], result);		
-		}
-	}
-	
-	Vector splitClassPath(String classPath) {
-		Vector result= new Vector();
-		String separator= System.getProperty("path.separator");
-		StringTokenizer tokenizer= new StringTokenizer(classPath, separator);
-		while (tokenizer.hasMoreTokens()) 
-			result.addElement(tokenizer.nextToken());
-		return result;
-	}
-	
-	protected boolean isTestClass(String classFileName) {
-		return 
-			classFileName.endsWith(".class") && 
-			classFileName.indexOf('$') < 0 &&
-			classFileName.indexOf("Test") > 0;
-	}
-	
-	protected String classNameFromFile(String classFileName) {
-		// convert /a/b.class to a.b
-		String s= classFileName.substring(0, classFileName.length()-SUFFIX_LENGTH);
-		String s2= s.replace(File.separatorChar, '.');
-		if (s2.startsWith("."))
-			return s2.substring(1);
-		return s2;
-	}	
+    public ClassPathTestCollector() {
+    }
+
+    public Enumeration collectTests() {
+        String classPath= System.getProperty("java.class.path");
+        Hashtable result = collectFilesInPath(classPath);
+        return result.elements();
+    }
+
+    public Hashtable collectFilesInPath(String classPath) {
+        Hashtable result= collectFilesInRoots(splitClassPath(classPath));
+        return result;
+    }
+
+    Hashtable collectFilesInRoots(Vector roots) {
+        Hashtable result= new Hashtable(100);
+        Enumeration e= roots.elements();
+        while (e.hasMoreElements())
+            gatherFiles(new File((String)e.nextElement()), "", result);
+        return result;
+    }
+
+    void gatherFiles(File classRoot, String classFileName, Hashtable result) {
+        File thisRoot= new File(classRoot, classFileName);
+        if (thisRoot.isFile()) {
+            if (isTestClass(classFileName)) {
+                String className= classNameFromFile(classFileName);
+                result.put(className, className);
+            }
+            return;
+        }
+        String[] contents= thisRoot.list();
+        if (contents != null) {
+            for (int i= 0; i < contents.length; i++)
+                gatherFiles(classRoot, classFileName+File.separatorChar+contents[i], result);
+        }
+    }
+
+    Vector splitClassPath(String classPath) {
+        Vector result= new Vector();
+        String separator= System.getProperty("path.separator");
+        StringTokenizer tokenizer= new StringTokenizer(classPath, separator);
+        while (tokenizer.hasMoreTokens())
+            result.addElement(tokenizer.nextToken());
+        return result;
+    }
+
+    protected boolean isTestClass(String classFileName) {
+        return
+                classFileName.endsWith(".class") &&
+                classFileName.indexOf('$') < 0 &&
+                classFileName.indexOf("Test") > 0;
+    }
+
+    protected String classNameFromFile(String classFileName) {
+        // convert /a/b.class to a.b
+        String s= classFileName.substring(0, classFileName.length()-SUFFIX_LENGTH);
+        String s2= s.replace(File.separatorChar, '.');
+        if (s2.startsWith("."))
+            return s2.substring(1);
+        return s2;
+    }
 }
diff --git a/test-runner/src/junit/runner/FailureDetailView.java b/test-runner/src/junit/runner/FailureDetailView.java
index 7108cec..1b8365a 100644
--- a/test-runner/src/junit/runner/FailureDetailView.java
+++ b/test-runner/src/junit/runner/FailureDetailView.java
@@ -1,7 +1,7 @@
 package junit.runner;
 
 // The following line was removed for compatibility with Android libraries.
-//import java.awt.Component; 
+//import java.awt.Component;
 
 import junit.framework.*;
 
@@ -17,12 +17,12 @@
     //   */
     //  public Component getComponent();
 
-	/**
-	 * Shows details of a TestFailure
-	 */
-	public void showFailure(TestFailure failure);
-	/**
-	 * Clears the view
-	 */
-	public void clear();
+    /**
+     * Shows details of a TestFailure
+     */
+    public void showFailure(TestFailure failure);
+    /**
+     * Clears the view
+     */
+    public void clear();
 }
diff --git a/test-runner/src/junit/runner/LoadingTestCollector.java b/test-runner/src/junit/runner/LoadingTestCollector.java
index b1760b1..489d9d6 100644
--- a/test-runner/src/junit/runner/LoadingTestCollector.java
+++ b/test-runner/src/junit/runner/LoadingTestCollector.java
@@ -12,59 +12,59 @@
  * {@hide} - Not needed for 1.0 SDK
  */
 public class LoadingTestCollector extends ClassPathTestCollector {
-	
-	TestCaseClassLoader fLoader;
-	
-	public LoadingTestCollector() {
-		fLoader= new TestCaseClassLoader();
-	}
-	
-	protected boolean isTestClass(String classFileName) {	
-		try {
-			if (classFileName.endsWith(".class")) {
-				Class testClass= classFromFile(classFileName);
-				return (testClass != null) && isTestClass(testClass);
-			}
-		} 
-		catch (ClassNotFoundException expected) {
-		}
-		catch (NoClassDefFoundError notFatal) {
-		} 
-		return false;
-	}
-	
-	Class classFromFile(String classFileName) throws ClassNotFoundException {
-		String className= classNameFromFile(classFileName);
-		if (!fLoader.isExcluded(className))
-			return fLoader.loadClass(className, false);
-		return null;
-	}
-	
-	boolean isTestClass(Class testClass) {
-		if (hasSuiteMethod(testClass))
-			return true;
-		if (Test.class.isAssignableFrom(testClass) &&
-			Modifier.isPublic(testClass.getModifiers()) &&
-			hasPublicConstructor(testClass)) 
-			return true;
-		return false;
-	}
-	
-	boolean hasSuiteMethod(Class testClass) {
-		try {
-			testClass.getMethod(BaseTestRunner.SUITE_METHODNAME, new Class[0]);
-	 	} catch(Exception e) {
-	 		return false;
-		}
-		return true;
-	}
-	
-	boolean hasPublicConstructor(Class testClass) {
-		try {
-			TestSuite.getTestConstructor(testClass);
-		} catch(NoSuchMethodException e) {
-			return false;
-		}
-		return true;
-	}
+
+    TestCaseClassLoader fLoader;
+
+    public LoadingTestCollector() {
+        fLoader= new TestCaseClassLoader();
+    }
+
+    protected boolean isTestClass(String classFileName) {
+        try {
+            if (classFileName.endsWith(".class")) {
+                Class testClass= classFromFile(classFileName);
+                return (testClass != null) && isTestClass(testClass);
+            }
+        }
+        catch (ClassNotFoundException expected) {
+        }
+        catch (NoClassDefFoundError notFatal) {
+        }
+        return false;
+    }
+
+    Class classFromFile(String classFileName) throws ClassNotFoundException {
+        String className= classNameFromFile(classFileName);
+        if (!fLoader.isExcluded(className))
+            return fLoader.loadClass(className, false);
+        return null;
+    }
+
+    boolean isTestClass(Class testClass) {
+        if (hasSuiteMethod(testClass))
+            return true;
+        if (Test.class.isAssignableFrom(testClass) &&
+                Modifier.isPublic(testClass.getModifiers()) &&
+                hasPublicConstructor(testClass))
+            return true;
+        return false;
+    }
+
+    boolean hasSuiteMethod(Class testClass) {
+        try {
+            testClass.getMethod(BaseTestRunner.SUITE_METHODNAME, new Class[0]);
+        } catch(Exception e) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean hasPublicConstructor(Class testClass) {
+        try {
+            TestSuite.getTestConstructor(testClass);
+        } catch(NoSuchMethodException e) {
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java b/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
index a6d84fe..c4d80d0 100644
--- a/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
+++ b/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
@@ -5,16 +5,16 @@
  * {@hide} - Not needed for 1.0 SDK
  */
 public class ReloadingTestSuiteLoader implements TestSuiteLoader {
-	
-	public Class load(String suiteClassName) throws ClassNotFoundException {
-		return createLoader().loadClass(suiteClassName, true);
-	}
-	
-	public Class reload(Class aClass) throws ClassNotFoundException {
-		return createLoader().loadClass(aClass.getName(), true);
-	}
-	
-	protected TestCaseClassLoader createLoader() {
-		return new TestCaseClassLoader();
-	}
+
+    public Class load(String suiteClassName) throws ClassNotFoundException {
+        return createLoader().loadClass(suiteClassName, true);
+    }
+
+    public Class reload(Class aClass) throws ClassNotFoundException {
+        return createLoader().loadClass(aClass.getName(), true);
+    }
+
+    protected TestCaseClassLoader createLoader() {
+        return new TestCaseClassLoader();
+    }
 }
diff --git a/test-runner/src/junit/runner/SimpleTestCollector.java b/test-runner/src/junit/runner/SimpleTestCollector.java
index 543168f8..6cb0e19 100644
--- a/test-runner/src/junit/runner/SimpleTestCollector.java
+++ b/test-runner/src/junit/runner/SimpleTestCollector.java
@@ -8,14 +8,14 @@
  * {@hide} - Not needed for 1.0 SDK
  */
 public class SimpleTestCollector extends ClassPathTestCollector {
-	
-	public SimpleTestCollector() {
-	}
-	
-	protected boolean isTestClass(String classFileName) {
-		return 
-			classFileName.endsWith(".class") && 
-			classFileName.indexOf('$') < 0 &&
-			classFileName.indexOf("Test") > 0;
-	}
+
+    public SimpleTestCollector() {
+    }
+
+    protected boolean isTestClass(String classFileName) {
+        return
+                classFileName.endsWith(".class") &&
+                classFileName.indexOf('$') < 0 &&
+                classFileName.indexOf("Test") > 0;
+    }
 }
diff --git a/test-runner/src/junit/runner/Sorter.java b/test-runner/src/junit/runner/Sorter.java
index 66f551e..7731f66 100644
--- a/test-runner/src/junit/runner/Sorter.java
+++ b/test-runner/src/junit/runner/Sorter.java
@@ -11,29 +11,29 @@
  * {@hide} - Not needed for 1.0 SDK
  */
 public class Sorter {
-	public static interface Swapper {
-		public void swap(Vector values, int left, int right);
-	}
-		
-	public static void sortStrings(Vector values , int left, int right, Swapper swapper) { 
-		int oleft= left;
-		int oright= right;
-		String mid= (String)values.elementAt((left + right) / 2); 
-		do { 
-			while (((String)(values.elementAt(left))).compareTo(mid) < 0)  
-				left++; 
-			while (mid.compareTo((String)(values.elementAt(right))) < 0)  
-				right--; 
-			if (left <= right) {
-				swapper.swap(values, left, right); 
-				left++; 
-				right--; 
-			} 
-		} while (left <= right);
-		
-		if (oleft < right) 
-			sortStrings(values, oleft, right, swapper); 
-		if (left < oright) 
-			 sortStrings(values, left, oright, swapper); 
-	}
+    public static interface Swapper {
+        public void swap(Vector values, int left, int right);
+    }
+
+    public static void sortStrings(Vector values , int left, int right, Swapper swapper) {
+        int oleft= left;
+        int oright= right;
+        String mid= (String)values.elementAt((left + right) / 2);
+        do {
+            while (((String)(values.elementAt(left))).compareTo(mid) < 0)
+                left++;
+            while (mid.compareTo((String)(values.elementAt(right))) < 0)
+                right--;
+            if (left <= right) {
+                swapper.swap(values, left, right);
+                left++;
+                right--;
+            }
+        } while (left <= right);
+
+        if (oleft < right)
+            sortStrings(values, oleft, right, swapper);
+        if (left < oright)
+            sortStrings(values, left, oright, swapper);
+    }
 }
diff --git a/test-runner/src/junit/runner/StandardTestSuiteLoader.java b/test-runner/src/junit/runner/StandardTestSuiteLoader.java
index bce7dec..381e684 100644
--- a/test-runner/src/junit/runner/StandardTestSuiteLoader.java
+++ b/test-runner/src/junit/runner/StandardTestSuiteLoader.java
@@ -5,16 +5,16 @@
  * {@hide} - Not needed for 1.0 SDK
  */
 public class StandardTestSuiteLoader implements TestSuiteLoader {
-	/**
-	 * Uses the system class loader to load the test class
-	 */
-	public Class load(String suiteClassName) throws ClassNotFoundException {
-		return Class.forName(suiteClassName);
-	}
-	/**
-	 * Uses the system class loader to load the test class
-	 */
-	public Class reload(Class aClass) throws ClassNotFoundException {
-		return aClass;
-	}
+    /**
+     * Uses the system class loader to load the test class
+     */
+    public Class load(String suiteClassName) throws ClassNotFoundException {
+        return Class.forName(suiteClassName);
+    }
+    /**
+     * Uses the system class loader to load the test class
+     */
+    public Class reload(Class aClass) throws ClassNotFoundException {
+        return aClass;
+    }
 }
diff --git a/test-runner/src/junit/runner/TestCaseClassLoader.java b/test-runner/src/junit/runner/TestCaseClassLoader.java
index 3a510c6..09eec7f 100644
--- a/test-runner/src/junit/runner/TestCaseClassLoader.java
+++ b/test-runner/src/junit/runner/TestCaseClassLoader.java
@@ -14,7 +14,7 @@
  * loader. They will be shared across test runs.
  * <p>
  * The list of excluded package paths is specified in
- * a properties file "excluded.properties" that is located in 
+ * a properties file "excluded.properties" that is located in
  * the same place as the TestCaseClassLoader class.
  * <p>
  * <b>Known limitation:</b> the TestCaseClassLoader cannot load classes
@@ -22,204 +22,204 @@
  * {@hide} - Not needed for 1.0 SDK
  */
 public class TestCaseClassLoader extends ClassLoader {
-	/** scanned class path */
-	private Vector fPathItems;
-	/** default excluded paths */
-	private String[] defaultExclusions= {
-		"junit.framework.", 
-		"junit.extensions.", 
-		"junit.runner."
-	};
-	/** name of excluded properties file */
-	static final String EXCLUDED_FILE= "excluded.properties";
-	/** excluded paths */
-	private Vector fExcluded;
-	 
-	/**
-	 * Constructs a TestCaseLoader. It scans the class path
-	 * and the excluded package paths
-	 */
-	public TestCaseClassLoader() {
-		this(System.getProperty("java.class.path"));
-	}
-	
-	/**
-	 * Constructs a TestCaseLoader. It scans the class path
-	 * and the excluded package paths
-	 */
-	public TestCaseClassLoader(String classPath) {
-		scanPath(classPath);
-		readExcludedPackages();
-	}
+    /** scanned class path */
+    private Vector fPathItems;
+    /** default excluded paths */
+    private String[] defaultExclusions= {
+            "junit.framework.",
+            "junit.extensions.",
+            "junit.runner."
+    };
+    /** name of excluded properties file */
+    static final String EXCLUDED_FILE= "excluded.properties";
+    /** excluded paths */
+    private Vector fExcluded;
 
-	private void scanPath(String classPath) {
-		String separator= System.getProperty("path.separator");
-		fPathItems= new Vector(10);
-		StringTokenizer st= new StringTokenizer(classPath, separator);
-		while (st.hasMoreTokens()) {
-			fPathItems.addElement(st.nextToken());
-		}
-	}
-	
-	public URL getResource(String name) {
-		return ClassLoader.getSystemResource(name);
-	}
-	
-	public InputStream getResourceAsStream(String name) {
-		return ClassLoader.getSystemResourceAsStream(name);
-	} 
-	
-	public boolean isExcluded(String name) {
-		for (int i= 0; i < fExcluded.size(); i++) {
-			if (name.startsWith((String) fExcluded.elementAt(i))) {
-				return true;
-			}
-		}
-		return false;	
-	}
-	
-	public synchronized Class loadClass(String name, boolean resolve)
-		throws ClassNotFoundException {
-			
-		Class c= findLoadedClass(name);
-		if (c != null)
-			return c;
-		//
-		// Delegate the loading of excluded classes to the
-		// standard class loader.
-		//
-		if (isExcluded(name)) {
-			try {
-				c= findSystemClass(name);
-				return c;
-			} catch (ClassNotFoundException e) {
-				// keep searching
-			}
-		}
-		if (c == null) {
-			byte[] data= lookupClassData(name);
-			if (data == null)
-				throw new ClassNotFoundException();
-			c= defineClass(name, data, 0, data.length);
-		}
-		if (resolve) 
-			resolveClass(c);
-		return c;
-	}
-	
-	private byte[] lookupClassData(String className) throws ClassNotFoundException {
-		byte[] data= null;
-		for (int i= 0; i < fPathItems.size(); i++) {
-			String path= (String) fPathItems.elementAt(i);
-			String fileName= className.replace('.', '/')+".class";
-			if (isJar(path)) {
-				data= loadJarData(path, fileName);
-			} else {
-				data= loadFileData(path, fileName);
-			}
-			if (data != null)
-				return data;
-		}
-		throw new ClassNotFoundException(className);
-	}
-		
-	boolean isJar(String pathEntry) {
-		return pathEntry.endsWith(".jar") ||
-		       pathEntry.endsWith(".apk") ||
-                       pathEntry.endsWith(".zip");
-	}
+    /**
+     * Constructs a TestCaseLoader. It scans the class path
+     * and the excluded package paths
+     */
+    public TestCaseClassLoader() {
+        this(System.getProperty("java.class.path"));
+    }
 
-	private byte[] loadFileData(String path, String fileName) {
-		File file= new File(path, fileName);
-		if (file.exists()) { 
-			return getClassData(file);
-		}
-		return null;
-	}
-	
-	private byte[] getClassData(File f) {
-		try {
-			FileInputStream stream= new FileInputStream(f);
-			ByteArrayOutputStream out= new ByteArrayOutputStream(1000);
-			byte[] b= new byte[1000];
-			int n;
-			while ((n= stream.read(b)) != -1) 
-				out.write(b, 0, n);
-			stream.close();
-			out.close();
-			return out.toByteArray();
+    /**
+     * Constructs a TestCaseLoader. It scans the class path
+     * and the excluded package paths
+     */
+    public TestCaseClassLoader(String classPath) {
+        scanPath(classPath);
+        readExcludedPackages();
+    }
 
-		} catch (IOException e) {
-		}
-		return null;
-	}
+    private void scanPath(String classPath) {
+        String separator= System.getProperty("path.separator");
+        fPathItems= new Vector(10);
+        StringTokenizer st= new StringTokenizer(classPath, separator);
+        while (st.hasMoreTokens()) {
+            fPathItems.addElement(st.nextToken());
+        }
+    }
 
-	private byte[] loadJarData(String path, String fileName) {
-		ZipFile zipFile= null;
-		InputStream stream= null;
-		File archive= new File(path);
-		if (!archive.exists())
-			return null;
-		try {
-			zipFile= new ZipFile(archive);
-		} catch(IOException io) {
-			return null;
-		}
-		ZipEntry entry= zipFile.getEntry(fileName);
-		if (entry == null)
-			return null;
-		int size= (int) entry.getSize();
-		try {
-			stream= zipFile.getInputStream(entry);
-			byte[] data= new byte[size];
-			int pos= 0;
-			while (pos < size) {
-				int n= stream.read(data, pos, data.length - pos);
-				pos += n;
-			}
-			zipFile.close();
-			return data;
-		} catch (IOException e) {
-		} finally {
-			try {
-				if (stream != null)
-					stream.close();
-			} catch (IOException e) {
-			}
-		}
-		return null;
-	}
-	
-	private void readExcludedPackages() {		
-		fExcluded= new Vector(10);
-		for (int i= 0; i < defaultExclusions.length; i++)
-			fExcluded.addElement(defaultExclusions[i]);
-			
-		InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE);
-		if (is == null) 
-			return;
-		Properties p= new Properties();
-		try {
-			p.load(is);
-		}
-		catch (IOException e) {
-			return;
-		} finally {
-			try {
-				is.close();
-			} catch (IOException e) {
-			}
-		}
-		for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) {
-			String key= (String)e.nextElement();
-			if (key.startsWith("excluded.")) {
-				String path= p.getProperty(key);
-				path= path.trim();
-				if (path.endsWith("*"))
-					path= path.substring(0, path.length()-1);
-				if (path.length() > 0) 
-					fExcluded.addElement(path);				
-			}
-		}
-	}
+    public URL getResource(String name) {
+        return ClassLoader.getSystemResource(name);
+    }
+
+    public InputStream getResourceAsStream(String name) {
+        return ClassLoader.getSystemResourceAsStream(name);
+    }
+
+    public boolean isExcluded(String name) {
+        for (int i= 0; i < fExcluded.size(); i++) {
+            if (name.startsWith((String) fExcluded.elementAt(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public synchronized Class loadClass(String name, boolean resolve)
+            throws ClassNotFoundException {
+
+        Class c= findLoadedClass(name);
+        if (c != null)
+            return c;
+        //
+        // Delegate the loading of excluded classes to the
+        // standard class loader.
+        //
+        if (isExcluded(name)) {
+            try {
+                c= findSystemClass(name);
+                return c;
+            } catch (ClassNotFoundException e) {
+                // keep searching
+            }
+        }
+        if (c == null) {
+            byte[] data= lookupClassData(name);
+            if (data == null)
+                throw new ClassNotFoundException();
+            c= defineClass(name, data, 0, data.length);
+        }
+        if (resolve)
+            resolveClass(c);
+        return c;
+    }
+
+    private byte[] lookupClassData(String className) throws ClassNotFoundException {
+        byte[] data= null;
+        for (int i= 0; i < fPathItems.size(); i++) {
+            String path= (String) fPathItems.elementAt(i);
+            String fileName= className.replace('.', '/')+".class";
+            if (isJar(path)) {
+                data= loadJarData(path, fileName);
+            } else {
+                data= loadFileData(path, fileName);
+            }
+            if (data != null)
+                return data;
+        }
+        throw new ClassNotFoundException(className);
+    }
+
+    boolean isJar(String pathEntry) {
+        return pathEntry.endsWith(".jar") ||
+                pathEntry.endsWith(".apk") ||
+                pathEntry.endsWith(".zip");
+    }
+
+    private byte[] loadFileData(String path, String fileName) {
+        File file= new File(path, fileName);
+        if (file.exists()) {
+            return getClassData(file);
+        }
+        return null;
+    }
+
+    private byte[] getClassData(File f) {
+        try {
+            FileInputStream stream= new FileInputStream(f);
+            ByteArrayOutputStream out= new ByteArrayOutputStream(1000);
+            byte[] b= new byte[1000];
+            int n;
+            while ((n= stream.read(b)) != -1)
+                out.write(b, 0, n);
+            stream.close();
+            out.close();
+            return out.toByteArray();
+
+        } catch (IOException e) {
+        }
+        return null;
+    }
+
+    private byte[] loadJarData(String path, String fileName) {
+        ZipFile zipFile= null;
+        InputStream stream= null;
+        File archive= new File(path);
+        if (!archive.exists())
+            return null;
+        try {
+            zipFile= new ZipFile(archive);
+        } catch(IOException io) {
+            return null;
+        }
+        ZipEntry entry= zipFile.getEntry(fileName);
+        if (entry == null)
+            return null;
+        int size= (int) entry.getSize();
+        try {
+            stream= zipFile.getInputStream(entry);
+            byte[] data= new byte[size];
+            int pos= 0;
+            while (pos < size) {
+                int n= stream.read(data, pos, data.length - pos);
+                pos += n;
+            }
+            zipFile.close();
+            return data;
+        } catch (IOException e) {
+        } finally {
+            try {
+                if (stream != null)
+                    stream.close();
+            } catch (IOException e) {
+            }
+        }
+        return null;
+    }
+
+    private void readExcludedPackages() {
+        fExcluded= new Vector(10);
+        for (int i= 0; i < defaultExclusions.length; i++)
+            fExcluded.addElement(defaultExclusions[i]);
+
+        InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE);
+        if (is == null)
+            return;
+        Properties p= new Properties();
+        try {
+            p.load(is);
+        }
+        catch (IOException e) {
+            return;
+        } finally {
+            try {
+                is.close();
+            } catch (IOException e) {
+            }
+        }
+        for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) {
+            String key= (String)e.nextElement();
+            if (key.startsWith("excluded.")) {
+                String path= p.getProperty(key);
+                path= path.trim();
+                if (path.endsWith("*"))
+                    path= path.substring(0, path.length()-1);
+                if (path.length() > 0)
+                    fExcluded.addElement(path);
+            }
+        }
+    }
 }
diff --git a/test-runner/src/junit/runner/TestCollector.java b/test-runner/src/junit/runner/TestCollector.java
index 208dccd..3ac9d9e 100644
--- a/test-runner/src/junit/runner/TestCollector.java
+++ b/test-runner/src/junit/runner/TestCollector.java
@@ -5,13 +5,13 @@
 
 /**
  * Collects Test class names to be presented
- * by the TestSelector. 
+ * by the TestSelector.
  * @see TestSelector
  * {@hide} - Not needed for 1.0 SDK
  */
 public interface TestCollector {
-	/**
-	 * Returns an enumeration of Strings with qualified class names
-	 */
-	public Enumeration collectTests();
+    /**
+     * Returns an enumeration of Strings with qualified class names
+     */
+    public Enumeration collectTests();
 }
diff --git a/test-runner/src/junit/runner/TestRunListener.java b/test-runner/src/junit/runner/TestRunListener.java
index 0e95819..0410f0c 100644
--- a/test-runner/src/junit/runner/TestRunListener.java
+++ b/test-runner/src/junit/runner/TestRunListener.java
@@ -6,15 +6,15 @@
  * making it suitable for remote test execution.
  * {@hide} - Not needed for 1.0 SDK
  */
- public interface TestRunListener {
-     /* test status constants*/
-     public static final int STATUS_ERROR= 1;
-     public static final int STATUS_FAILURE= 2;
+public interface TestRunListener {
+    /* test status constants*/
+    public static final int STATUS_ERROR= 1;
+    public static final int STATUS_FAILURE= 2;
 
-     public void testRunStarted(String testSuiteName, int testCount);
-     public void testRunEnded(long elapsedTime);
-     public void testRunStopped(long elapsedTime);
-     public void testStarted(String testName);
-     public void testEnded(String testName);
-     public void testFailed(int status, String testName, String trace);
+    public void testRunStarted(String testSuiteName, int testCount);
+    public void testRunEnded(long elapsedTime);
+    public void testRunStopped(long elapsedTime);
+    public void testStarted(String testName);
+    public void testEnded(String testName);
+    public void testFailed(int status, String testName, String trace);
 }
diff --git a/test-runner/src/junit/runner/TestSuiteLoader.java b/test-runner/src/junit/runner/TestSuiteLoader.java
index 39a4cf7..581ea23 100644
--- a/test-runner/src/junit/runner/TestSuiteLoader.java
+++ b/test-runner/src/junit/runner/TestSuiteLoader.java
@@ -4,6 +4,6 @@
  * An interface to define how a test suite should be loaded.
  */
 public interface TestSuiteLoader {
-	abstract public Class load(String suiteClassName) throws ClassNotFoundException;
-	abstract public Class reload(Class aClass) throws ClassNotFoundException;
+    abstract public Class load(String suiteClassName) throws ClassNotFoundException;
+    abstract public Class reload(Class aClass) throws ClassNotFoundException;
 }
diff --git a/test-runner/src/junit/runner/Version.java b/test-runner/src/junit/runner/Version.java
index b4541ab..4a6dc85 100644
--- a/test-runner/src/junit/runner/Version.java
+++ b/test-runner/src/junit/runner/Version.java
@@ -4,11 +4,17 @@
  * This class defines the current version of JUnit
  */
 public class Version {
-	private Version() {
-		// don't instantiate
-	}
+    private Version() {
+        // don't instantiate
+    }
 
-	public static String id() {
-		return "3.8.1";
-	}
+    public static String id() {
+        return "4.10";
+    }
+
+    // android-changed
+    /** @hide - not needed for public API */
+    public static void main(String[] args) {
+        System.out.println(id());
+    }
 }
diff --git a/test-runner/src/junit/textui/ResultPrinter.java b/test-runner/src/junit/textui/ResultPrinter.java
index 5c97112..4b26558 100644
--- a/test-runner/src/junit/textui/ResultPrinter.java
+++ b/test-runner/src/junit/textui/ResultPrinter.java
@@ -14,129 +14,129 @@
 import junit.runner.BaseTestRunner;
 
 public class ResultPrinter implements TestListener {
-	PrintStream fWriter;
-	int fColumn= 0;
-	
-	public ResultPrinter(PrintStream writer) {
-		fWriter= writer;
-	}
-	
-	/* API for use by textui.TestRunner
-	 */
+    PrintStream fWriter;
+    int fColumn= 0;
 
-	synchronized void print(TestResult result, long runTime) {
-		printHeader(runTime);
-	    printErrors(result);
-	    printFailures(result);
-	    printFooter(result);
-	}
+    public ResultPrinter(PrintStream writer) {
+        fWriter= writer;
+    }
 
-	void printWaitPrompt() {
-		getWriter().println();
-		getWriter().println("<RETURN> to continue");
-	}
-	
-	/* Internal methods 
-	 */
+    /* API for use by textui.TestRunner
+     */
 
-	protected void printHeader(long runTime) {
-		getWriter().println();
-		getWriter().println("Time: "+elapsedTimeAsString(runTime));
-	}
-	
-	protected void printErrors(TestResult result) {
-		printDefects(result.errors(), result.errorCount(), "error");
-	}
-	
-	protected void printFailures(TestResult result) {
-		printDefects(result.failures(), result.failureCount(), "failure");
-	}
-	
-	protected void printDefects(Enumeration booBoos, int count, String type) {
-		if (count == 0) return;
-		if (count == 1)
-			getWriter().println("There was " + count + " " + type + ":");
-		else
-			getWriter().println("There were " + count + " " + type + "s:");
-		for (int i= 1; booBoos.hasMoreElements(); i++) {
-			printDefect((TestFailure) booBoos.nextElement(), i);
-		}
-	}
-	
-	public void printDefect(TestFailure booBoo, int count) { // only public for testing purposes
-		printDefectHeader(booBoo, count);
-		printDefectTrace(booBoo);
-	}
+    synchronized void print(TestResult result, long runTime) {
+        printHeader(runTime);
+        printErrors(result);
+        printFailures(result);
+        printFooter(result);
+    }
 
-	protected void printDefectHeader(TestFailure booBoo, int count) {
-		// I feel like making this a println, then adding a line giving the throwable a chance to print something
-		// before we get to the stack trace.
-		getWriter().print(count + ") " + booBoo.failedTest());
-	}
+    void printWaitPrompt() {
+        getWriter().println();
+        getWriter().println("<RETURN> to continue");
+    }
 
-	protected void printDefectTrace(TestFailure booBoo) {
-		getWriter().print(BaseTestRunner.getFilteredTrace(booBoo.trace()));
-	}
+    /* Internal methods
+     */
 
-	protected void printFooter(TestResult result) {
-		if (result.wasSuccessful()) {
-			getWriter().println();
-			getWriter().print("OK");
-			getWriter().println (" (" + result.runCount() + " test" + (result.runCount() == 1 ? "": "s") + ")");
+    protected void printHeader(long runTime) {
+        getWriter().println();
+        getWriter().println("Time: "+elapsedTimeAsString(runTime));
+    }
 
-		} else {
-			getWriter().println();
-			getWriter().println("FAILURES!!!");
-			getWriter().println("Tests run: "+result.runCount()+ 
-				         ",  Failures: "+result.failureCount()+
-				         ",  Errors: "+result.errorCount());
-		}
-	    getWriter().println();
-	}
+    protected void printErrors(TestResult result) {
+        printDefects(result.errors(), result.errorCount(), "error");
+    }
+
+    protected void printFailures(TestResult result) {
+        printDefects(result.failures(), result.failureCount(), "failure");
+    }
+
+    protected void printDefects(Enumeration<TestFailure> booBoos, int count, String type) {
+        if (count == 0) return;
+        if (count == 1)
+            getWriter().println("There was " + count + " " + type + ":");
+        else
+            getWriter().println("There were " + count + " " + type + "s:");
+        for (int i= 1; booBoos.hasMoreElements(); i++) {
+            printDefect(booBoos.nextElement(), i);
+        }
+    }
+
+    public void printDefect(TestFailure booBoo, int count) { // only public for testing purposes
+        printDefectHeader(booBoo, count);
+        printDefectTrace(booBoo);
+    }
+
+    protected void printDefectHeader(TestFailure booBoo, int count) {
+        // I feel like making this a println, then adding a line giving the throwable a chance to print something
+        // before we get to the stack trace.
+        getWriter().print(count + ") " + booBoo.failedTest());
+    }
+
+    protected void printDefectTrace(TestFailure booBoo) {
+        getWriter().print(BaseTestRunner.getFilteredTrace(booBoo.trace()));
+    }
+
+    protected void printFooter(TestResult result) {
+        if (result.wasSuccessful()) {
+            getWriter().println();
+            getWriter().print("OK");
+            getWriter().println (" (" + result.runCount() + " test" + (result.runCount() == 1 ? "": "s") + ")");
+
+        } else {
+            getWriter().println();
+            getWriter().println("FAILURES!!!");
+            getWriter().println("Tests run: "+result.runCount()+
+                    ",  Failures: "+result.failureCount()+
+                    ",  Errors: "+result.errorCount());
+        }
+        getWriter().println();
+    }
 
 
-	/**
-	 * Returns the formatted string of the elapsed time.
-	 * Duplicated from BaseTestRunner. Fix it.
-	 */
-	protected String elapsedTimeAsString(long runTime) {
-            // The following line was altered for compatibility with
-            // Android libraries.
-	    return Double.toString((double)runTime/1000);
-	}
+    /**
+     * Returns the formatted string of the elapsed time.
+     * Duplicated from BaseTestRunner. Fix it.
+     */
+    protected String elapsedTimeAsString(long runTime) {
+        // The following line was altered for compatibility with
+        // Android libraries.
+        return Double.toString((double)runTime/1000);
+    }
 
-	public PrintStream getWriter() {
-		return fWriter;
-	}
-	/**
-	 * @see junit.framework.TestListener#addError(Test, Throwable)
-	 */
-	public void addError(Test test, Throwable t) {
-		getWriter().print("E");
-	}
+    public PrintStream getWriter() {
+        return fWriter;
+    }
+    /**
+     * @see junit.framework.TestListener#addError(Test, Throwable)
+     */
+    public void addError(Test test, Throwable t) {
+        getWriter().print("E");
+    }
 
-	/**
-	 * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
-	 */
-	public void addFailure(Test test, AssertionFailedError t) {
-		getWriter().print("F");
-	}
+    /**
+     * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
+     */
+    public void addFailure(Test test, AssertionFailedError t) {
+        getWriter().print("F");
+    }
 
-	/**
-	 * @see junit.framework.TestListener#endTest(Test)
-	 */
-	public void endTest(Test test) {
-	}
+    /**
+     * @see junit.framework.TestListener#endTest(Test)
+     */
+    public void endTest(Test test) {
+    }
 
-	/**
-	 * @see junit.framework.TestListener#startTest(Test)
-	 */
-	public void startTest(Test test) {
-		getWriter().print(".");
-		if (fColumn++ >= 40) {
-			getWriter().println();
-			fColumn= 0;
-		}
-	}
+    /**
+     * @see junit.framework.TestListener#startTest(Test)
+     */
+    public void startTest(Test test) {
+        getWriter().print(".");
+        if (fColumn++ >= 40) {
+            getWriter().println();
+            fColumn= 0;
+        }
+    }
 
 }
diff --git a/test-runner/src/junit/textui/TestRunner.java b/test-runner/src/junit/textui/TestRunner.java
index 8bdc325..e955e0e 100644
--- a/test-runner/src/junit/textui/TestRunner.java
+++ b/test-runner/src/junit/textui/TestRunner.java
@@ -3,187 +3,201 @@
 
 import java.io.PrintStream;
 
-import junit.framework.*;
-import junit.runner.*;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+import junit.runner.BaseTestRunner;
+import junit.runner.Version;
 
 /**
  * A command line based tool to run tests.
  * <pre>
  * java junit.textui.TestRunner [-wait] TestCaseClass
  * </pre>
- * TestRunner expects the name of a TestCase class as argument.
- * If this class defines a static <code>suite</code> method it 
- * will be invoked and the returned test is run. Otherwise all 
+ * 
+ * <p>TestRunner expects the name of a TestCase class as argument.
+ * If this class defines a static <code>suite</code> method it
+ * will be invoked and the returned test is run. Otherwise all
  * the methods starting with "test" having no arguments are run.
  * <p>
  * When the wait command line argument is given TestRunner
  * waits until the users types RETURN.
  * <p>
  * TestRunner prints a trace as the tests are executed followed by a
- * summary at the end. 
+ * summary at the end.
  */
 public class TestRunner extends BaseTestRunner {
-	private ResultPrinter fPrinter;
-	
-	public static final int SUCCESS_EXIT= 0;
-	public static final int FAILURE_EXIT= 1;
-	public static final int EXCEPTION_EXIT= 2;
+    private ResultPrinter fPrinter;
 
-	/**
-	 * Constructs a TestRunner.
-	 */
-	public TestRunner() {
-		this(System.out);
-	}
+    public static final int SUCCESS_EXIT= 0;
+    public static final int FAILURE_EXIT= 1;
+    public static final int EXCEPTION_EXIT= 2;
 
-	/**
-	 * Constructs a TestRunner using the given stream for all the output
-	 */
-	public TestRunner(PrintStream writer) {
-		this(new ResultPrinter(writer));
-	}
-	
-	/**
-	 * Constructs a TestRunner using the given ResultPrinter all the output
-	 */
-	public TestRunner(ResultPrinter printer) {
-		fPrinter= printer;
-	}
-	
-	/**
-	 * Runs a suite extracted from a TestCase subclass.
-	 */
-	static public void run(Class testClass) {
-		run(new TestSuite(testClass));
-	}
+    /**
+     * Constructs a TestRunner.
+     */
+    public TestRunner() {
+        this(System.out);
+    }
 
-	/**
-	 * Runs a single test and collects its results.
-	 * This method can be used to start a test run
-	 * from your program.
-	 * <pre>
-	 * public static void main (String[] args) {
-	 *     test.textui.TestRunner.run(suite());
-	 * }
-	 * </pre>
-	 */
-	static public TestResult run(Test test) {
-		TestRunner runner= new TestRunner();
-		return runner.doRun(test);
-	}
+    /**
+     * Constructs a TestRunner using the given stream for all the output
+     */
+    public TestRunner(PrintStream writer) {
+        this(new ResultPrinter(writer));
+    }
 
-	/**
-	 * Runs a single test and waits until the user
-	 * types RETURN.
-	 */
-	static public void runAndWait(Test suite) {
-		TestRunner aTestRunner= new TestRunner();
-		aTestRunner.doRun(suite, true);
-	}
+    /**
+     * Constructs a TestRunner using the given ResultPrinter all the output
+     */
+    public TestRunner(ResultPrinter printer) {
+        fPrinter= printer;
+    }
 
-	/**
-	 * Always use the StandardTestSuiteLoader. Overridden from
-	 * BaseTestRunner.
-	 */
-	public TestSuiteLoader getLoader() {
-		return new StandardTestSuiteLoader();
-	}
+    /**
+     * Runs a suite extracted from a TestCase subclass.
+     */
+    static public void run(Class<? extends TestCase> testClass) {
+        run(new TestSuite(testClass));
+    }
 
-	public void testFailed(int status, Test test, Throwable t) {
-	}
-	
-	public void testStarted(String testName) {
-	}
-	
-	public void testEnded(String testName) {
-	}
+    /**
+     * Runs a single test and collects its results.
+     * This method can be used to start a test run
+     * from your program.
+     * <pre>
+     * public static void main (String[] args) {
+     *    test.textui.TestRunner.run(suite());
+     * }
+     * </pre>
+     */
+    static public TestResult run(Test test) {
+        TestRunner runner= new TestRunner();
+        return runner.doRun(test);
+    }
 
-	/**
-	 * Creates the TestResult to be used for the test run.
-	 */
-	protected TestResult createTestResult() {
-		return new TestResult();
-	}
-	
-	public TestResult doRun(Test test) {
-		return doRun(test, false);
-	}
-	
-	public TestResult doRun(Test suite, boolean wait) {
-		TestResult result= createTestResult();
-		result.addListener(fPrinter);
-		long startTime= System.currentTimeMillis();
-		suite.run(result);
-		long endTime= System.currentTimeMillis();
-		long runTime= endTime-startTime;
-		fPrinter.print(result, runTime);
+    /**
+     * Runs a single test and waits until the user
+     * types RETURN.
+     */
+    static public void runAndWait(Test suite) {
+        TestRunner aTestRunner= new TestRunner();
+        aTestRunner.doRun(suite, true);
+    }
 
-		pause(wait);
-		return result;
-	}
+    @Override
+    public void testFailed(int status, Test test, Throwable t) {
+    }
 
-	protected void pause(boolean wait) {
-		if (!wait) return;
-		fPrinter.printWaitPrompt();
-		try {
-			System.in.read();
-		}
-		catch(Exception e) {
-		}
-	}
-	
-	public static void main(String args[]) {
-		TestRunner aTestRunner= new TestRunner();
-		try {
-			TestResult r= aTestRunner.start(args);
-			if (!r.wasSuccessful()) 
-				System.exit(FAILURE_EXIT);
-			System.exit(SUCCESS_EXIT);
-		} catch(Exception e) {
-			System.err.println(e.getMessage());
-			System.exit(EXCEPTION_EXIT);
-		}
-	}
+    @Override
+    public void testStarted(String testName) {
+    }
 
-	/**
-	 * Starts a test run. Analyzes the command line arguments
-	 * and runs the given test suite.
-	 */
-	protected TestResult start(String args[]) throws Exception {
-		String testCase= "";
-		boolean wait= false;
-		
-		for (int i= 0; i < args.length; i++) {
-			if (args[i].equals("-wait"))
-				wait= true;
-			else if (args[i].equals("-c")) 
-				testCase= extractClassName(args[++i]);
-			else if (args[i].equals("-v"))
-				System.err.println("JUnit "+Version.id()+" by Kent Beck and Erich Gamma");
-			else
-				testCase= args[i];
-		}
-		
-		if (testCase.equals("")) 
-			throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class");
+    @Override
+    public void testEnded(String testName) {
+    }
 
-		try {
-			Test suite= getTest(testCase);
-			return doRun(suite, wait);
-		}
-		catch(Exception e) {
-			throw new Exception("Could not create and run test suite: "+e);
-		}
-	}
-		
-	protected void runFailed(String message) {
-		System.err.println(message);
-		System.exit(FAILURE_EXIT);
-	}
-	
-	public void setPrinter(ResultPrinter printer) {
-		fPrinter= printer;
-	}
-		
-	
+    /**
+     * Creates the TestResult to be used for the test run.
+     */
+    protected TestResult createTestResult() {
+        return new TestResult();
+    }
+
+    public TestResult doRun(Test test) {
+        return doRun(test, false);
+    }
+
+    public TestResult doRun(Test suite, boolean wait) {
+        TestResult result= createTestResult();
+        result.addListener(fPrinter);
+        long startTime= System.currentTimeMillis();
+        suite.run(result);
+        long endTime= System.currentTimeMillis();
+        long runTime= endTime-startTime;
+        fPrinter.print(result, runTime);
+
+        pause(wait);
+        return result;
+    }
+
+    protected void pause(boolean wait) {
+        if (!wait) return;
+        fPrinter.printWaitPrompt();
+        try {
+            System.in.read();
+        }
+        catch(Exception e) {
+        }
+    }
+
+    public static void main(String args[]) {
+        TestRunner aTestRunner= new TestRunner();
+        try {
+            TestResult r= aTestRunner.start(args);
+            if (!r.wasSuccessful())
+                System.exit(FAILURE_EXIT);
+            System.exit(SUCCESS_EXIT);
+        } catch(Exception e) {
+            System.err.println(e.getMessage());
+            System.exit(EXCEPTION_EXIT);
+        }
+    }
+
+    /**
+     * Starts a test run. Analyzes the command line arguments
+     * and runs the given test suite.
+     */
+    public TestResult start(String args[]) throws Exception {
+        String testCase= "";
+        String method= "";
+        boolean wait= false;
+
+        for (int i= 0; i < args.length; i++) {
+            if (args[i].equals("-wait"))
+                wait= true;
+            else if (args[i].equals("-c"))
+                testCase= extractClassName(args[++i]);
+            else if (args[i].equals("-m")) {
+                String arg= args[++i];
+                int lastIndex= arg.lastIndexOf('.');
+                testCase= arg.substring(0, lastIndex);
+                method= arg.substring(lastIndex + 1);
+            } else if (args[i].equals("-v"))
+                System.err.println("JUnit " + Version.id() + " by Kent Beck and Erich Gamma");
+            else
+                testCase= args[i];
+        }
+
+        if (testCase.equals(""))
+            throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class");
+
+        try {
+            if (!method.equals(""))
+                return runSingleMethod(testCase, method, wait);
+            Test suite= getTest(testCase);
+            return doRun(suite, wait);
+        } catch (Exception e) {
+            throw new Exception("Could not create and run test suite: " + e);
+        }
+    }
+
+    protected TestResult runSingleMethod(String testCase, String method, boolean wait) throws Exception {
+        Class<? extends TestCase> testClass= loadSuiteClass(testCase).asSubclass(TestCase.class);
+        Test test= TestSuite.createTest(testClass, method);
+        return doRun(test, wait);
+    }
+
+    @Override
+    protected void runFailed(String message) {
+        System.err.println(message);
+        System.exit(FAILURE_EXIT);
+    }
+
+    public void setPrinter(ResultPrinter printer) {
+        fPrinter= printer;
+    }
+
+
 }
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index d151d9e..77c0a3f 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -168,7 +168,7 @@
         }
 
         // This is asynchronous, but it gets processed by WebCore before it starts loading pages.
-        mWebViewClassic.useMockDeviceOrientation();
+        mWebViewClassic.setUseMockDeviceOrientation();
     }
 
     @Override
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
index fc22472..f958ade 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
@@ -394,7 +394,7 @@
         webViewSettings.setPageCacheCapacity(0);
 
         // This is asynchronous, but it gets processed by WebCore before it starts loading pages.
-        WebViewClassic.fromWebView(mCurrentWebView).useMockDeviceOrientation();
+        WebViewClassic.fromWebView(mCurrentWebView).setUseMockDeviceOrientation();
 
         // Must do this after setting the AppCache path.
         WebStorage.getInstance().deleteAllData();
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_element.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_element.java
index 3e2a2ca..f52fe6ff 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_element.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_element.java
@@ -108,7 +108,7 @@
         _RS_ASSERT("complexElemsimpleElem.getDataType() == NONE",
                    complexElem.getDataType() == DataType.NONE);
         _RS_ASSERT("complexElem.getSizeBytes() == ScriptField_ComplexStruct.Item.sizeof",
-                   complexElem.getSizeBytes() == ScriptField_ComplexStruct.Item.sizeof);
+                   complexElem.getBytesSize() == ScriptField_ComplexStruct.Item.sizeof);
 
         for (int i = 0; i < subElemCount; i ++) {
             _RS_ASSERT("complexElem.getSubElement(i) != null",
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_raster.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_raster.java
index 1de4d71..ca54ac4 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_raster.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_raster.java
@@ -60,13 +60,13 @@
     }
 
     private void testJavaSide(RenderScript RS) {
-        _RS_ASSERT("pointSpriteEnabled.getPointSpriteEnabled() == true",
-                    pointSpriteEnabled.getPointSpriteEnabled() == true);
+        _RS_ASSERT("pointSpriteEnabled.isPointSpriteEnabled() == true",
+                    pointSpriteEnabled.isPointSpriteEnabled() == true);
         _RS_ASSERT("pointSpriteEnabled.getCullMode() == ProgramRaster.CullMode.BACK",
                     pointSpriteEnabled.getCullMode() == ProgramRaster.CullMode.BACK);
 
-        _RS_ASSERT("cullMode.getPointSpriteEnabled() == false",
-                    cullMode.getPointSpriteEnabled() == false);
+        _RS_ASSERT("cullMode.isPointSpriteEnabled() == false",
+                    cullMode.isPointSpriteEnabled() == false);
         _RS_ASSERT("cullMode.getCullMode() == ProgramRaster.CullMode.FRONT",
                     cullMode.getCullMode() == ProgramRaster.CullMode.FRONT);
     }
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_store.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_store.java
index 72a401d..4410ee3 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_store.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_store.java
@@ -112,15 +112,15 @@
                      boolean B,
                      boolean A,
                      boolean dither) {
-        _RS_ASSERT("ps.getDepthMaskEnabled() == depthMask", ps.getDepthMaskEnabled() == depthMask);
+        _RS_ASSERT("ps.isDepthMaskEnabled() == depthMask", ps.isDepthMaskEnabled() == depthMask);
         _RS_ASSERT("ps.getDepthFunc() == df", ps.getDepthFunc() == df);
         _RS_ASSERT("ps.getBlendSrcFunc() == bsf", ps.getBlendSrcFunc() == bsf);
         _RS_ASSERT("ps.getBlendDstFunc() == bdf", ps.getBlendDstFunc() == bdf);
-        _RS_ASSERT("ps.getColorMaskREnabled() == R", ps.getColorMaskREnabled() == R);
-        _RS_ASSERT("ps.getColorMaskGEnabled() == G", ps.getColorMaskGEnabled() == G);
-        _RS_ASSERT("ps.getColorMaskBEnabled() == B", ps.getColorMaskBEnabled() == B);
-        _RS_ASSERT("ps.getColorMaskAEnabled() == A", ps.getColorMaskAEnabled() == A);
-        _RS_ASSERT("ps.getDitherEnabled() == dither", ps.getDitherEnabled() == dither);
+        _RS_ASSERT("ps.isColorMaskRedEnabled() == R", ps.isColorMaskRedEnabled() == R);
+        _RS_ASSERT("ps.isColorMaskGreenEnabled() == G", ps.isColorMaskGreenEnabled() == G);
+        _RS_ASSERT("ps.isColorMaskBlueEnabled () == B", ps.isColorMaskBlueEnabled () == B);
+        _RS_ASSERT("ps.isColorMaskAlphaEnabled() == A", ps.isColorMaskAlphaEnabled() == A);
+        _RS_ASSERT("ps.isDitherEnabled() == dither", ps.isDitherEnabled() == dither);
     }
 
     void varyBuilderColorAndDither(ProgramStore.Builder pb,
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index 03d5134..3bd03f5 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -21,6 +21,7 @@
 import android.net.wifi.p2p.WifiP2pDevice;
 import android.net.wifi.p2p.WifiP2pGroup;
 import android.net.wifi.p2p.WifiP2pProvDiscEvent;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
 import android.net.wifi.StateChangeResult;
 import android.os.Message;
 import android.util.Log;
@@ -29,6 +30,7 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.StateMachine;
 
+import java.util.List;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
 
@@ -214,6 +216,52 @@
        group_capab=0x0 */
     private static final String P2P_PROV_DISC_SHOW_PIN_STR = "P2P-PROV-DISC-SHOW-PIN";
 
+    /*
+     * Protocol format is as follows.<br>
+     * See the Table.62 in the WiFi Direct specification for the detail.
+     * ______________________________________________________________
+     * |           Length(2byte)     | Type(1byte) | TransId(1byte)}|
+     * ______________________________________________________________
+     * | status(1byte)  |            vendor specific(variable)      |
+     *
+     * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300000101
+     * length=3, service type=0(ALL Service), transaction id=1,
+     * status=1(service protocol type not available)<br>
+     *
+     * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300020201
+     * length=3, service type=2(UPnP), transaction id=2,
+     * status=1(service protocol type not available)
+     *
+     * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 990002030010757569643a3131323
+     * 2646534652d383537342d353961622d393332322d3333333435363738393034343a3
+     * a75726e3a736368656d61732d75706e702d6f72673a736572766963653a436f6e746
+     * 56e744469726563746f72793a322c757569643a36383539646564652d383537342d3
+     * 53961622d393333322d3132333435363738393031323a3a75706e703a726f6f74646
+     * 576696365
+     * length=153,type=2(UPnP),transaction id=3,status=0
+     *
+     * UPnP Protocol format is as follows.
+     * ______________________________________________________
+     * |  Version (1)  |          USN (Variable)            |
+     *
+     * version=0x10(UPnP1.0) data=usn:uuid:1122de4e-8574-59ab-9322-33345678
+     * 9044::urn:schemas-upnp-org:service:ContentDirectory:2,usn:uuid:6859d
+     * ede-8574-59ab-9332-123456789012::upnp:rootdevice
+     *
+     * P2P-SERV-DISC-RESP 58:17:0c:bc:dd:ca 21 1900010200045f6970
+     * 70c00c000c01094d795072696e746572c027
+     * length=25, type=1(Bonjour),transaction id=2,status=0
+     *
+     * Bonjour Protocol format is as follows.
+     * __________________________________________________________
+     * |DNS Name(Variable)|DNS Type(1)|Version(1)|RDATA(Variable)|
+     *
+     * DNS Name=_ipp._tcp.local.,DNS type=12(PTR), Version=1,
+     * RDATA=MyPrinter._ipp._tcp.local.
+     *
+     */
+    private static final String P2P_SERV_DISC_RESP_STR = "P2P-SERV-DISC-RESP";
+
     private static final String HOST_AP_EVENT_PREFIX_STR = "AP";
     /* AP-STA-CONNECTED 42:fc:89:a8:96:09 dev_addr=02:90:4c:a0:92:54 */
     private static final String AP_STA_CONNECTED_STR = "AP-STA-CONNECTED";
@@ -268,6 +316,7 @@
     public static final int P2P_PROV_DISC_ENTER_PIN_EVENT        = BASE + 35;
     public static final int P2P_PROV_DISC_SHOW_PIN_EVENT         = BASE + 36;
     public static final int P2P_FIND_STOPPED_EVENT               = BASE + 37;
+    public static final int P2P_SERV_DISC_RESP_EVENT             = BASE + 38;
 
     /* hostap events */
     public static final int AP_STA_DISCONNECTED_EVENT            = BASE + 41;
@@ -558,6 +607,13 @@
             } else if (dataString.startsWith(P2P_PROV_DISC_SHOW_PIN_STR)) {
                 mStateMachine.sendMessage(P2P_PROV_DISC_SHOW_PIN_EVENT,
                         new WifiP2pProvDiscEvent(dataString));
+            } else if (dataString.startsWith(P2P_SERV_DISC_RESP_STR)) {
+                List<WifiP2pServiceResponse> list = WifiP2pServiceResponse.newInstance(dataString);
+                if (list != null) {
+                    mStateMachine.sendMessage(P2P_SERV_DISC_RESP_EVENT, list);
+                } else {
+                    Log.e(TAG, "Null service resp " + dataString);
+                }
             }
         }
 
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index db73ea8..4ec2e02 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -19,8 +19,9 @@
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pGroup;
 import android.net.wifi.p2p.WifiP2pDevice;
-import android.os.SystemProperties;
 import android.text.TextUtils;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
 import android.util.Log;
 
 import java.io.InputStream;
@@ -644,4 +645,78 @@
     public String p2pPeer(String deviceAddress) {
         return doStringCommand("P2P_PEER " + deviceAddress);
     }
+
+    public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) {
+        /*
+         * P2P_SERVICE_ADD bonjour <query hexdump> <RDATA hexdump>
+         * P2P_SERVICE_ADD upnp <version hex> <service>
+         *
+         * e.g)
+         * [Bonjour]
+         * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
+         * P2P_SERVICE_ADD bonjour 045f697070c00c000c01 094d795072696e746572c027
+         * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
+         * P2P_SERVICE_ADD bonjour 096d797072696e746572045f697070c00c001001
+         *  09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074
+         *
+         * [UPnP]
+         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012
+         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice
+         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
+         * -org:device:InternetGatewayDevice:1
+         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
+         * -org:service:ContentDirectory:2
+         */
+        for (String s : servInfo.getSupplicantQueryList()) {
+            String command = "P2P_SERVICE_ADD";
+            command += (" " + s);
+            if (!doBooleanCommand(command)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean p2pServiceDel(WifiP2pServiceInfo servInfo) {
+        /*
+         * P2P_SERVICE_DEL bonjour <query hexdump>
+         * P2P_SERVICE_DEL upnp <version hex> <service>
+         */
+        for (String s : servInfo.getSupplicantQueryList()) {
+            String command = "P2P_SERVICE_DEL ";
+
+            String[] data = s.split(" ");
+            if (data.length < 2) {
+                return false;
+            }
+            if ("upnp".equals(data[0])) {
+                command += s;
+            } else if ("bonjour".equals(data[0])) {
+                command += data[0];
+                command += (" " + data[1]);
+            } else {
+                return false;
+            }
+            if (!doBooleanCommand(command)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean p2pServiceFlush() {
+        return doBooleanCommand("P2P_SERVICE_FLUSH");
+    }
+
+    public String p2pServDiscReq(String addr, String query) {
+        String command = "P2P_SERV_DISC_REQ";
+        command += (" " + addr);
+        command += (" " + query);
+
+        return doStringCommand(command);
+    }
+
+    public boolean p2pServDiscCancelReq(String id) {
+        return doBooleanCommand("P2P_SERV_DISC_CANCEL_REQ " + id);
+    }
 }
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
index 9ce2545..3751727 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
@@ -24,6 +24,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 
 /**
  * A class representing a Wi-Fi P2p device list
@@ -32,24 +33,28 @@
  */
 public class WifiP2pDeviceList implements Parcelable {
 
-    private Collection<WifiP2pDevice> mDevices;
+    private HashMap<String, WifiP2pDevice> mDevices;
 
     public WifiP2pDeviceList() {
-        mDevices = new ArrayList<WifiP2pDevice>();
+        mDevices = new HashMap<String, WifiP2pDevice>();
     }
 
     /** copy constructor */
     public WifiP2pDeviceList(WifiP2pDeviceList source) {
         if (source != null) {
-            mDevices = source.getDeviceList();
+            for (WifiP2pDevice d : source.getDeviceList()) {
+                mDevices.put(d.deviceAddress, d);
+            }
         }
     }
 
     /** @hide */
     public WifiP2pDeviceList(ArrayList<WifiP2pDevice> devices) {
-        mDevices = new ArrayList<WifiP2pDevice>();
+        mDevices = new HashMap<String, WifiP2pDevice>();
         for (WifiP2pDevice device : devices) {
-            mDevices.add(device);
+            if (device.deviceAddress != null) {
+                mDevices.put(device.deviceAddress, device);
+            }
         }
     }
 
@@ -62,37 +67,42 @@
 
     /** @hide */
     public void update(WifiP2pDevice device) {
-        if (device == null) return;
-        for (WifiP2pDevice d : mDevices) {
-            //Found, update fields that can change
-            if (d.equals(device)) {
-                d.deviceName = device.deviceName;
-                d.primaryDeviceType = device.primaryDeviceType;
-                d.secondaryDeviceType = device.secondaryDeviceType;
-                d.wpsConfigMethodsSupported = device.wpsConfigMethodsSupported;
-                d.deviceCapability = device.deviceCapability;
-                d.groupCapability = device.groupCapability;
-                return;
-            }
+        if (device == null || device.deviceAddress == null) return;
+        WifiP2pDevice d = mDevices.get(device.deviceAddress);
+        if (d != null) {
+            d.deviceName = device.deviceName;
+            d.primaryDeviceType = device.primaryDeviceType;
+            d.secondaryDeviceType = device.secondaryDeviceType;
+            d.wpsConfigMethodsSupported = device.wpsConfigMethodsSupported;
+            d.deviceCapability = device.deviceCapability;
+            d.groupCapability = device.groupCapability;
+            return;
         }
         //Not found, add a new one
-        mDevices.add(device);
+        mDevices.put(device.deviceAddress, device);
+    }
+
+    /** @hide */
+    public WifiP2pDevice get(String deviceAddress) {
+        if (deviceAddress == null) return null;
+
+        return mDevices.get(deviceAddress);
     }
 
     /** @hide */
     public boolean remove(WifiP2pDevice device) {
-        if (device == null) return false;
-        return mDevices.remove(device);
+        if (device == null || device.deviceAddress == null) return false;
+        return mDevices.remove(device.deviceAddress) != null;
     }
 
     /** Get the list of devices */
     public Collection<WifiP2pDevice> getDeviceList() {
-        return Collections.unmodifiableCollection(mDevices);
+        return Collections.unmodifiableCollection(mDevices.values());
     }
 
     public String toString() {
         StringBuffer sbuf = new StringBuffer();
-        for (WifiP2pDevice device : mDevices) {
+        for (WifiP2pDevice device : mDevices.values()) {
             sbuf.append("\n").append(device);
         }
         return sbuf.toString();
@@ -106,7 +116,7 @@
     /** Implement the Parcelable interface */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mDevices.size());
-        for(WifiP2pDevice device : mDevices) {
+        for(WifiP2pDevice device : mDevices.values()) {
             dest.writeParcelable(device, flags);
         }
     }
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index c7f6bf0..35f37a8 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -21,6 +21,14 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
+import android.net.nsd.DnsSdTxtRecord;
+import android.net.wifi.p2p.nsd.WifiP2pBonjourServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pBonjourServiceResponse;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceResponse;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Handler;
@@ -36,6 +44,7 @@
 import com.android.internal.util.Protocol;
 
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * This class provides the API for managing Wi-Fi peer-to-peer connectivity. This lets an
@@ -290,6 +299,61 @@
     /** @hide */
     public static final int RESPONSE_GROUP_INFO                     = BASE + 24;
 
+    /** @hide */
+    public static final int ADD_LOCAL_SERVICE                       = BASE + 28;
+    /** @hide */
+    public static final int ADD_LOCAL_SERVICE_FAILED                = BASE + 29;
+    /** @hide */
+    public static final int ADD_LOCAL_SERVICE_SUCCEEDED             = BASE + 30;
+
+    /** @hide */
+    public static final int REMOVE_LOCAL_SERVICE                    = BASE + 31;
+    /** @hide */
+    public static final int REMOVE_LOCAL_SERVICE_FAILED             = BASE + 32;
+    /** @hide */
+    public static final int REMOVE_LOCAL_SERVICE_SUCCEEDED          = BASE + 33;
+
+    /** @hide */
+    public static final int CLEAR_LOCAL_SERVICES                    = BASE + 34;
+    /** @hide */
+    public static final int CLEAR_LOCAL_SERVICES_FAILED             = BASE + 35;
+    /** @hide */
+    public static final int CLEAR_LOCAL_SERVICES_SUCCEEDED          = BASE + 36;
+
+    /** @hide */
+    public static final int ADD_SERVICE_REQUEST                     = BASE + 37;
+    /** @hide */
+    public static final int ADD_SERVICE_REQUEST_FAILED              = BASE + 38;
+    /** @hide */
+    public static final int ADD_SERVICE_REQUEST_SUCCEEDED           = BASE + 39;
+
+    /** @hide */
+    public static final int REMOVE_SERVICE_REQUEST                  = BASE + 40;
+    /** @hide */
+    public static final int REMOVE_SERVICE_REQUEST_FAILED           = BASE + 41;
+    /** @hide */
+    public static final int REMOVE_SERVICE_REQUEST_SUCCEEDED        = BASE + 42;
+
+    /** @hide */
+    public static final int CLEAR_SERVICE_REQUESTS                  = BASE + 43;
+    /** @hide */
+    public static final int CLEAR_SERVICE_REQUESTS_FAILED           = BASE + 44;
+    /** @hide */
+    public static final int CLEAR_SERVICE_REQUESTS_SUCCEEDED        = BASE + 45;
+
+    /** @hide */
+    public static final int DISCOVER_SERVICES                       = BASE + 46;
+    /** @hide */
+    public static final int DISCOVER_SERVICES_FAILED                = BASE + 47;
+    /** @hide */
+    public static final int DISCOVER_SERVICES_SUCCEEDED             = BASE + 48;
+
+    /** @hide */
+    public static final int PING                                    = BASE + 49;
+
+    /** @hide */
+    public static final int RESPONSE_SERVICE                        = BASE + 50;
+
     /**
      * Create a new WifiP2pManager instance. Applications use
      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -321,6 +385,14 @@
      */
     public static final int BUSY                = 2;
 
+    /**
+     * Passed with {@link ActionListener#onFailure}.
+     * Indicates that the {@link #discoverServices} failed because no service
+     * requests are set.
+     * @hide
+     */
+    public static final int NO_SERVICE_REQUESTS = 3;
+
     /** Interface for callback invocation when framework channel is lost */
     public interface ChannelListener {
         /**
@@ -370,6 +442,93 @@
     }
 
     /**
+    * Interface for callback invocation when service discovery response other than
+    * UPnP or Bonjour is received
+    * @hide
+    */
+    public interface ServiceResponseListener {
+
+        /**
+         * The requested service response is available.
+         *
+         * @param serviceType service type. see the service type of
+         * {@link WifiP2pServiceInfo}
+         * @param responseData service discovery response data based on the requested
+         *  service protocol type. The format depends on the service type.
+         * @param srcDevice source device.
+         */
+        public void onServiceAvailable(int serviceType,
+                byte[] responseData, WifiP2pDevice srcDevice);
+    }
+
+    /**
+     * Interface for callback invocation when Bonjour service discovery response
+     * is received
+     * @hide
+     */
+    public interface BonjourServiceResponseListener {
+
+        /**
+         * The requested Bonjour service response is available.
+         *
+         * <p>This function is invoked when the device with the specified Bonjour
+         * registration type returned the instance name.
+         * @param instanceName instance name.<br>
+         *  e.g) "MyPrinter".
+         * @param registrationType <br>
+         * e.g) "_ipp._tcp.local."
+         * @param srcDevice source device.
+         */
+        public void onBonjourServiceAvailable(String instanceName,
+                String registrationType, WifiP2pDevice srcDevice);
+
+   }
+
+    /**
+     * Interface for callback invocation when Bonjour TXT record is available
+     * for a service
+     * @hide
+     */
+   public interface BonjourTxtRecordListener {
+        /**
+         * The requested Bonjour service response is available.
+         *
+         * <p>This function is invoked when the device with the specified full
+         * service domain service returned TXT record.
+         *
+         * @param fullDomainName full domain name. <br>
+         * e.g) "MyPrinter._ipp._tcp.local.".
+         * @param record txt record.
+         * @param srcDevice source device.
+         */
+        public void onBonjourTxtRecordAvailable(String fullDomainName,
+                DnsSdTxtRecord record,
+                WifiP2pDevice srcDevice);
+   }
+
+    /**
+     * Interface for callback invocation when upnp service discovery response
+     * is received
+     * @hide
+     * */
+    public interface UpnpServiceResponseListener {
+
+        /**
+         * The requested upnp service response is available.
+         *
+         * <p>This function is invoked when the specified device or service is found.
+         *
+         * @param uniqueServiceNames The list of unique service names.<br>
+         * e.g) uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:device:
+         * MediaServer:1
+         * @param srcDevice source device.
+         */
+        public void onUpnpServiceAvailable(List<String> uniqueServiceNames,
+                WifiP2pDevice srcDevice);
+    }
+
+
+    /**
      * A channel that connects the application to the Wifi p2p framework.
      * Most p2p operations require a Channel as an argument. An instance of Channel is obtained
      * by doing a call on {@link #initialize}
@@ -382,6 +541,10 @@
         }
         private final static int INVALID_LISTENER_KEY = 0;
         private ChannelListener mChannelListener;
+        private ServiceResponseListener mServRspListener;
+        private BonjourServiceResponseListener mBonjourServRspListener;
+        private BonjourTxtRecordListener mBonjourTxtListener;
+        private UpnpServiceResponseListener mUpnpServRspListener;
         private HashMap<Integer, Object> mListenerMap = new HashMap<Integer, Object>();
         private Object mListenerMapLock = new Object();
         private int mListenerKey = 0;
@@ -406,10 +569,17 @@
                     /* ActionListeners grouped together */
                     case WifiP2pManager.DISCOVER_PEERS_FAILED:
                     case WifiP2pManager.STOP_DISCOVERY_FAILED:
+                    case WifiP2pManager.DISCOVER_SERVICES_FAILED:
                     case WifiP2pManager.CONNECT_FAILED:
                     case WifiP2pManager.CANCEL_CONNECT_FAILED:
                     case WifiP2pManager.CREATE_GROUP_FAILED:
                     case WifiP2pManager.REMOVE_GROUP_FAILED:
+                    case WifiP2pManager.ADD_LOCAL_SERVICE_FAILED:
+                    case WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED:
+                    case WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED:
+                    case WifiP2pManager.ADD_SERVICE_REQUEST_FAILED:
+                    case WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED:
+                    case WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED:
                         if (listener != null) {
                             ((ActionListener) listener).onFailure(message.arg1);
                         }
@@ -417,10 +587,17 @@
                     /* ActionListeners grouped together */
                     case WifiP2pManager.DISCOVER_PEERS_SUCCEEDED:
                     case WifiP2pManager.STOP_DISCOVERY_SUCCEEDED:
+                    case WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED:
                     case WifiP2pManager.CONNECT_SUCCEEDED:
                     case WifiP2pManager.CANCEL_CONNECT_SUCCEEDED:
                     case WifiP2pManager.CREATE_GROUP_SUCCEEDED:
                     case WifiP2pManager.REMOVE_GROUP_SUCCEEDED:
+                    case WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED:
+                    case WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED:
+                    case WifiP2pManager.CLEAR_LOCAL_SERVICES_SUCCEEDED:
+                    case WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED:
+                    case WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED:
+                    case WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED:
                         if (listener != null) {
                             ((ActionListener) listener).onSuccess();
                         }
@@ -443,6 +620,10 @@
                             ((GroupInfoListener) listener).onGroupInfoAvailable(group);
                         }
                         break;
+                    case WifiP2pManager.RESPONSE_SERVICE:
+                        WifiP2pServiceResponse resp = (WifiP2pServiceResponse) message.obj;
+                        handleServiceResponse(resp);
+                        break;
                    default:
                         Log.d(TAG, "Ignored " + message);
                         break;
@@ -450,7 +631,47 @@
             }
         }
 
-        int putListener(Object listener) {
+        private void handleServiceResponse(WifiP2pServiceResponse resp) {
+            if (resp instanceof WifiP2pBonjourServiceResponse) {
+                handleBonjourServiceResponse((WifiP2pBonjourServiceResponse)resp);
+            } else if (resp instanceof WifiP2pUpnpServiceResponse) {
+                if (mUpnpServRspListener != null) {
+                    handleUpnpServiceResponse((WifiP2pUpnpServiceResponse)resp);
+                }
+            } else {
+                if (mServRspListener != null) {
+                    mServRspListener.onServiceAvailable(resp.getServiceType(),
+                            resp.getRawData(), resp.getSrcDevice());
+                }
+            }
+        }
+
+        private void handleUpnpServiceResponse(WifiP2pUpnpServiceResponse resp) {
+            mUpnpServRspListener.onUpnpServiceAvailable(resp.getUniqueServiceNames(),
+                    resp.getSrcDevice());
+        }
+
+        private void handleBonjourServiceResponse(WifiP2pBonjourServiceResponse resp) {
+            if (resp.getDnsType() == WifiP2pBonjourServiceInfo.DNS_TYPE_PTR) {
+                if (mBonjourServRspListener != null) {
+                    mBonjourServRspListener.onBonjourServiceAvailable(
+                            resp.getInstanceName(),
+                            resp.getDnsQueryName(),
+                            resp.getSrcDevice());
+                }
+            } else if (resp.getDnsType() == WifiP2pBonjourServiceInfo.DNS_TYPE_TXT) {
+                if (mBonjourTxtListener != null) {
+                    mBonjourTxtListener.onBonjourTxtRecordAvailable(
+                            resp.getDnsQueryName(),
+                            resp.getTxtRecord(),
+                            resp.getSrcDevice());
+                }
+            } else {
+                Log.e(TAG, "Unhandled resp " + resp);
+            }
+        }
+
+        private int putListener(Object listener) {
             if (listener == null) return INVALID_LISTENER_KEY;
             int key;
             synchronized (mListenerMapLock) {
@@ -462,7 +683,7 @@
             return key;
         }
 
-        Object getListener(int key) {
+        private Object getListener(int key) {
             if (key == INVALID_LISTENER_KEY) return null;
             synchronized (mListenerMapLock) {
                 return mListenerMap.remove(key);
@@ -470,6 +691,18 @@
         }
     }
 
+    private static void checkChannel(Channel c) {
+        if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+    }
+
+    private static void checkServiceInfo(WifiP2pServiceInfo info) {
+        if (info == null) throw new IllegalArgumentException("service info is null");
+    }
+
+    private static void checkServiceRequest(WifiP2pServiceRequest req) {
+        if (req == null) throw new IllegalArgumentException("service request is null");
+    }
+
     /**
      * Registers the application with the Wi-Fi framework. This function
      * must be the first to be called before any p2p operations are performed.
@@ -512,7 +745,7 @@
      * @param listener for callbacks on success or failure. Can be null.
      */
     public void discoverPeers(Channel c, ActionListener listener) {
-        if (c == null) return;
+        checkChannel(c);
         c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener));
     }
 
@@ -522,7 +755,7 @@
      * @hide
      */
     public void stopPeerDiscovery(Channel c, ActionListener listener) {
-        if (c == null) return;
+        checkChannel(c);
         c.mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, c.putListener(listener));
     }
 
@@ -549,7 +782,7 @@
      * @param listener for callbacks on success or failure. Can be null.
      */
     public void connect(Channel c, WifiP2pConfig config, ActionListener listener) {
-        if (c == null) return;
+        checkChannel(c);
         c.mAsyncChannel.sendMessage(CONNECT, 0, c.putListener(listener), config);
     }
 
@@ -565,7 +798,7 @@
      * @param listener for callbacks on success or failure. Can be null.
      */
     public void cancelConnect(Channel c, ActionListener listener) {
-        if (c == null) return;
+        checkChannel(c);
         c.mAsyncChannel.sendMessage(CANCEL_CONNECT, 0, c.putListener(listener));
     }
 
@@ -589,7 +822,7 @@
      * @param listener for callbacks on success or failure. Can be null.
      */
     public void createGroup(Channel c, ActionListener listener) {
-        if (c == null) return;
+        checkChannel(c);
         c.mAsyncChannel.sendMessage(CREATE_GROUP, 0, c.putListener(listener));
     }
 
@@ -605,18 +838,225 @@
      * @param listener for callbacks on success or failure. Can be null.
      */
     public void removeGroup(Channel c, ActionListener listener) {
-        if (c == null) return;
+        checkChannel(c);
         c.mAsyncChannel.sendMessage(REMOVE_GROUP, 0, c.putListener(listener));
     }
 
     /**
+     * Register a local service of service discovery.
+     *
+     * <p> The function call immediately returns after sending a request to add a local
+     * service to the framework. The application is notified of a success or failure to
+     * add service through listener callbacks {@link ActionListener#onSuccess} or
+     * {@link ActionListener#onFailure}.
+     *
+     * <p>The service information is set through the subclass of {@link WifiP2pServiceInfo}.<br>
+     * e.g )  {@link WifiP2pUpnpServiceInfo#newInstance} or
+     *  {@link WifiP2pBonjourServiceInfo#newInstance}
+     *
+     * <p>If a local service is added, the framework responds the appropriate service discovery
+     *  request automatically.
+     *
+     * <p>These service information will be clear when p2p is disabled or call
+     *  {@link #removeLocalService} or {@link #clearLocalServices}.
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param servInfo is a local service information.
+     * @param listener for callbacks on success or failure. Can be null.
+     * @hide
+     */
+    public void addLocalService(Channel c, WifiP2pServiceInfo servInfo, ActionListener listener) {
+        checkChannel(c);
+        checkServiceInfo(servInfo);
+        c.mAsyncChannel.sendMessage(ADD_LOCAL_SERVICE, 0, c.putListener(listener), servInfo);
+    }
+
+    /**
+     * Unregister a specified local service of service discovery.
+     *
+     * <p> The function call immediately returns after sending a request to remove a
+     * local service to the framework. The application is notified of a success or failure to
+     * add service through listener callbacks {@link ActionListener#onSuccess} or
+     * {@link ActionListener#onFailure}.
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param servInfo is the local service information.
+     * @param listener for callbacks on success or failure. Can be null.
+     * @hide
+     */
+    public void removeLocalService(Channel c, WifiP2pServiceInfo servInfo,
+            ActionListener listener) {
+        checkChannel(c);
+        checkServiceInfo(servInfo);
+        c.mAsyncChannel.sendMessage(REMOVE_LOCAL_SERVICE, 0, c.putListener(listener), servInfo);
+    }
+
+    /**
+     * Clear all registered local services of service discovery.
+     *
+     * <p> The function call immediately returns after sending a request to clear all
+     * local services to the framework. The application is notified of a success or failure to
+     * add service through listener callbacks {@link ActionListener#onSuccess} or
+     * {@link ActionListener#onFailure}.
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param listener for callbacks on success or failure. Can be null.
+     * @hide
+     */
+    public void clearLocalServices(Channel c, ActionListener listener) {
+        checkChannel(c);
+        c.mAsyncChannel.sendMessage(CLEAR_LOCAL_SERVICES, 0, c.putListener(listener));
+    }
+
+    /**
+     * Register a callback to be invoked on receiving service discovery response.
+     *
+     * <p> see {@link #discoverServices} for the detail.
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param listener for callbacks on receiving service discovery response.
+     * @hide
+     */
+    public void setServiceResponseListener(Channel c,
+            ServiceResponseListener listener) {
+        checkChannel(c);
+        c.mServRspListener = listener;
+    }
+
+    /**
+     * Register a callback to be invoked on receiving Bonjour service discovery
+     * response.
+     *
+     * <p> see {@link #discoverServices} for the detail.
+     *
+     * @param c
+     * @param servlistener is for listening to a Bonjour service response
+     * @param txtListener is for listening to a Bonjour TXT record
+     * @hide
+     */
+    public void setBonjourResponseListeners(Channel c,
+            BonjourServiceResponseListener servListener, BonjourTxtRecordListener txtListener) {
+        checkChannel(c);
+        c.mBonjourServRspListener = servListener;
+        c.mBonjourTxtListener = txtListener;
+    }
+
+    /**
+     * Register a callback to be invoked on receiving upnp service discovery
+     * response.
+     *
+     * <p> see {@link #discoverServices} for the detail.
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param listener for callbacks on receiving service discovery response.
+     * @hide
+     */
+    public void setUpnpServiceResponseListener(Channel c,
+            UpnpServiceResponseListener listener) {
+        checkChannel(c);
+        c.mUpnpServRspListener = listener;
+    }
+
+    /**
+     * Initiate service discovery. A discovery process involves scanning for
+     * requested services for the purpose of establishing a connection to a peer
+     * that supports an available service.
+     *
+     * <p> The function call immediately returns after sending a request to start service
+     * discovery to the framework. The application is notified of a success or failure to initiate
+     * discovery through listener callbacks {@link ActionListener#onSuccess} or
+     * {@link ActionListener#onFailure}.
+     *
+     * <p> The services to be discovered are specified with calls to {@link #addServiceRequest}.
+     *
+     * <p>The application is notified of the response against the service discovery request
+     * through listener callbacks registered by {@link #setServiceResponseListener} or
+     * {@link #setBonjourServiceResponseListener}, or {@link #setUpnpServiceResponseListener}.
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param listener for callbacks on success or failure. Can be null.
+     * @hide
+     */
+    public void discoverServices(Channel c, ActionListener listener) {
+        checkChannel(c);
+        c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, c.putListener(listener));
+    }
+
+    /**
+     * Add a service discovery request.
+     *
+     * <p> The function call immediately returns after sending a request to add service
+     * discovery request to the framework. The application is notified of a success or failure to
+     * add service through listener callbacks {@link ActionListener#onSuccess} or
+     * {@link ActionListener#onFailure}.
+     *
+     * <p>After service discovery request is added, you can initiate service discovery by
+     * {@link #discoverServices}.
+     *
+     * <p>These information will be clear when wifi p2p is disabled or
+     * {@link #removeServiceRequest(Channel, WifiP2pServiceRequest, ActionListener)} or
+     * {@link #clearServiceRequests(Channel, ActionListener)} is called.
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param req is the service discovery request.
+     * @param listener for callbacks on success or failure. Can be null.
+     * @hide
+     */
+    public void addServiceRequest(Channel c,
+            WifiP2pServiceRequest req, ActionListener listener) {
+        checkChannel(c);
+        checkServiceRequest(req);
+        c.mAsyncChannel.sendMessage(ADD_SERVICE_REQUEST, 0,
+                c.putListener(listener), req);
+    }
+
+    /**
+     * Remove a specified service discovery request.
+     *
+     * <p> The function call immediately returns after sending a request to remove service
+     * discovery request to the framework. The application is notified of a success or failure to
+     * add service through listener callbacks {@link ActionListener#onSuccess} or
+     * {@link ActionListener#onFailure}.
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param req is the service discovery request.
+     * @param listener for callbacks on success or failure. Can be null.
+     * @hide
+     */
+    public void removeServiceRequest(Channel c, WifiP2pServiceRequest req,
+            ActionListener listener) {
+        checkChannel(c);
+        checkServiceRequest(req);
+        c.mAsyncChannel.sendMessage(REMOVE_SERVICE_REQUEST, 0,
+                c.putListener(listener), req);
+    }
+
+    /**
+     * Clear all registered service discovery requests.
+     *
+     * <p> The function call immediately returns after sending a request to clear all
+     * service discovery requests to the framework. The application is notified of a success
+     * or failure to add service through listener callbacks {@link ActionListener#onSuccess} or
+     * {@link ActionListener#onFailure}.
+     *
+     * @param c is the channel created at {@link #initialize}
+     * @param listener for callbacks on success or failure. Can be null.
+     * @hide
+     */
+    public void clearServiceRequests(Channel c, ActionListener listener) {
+        checkChannel(c);
+        c.mAsyncChannel.sendMessage(CLEAR_SERVICE_REQUESTS,
+                0, c.putListener(listener));
+    }
+
+    /**
      * Request the current list of peers.
      *
      * @param c is the channel created at {@link #initialize}
      * @param listener for callback when peer list is available. Can be null.
      */
     public void requestPeers(Channel c, PeerListListener listener) {
-        if (c == null) return;
+        checkChannel(c);
         c.mAsyncChannel.sendMessage(REQUEST_PEERS, 0, c.putListener(listener));
     }
 
@@ -627,7 +1067,7 @@
      * @param listener for callback when connection info is available. Can be null.
      */
     public void requestConnectionInfo(Channel c, ConnectionInfoListener listener) {
-        if (c == null) return;
+        checkChannel(c);
         c.mAsyncChannel.sendMessage(REQUEST_CONNECTION_INFO, 0, c.putListener(listener));
     }
 
@@ -638,7 +1078,7 @@
      * @param listener for callback when group info is available. Can be null.
      */
     public void requestGroupInfo(Channel c, GroupInfoListener listener) {
-        if (c == null) return;
+        checkChannel(c);
         c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO, 0, c.putListener(listener));
     }
 
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 32e1053..314e33e 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -42,6 +42,9 @@
 import android.net.wifi.WifiNative;
 import android.net.wifi.WifiStateMachine;
 import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -55,6 +58,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -71,7 +75,10 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
 
 /**
  * WifiP2pService inclues a state machine to perform Wi-Fi p2p operations. Applications
@@ -150,6 +157,16 @@
 
     private NetworkInfo mNetworkInfo;
 
+    /* The transaction Id of service discovery request */
+    private byte mServiceTransactionId = 0;
+
+    /* Service discovery request ID of wpa_supplicant.
+     * null means it's not set yet. */
+    private String mServiceDiscReqId;
+
+    /* clients(application) information list. */
+    private HashMap<Messenger, ClientInfo> mClientInfoList = new HashMap<Messenger, ClientInfo>();
+
     /* Is chosen as a unique range to avoid conflict with
        the range defined in Tethering.java */
     private static final String[] DHCP_RANGE = {"192.168.49.2", "192.168.49.254"};
@@ -313,6 +330,10 @@
                     replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
                             WifiP2pManager.BUSY);
                     break;
+                case WifiP2pManager.DISCOVER_SERVICES:
+                    replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+                            WifiP2pManager.BUSY);
+                    break;
                 case WifiP2pManager.CONNECT:
                     replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
                             WifiP2pManager.BUSY);
@@ -329,6 +350,32 @@
                     replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
                             WifiP2pManager.BUSY);
                     break;
+                case WifiP2pManager.ADD_LOCAL_SERVICE:
+                    replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+                            WifiP2pManager.BUSY);
+                    break;
+                case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+                    replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+                            WifiP2pManager.BUSY);
+                    break;
+                case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+                    replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+                            WifiP2pManager.BUSY);
+                    break;
+                case WifiP2pManager.ADD_SERVICE_REQUEST:
+                    replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+                            WifiP2pManager.BUSY);
+                    break;
+                case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+                    replyToMessage(message,
+                            WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+                            WifiP2pManager.BUSY);
+                    break;
+                case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+                    replyToMessage(message,
+                            WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+                            WifiP2pManager.BUSY);
+                    break;
                 case WifiP2pManager.REQUEST_PEERS:
                     replyToMessage(message, WifiP2pManager.RESPONSE_PEERS, mPeers);
                     break;
@@ -381,6 +428,10 @@
                     replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
                             WifiP2pManager.P2P_UNSUPPORTED);
                     break;
+                case WifiP2pManager.DISCOVER_SERVICES:
+                    replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+                            WifiP2pManager.P2P_UNSUPPORTED);
+                    break;
                 case WifiP2pManager.CONNECT:
                     replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
                             WifiP2pManager.P2P_UNSUPPORTED);
@@ -397,6 +448,32 @@
                     replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
                             WifiP2pManager.P2P_UNSUPPORTED);
                     break;
+                case WifiP2pManager.ADD_LOCAL_SERVICE:
+                    replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+                            WifiP2pManager.P2P_UNSUPPORTED);
+                    break;
+                case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+                    replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+                            WifiP2pManager.P2P_UNSUPPORTED);
+                    break;
+                case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+                    replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+                            WifiP2pManager.P2P_UNSUPPORTED);
+                    break;
+                case WifiP2pManager.ADD_SERVICE_REQUEST:
+                    replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+                            WifiP2pManager.P2P_UNSUPPORTED);
+                    break;
+                case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+                    replyToMessage(message,
+                            WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+                            WifiP2pManager.P2P_UNSUPPORTED);
+                    break;
+                case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+                    replyToMessage(message,
+                            WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+                            WifiP2pManager.P2P_UNSUPPORTED);
+                    break;
                default:
                     return NOT_HANDLED;
             }
@@ -507,6 +584,8 @@
                     transitionTo(mP2pDisablingState);
                     break;
                 case WifiP2pManager.DISCOVER_PEERS:
+                    // do not send service discovery request while normal find operation.
+                    clearSupplicantServiceRequest();
                     if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
                         replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
                         sendP2pDiscoveryChangedBroadcast(true);
@@ -526,6 +605,20 @@
                                 WifiP2pManager.ERROR);
                     }
                     break;
+                case WifiP2pManager.DISCOVER_SERVICES:
+                    if (DBG) logd(getName() + " discover services");
+                    if (!updateSupplicantServiceRequest()) {
+                        replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+                                WifiP2pManager.NO_SERVICE_REQUESTS);
+                        break;
+                    }
+                    if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+                        replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED);
+                    } else {
+                        replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+                                WifiP2pManager.ERROR);
+                    }
+                    break;
                 case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
                     WifiP2pDevice device = (WifiP2pDevice) message.obj;
                     if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
@@ -536,7 +629,55 @@
                     device = (WifiP2pDevice) message.obj;
                     if (mPeers.remove(device)) sendP2pPeersChangedBroadcast();
                     break;
-               default:
+               case WifiP2pManager.ADD_LOCAL_SERVICE:
+                    if (DBG) logd(getName() + " add service");
+                    WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)message.obj;
+                    if (addLocalService(message.replyTo, servInfo)) {
+                        replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED);
+                    } else {
+                        replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED);
+                    }
+                    break;
+                case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+                    if (DBG) logd(getName() + " remove service");
+                    servInfo = (WifiP2pServiceInfo)message.obj;
+                    removeLocalService(message.replyTo, servInfo);
+                    replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED);
+                    break;
+                case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+                    if (DBG) logd(getName() + " clear service");
+                    clearLocalServices(message.replyTo);
+                    break;
+                case WifiP2pManager.ADD_SERVICE_REQUEST:
+                    if (DBG) logd(getName() + " add service request");
+                    if (!addServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj)) {
+                        replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED);
+                        break;
+                    }
+                    replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED);
+                    break;
+                case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+                    if (DBG) logd(getName() + " remove service request");
+                    removeServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj);
+                    replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED);
+                    break;
+                case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+                    if (DBG) logd(getName() + " clear service request");
+                    clearServiceRequests(message.replyTo);
+                    replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED);
+                    break;
+               case WifiMonitor.P2P_SERV_DISC_RESP_EVENT:
+                    if (DBG) logd(getName() + " receive service response");
+                    List<WifiP2pServiceResponse> sdRespList =
+                        (List<WifiP2pServiceResponse>) message.obj;
+                    for (WifiP2pServiceResponse resp : sdRespList) {
+                        WifiP2pDevice dev =
+                            mPeers.get(resp.getSrcDevice().deviceAddress);
+                        resp.setSrcDevice(dev);
+                        sendServiceResponse(resp);
+                    }
+                    break;
+                default:
                     return NOT_HANDLED;
             }
             return HANDLED;
@@ -1286,6 +1427,12 @@
         mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress();
         updateThisDevice(WifiP2pDevice.AVAILABLE);
         if (DBG) Slog.d(TAG, "DeviceAddress: " + mThisDevice.deviceAddress);
+
+        mClientInfoList.clear();
+        mWifiNative.p2pFlush();
+        mWifiNative.p2pServiceFlush();
+        mServiceTransactionId = 0;
+        mServiceDiscReqId = null;
     }
 
     private void updateThisDevice(int status) {
@@ -1342,5 +1489,262 @@
         Slog.e(TAG, s);
     }
 
+    /**
+     * Update service discovery request to wpa_supplicant.
+     */
+    private boolean updateSupplicantServiceRequest() {
+        clearSupplicantServiceRequest();
+
+        StringBuffer sb = new StringBuffer();
+        for (ClientInfo c: mClientInfoList.values()) {
+            int key;
+            WifiP2pServiceRequest req;
+            for (int i=0; i < c.mReqList.size(); i++) {
+                key = c.mReqList.keyAt(i);
+                req = c.mReqList.get(key);
+                if (req != null) {
+                    sb.append(req.getSupplicantQuery());
+                }
+            }
+        }
+
+        if (sb.length() == 0) {
+            return false;
+        }
+
+        mServiceDiscReqId = mWifiNative.p2pServDiscReq("00:00:00:00:00:00", sb.toString());
+        if (mServiceDiscReqId == null) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Clear service discovery request in wpa_supplicant
+     */
+    private void clearSupplicantServiceRequest() {
+        if (mServiceDiscReqId == null) return;
+
+        mWifiNative.p2pServDiscCancelReq(mServiceDiscReqId);
+        mServiceDiscReqId = null;
+    }
+
+    /* TODO: We could track individual service adds seperately and avoid
+     * having to do update all service requests on every new request
+     */
+    private boolean addServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+        clearClientDeadChannels();
+        ClientInfo clientInfo = getClientInfo(m, true);
+        if (clientInfo == null) {
+            return false;
+        }
+
+        req.setTransactionId(++mServiceTransactionId);
+        clientInfo.mReqList.put(mServiceTransactionId, req);
+
+        if (mServiceDiscReqId == null) {
+            return true;
+        }
+
+        return updateSupplicantServiceRequest();
+    }
+
+    private void removeServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+
+        ClientInfo clientInfo = getClientInfo(m, false);
+        if (clientInfo == null) {
+            return;
+        }
+
+        clientInfo.mReqList.remove(req.getTransactionId());
+
+        if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+            if (DBG) logd("remove client information from framework");
+            mClientInfoList.remove(clientInfo.mMessenger);
+        }
+
+        if (mServiceDiscReqId == null) {
+            return;
+        }
+
+        updateSupplicantServiceRequest();
+    }
+
+    private void clearServiceRequests(Messenger m) {
+
+        ClientInfo clientInfo = getClientInfo(m, false);
+        if (clientInfo == null) {
+            return;
+        }
+
+        if (clientInfo.mReqList.size() == 0) {
+            return;
+        }
+
+        clientInfo.mReqList.clear();
+
+        if (clientInfo.mServList.size() == 0) {
+            if (DBG) logd("remove channel information from framework");
+            mClientInfoList.remove(clientInfo.mMessenger);
+        }
+
+        if (mServiceDiscReqId == null) {
+            return;
+        }
+
+        updateSupplicantServiceRequest();
+    }
+
+    private boolean addLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+        clearClientDeadChannels();
+        ClientInfo clientInfo = getClientInfo(m, true);
+        if (clientInfo == null) {
+            return false;
+        }
+
+        if (!clientInfo.mServList.add(servInfo)) {
+            return false;
+        }
+
+        if (!mWifiNative.p2pServiceAdd(servInfo)) {
+            clientInfo.mServList.remove(servInfo);
+            return false;
+        }
+
+        return true;
+    }
+
+    private void removeLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+        ClientInfo clientInfo = getClientInfo(m, false);
+        if (clientInfo == null) {
+            return;
+        }
+
+        mWifiNative.p2pServiceDel(servInfo);
+
+        clientInfo.mServList.remove(servInfo);
+        if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+            if (DBG) logd("remove client information from framework");
+            mClientInfoList.remove(clientInfo.mMessenger);
+        }
+    }
+
+    private void clearLocalServices(Messenger m) {
+        ClientInfo clientInfo = getClientInfo(m, false);
+        if (clientInfo == null) {
+            return;
+        }
+
+        for (WifiP2pServiceInfo servInfo: clientInfo.mServList) {
+            mWifiNative.p2pServiceDel(servInfo);
+        }
+
+        clientInfo.mServList.clear();
+        if (clientInfo.mReqList.size() == 0) {
+            if (DBG) logd("remove client information from framework");
+            mClientInfoList.remove(clientInfo.mMessenger);
+        }
+    }
+
+    private void clearClientInfo(Messenger m) {
+        clearLocalServices(m);
+        clearServiceRequests(m);
+    }
+
+    /**
+     * Send the service response to the WifiP2pManager.Channel.
+     *
+     * @param resp
+     */
+    private void sendServiceResponse(WifiP2pServiceResponse resp) {
+        for (ClientInfo c : mClientInfoList.values()) {
+            WifiP2pServiceRequest req = c.mReqList.get(resp.getTransactionId());
+            if (req != null) {
+                Message msg = Message.obtain();
+                msg.what = WifiP2pManager.RESPONSE_SERVICE;
+                msg.arg1 = 0;
+                msg.arg2 = 0;
+                msg.obj = resp;
+                try {
+                    c.mMessenger.send(msg);
+                } catch (RemoteException e) {
+                    if (DBG) logd("detect dead channel");
+                    clearClientInfo(c.mMessenger);
+                }
+            }
+        }
+    }
+
+    /**
+     * We dont get notifications of clients that have gone away.
+     * We detect this actively when services are added and throw
+     * them away.
+     *
+     * TODO: This can be done better with full async channels.
+     */
+    private void clearClientDeadChannels() {
+        for (ClientInfo c : mClientInfoList.values()) {
+            Message msg = Message.obtain();
+            msg.what = WifiP2pManager.PING;
+            msg.arg1 = 0;
+            msg.arg2 = 0;
+            msg.obj = null;
+            try {
+                c.mMessenger.send(msg);
+            } catch (RemoteException e) {
+                if (DBG) logd("detect dead channel");
+                clearClientInfo(c.mMessenger);
+            }
+        }
+    }
+
+    /**
+     * Return the specified ClientInfo.
+     * @param m Messenger
+     * @param createIfNotExist if true and the specified channel info does not exist,
+     * create new client info.
+     * @return the specified ClientInfo.
+     */
+    private ClientInfo getClientInfo(Messenger m, boolean createIfNotExist) {
+        ClientInfo clientInfo = mClientInfoList.get(m);
+
+        if (clientInfo == null && createIfNotExist) {
+            if (DBG) logd("add a new client");
+            clientInfo = new ClientInfo(m);
+            mClientInfoList.put(m, clientInfo);
+        }
+
+        return clientInfo;
+    }
+
+    }
+
+    /**
+     * Information about a particular client and we track the service discovery requests
+     * and the local services registered by the client.
+     */
+    private class ClientInfo {
+
+        /*
+         * A reference to WifiP2pManager.Channel handler.
+         * The response of this request is notified to WifiP2pManager.Channel handler
+         */
+        private Messenger mMessenger;
+
+        /*
+         * A service discovery request list.
+         */
+        private SparseArray<WifiP2pServiceRequest> mReqList;
+
+        /*
+         * A local service information list.
+         */
+        private List<WifiP2pServiceInfo> mServList;
+
+        private ClientInfo(Messenger m) {
+            mMessenger = m;
+            mReqList = new SparseArray();
+            mServList = new ArrayList<WifiP2pServiceInfo>();
+        }
     }
 }
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceInfo.java
new file mode 100644
index 0000000..ed278d5
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceInfo.java
@@ -0,0 +1,223 @@
+/*
+ * 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 android.net.wifi.p2p.nsd;
+
+import android.net.nsd.DnsSdTxtRecord;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class for Bonjour service information.
+ * @hide
+ */
+public class WifiP2pBonjourServiceInfo extends WifiP2pServiceInfo {
+
+    /**
+     * Bonjour version 1.
+     * @hide
+     */
+    public static final int VERSION_1 = 0x01;
+
+    /**
+     * Pointer record.
+     * @hide
+     */
+    public static final int DNS_TYPE_PTR = 12;
+
+    /**
+     * Text record.
+     * @hide
+     */
+    public static final int DNS_TYPE_TXT = 16;
+
+    /**
+     * virtual memory packet.
+     * see E.3 of the Wi-Fi Direct technical specification for the detail.<br>
+     * Key: domain name Value: pointer address.<br>
+     */
+    private final static Map<String, String> sVmPacket;
+
+    static {
+        sVmPacket = new HashMap<String, String>();
+        sVmPacket.put("_tcp.local.", "c00c");
+        sVmPacket.put("local.", "c011");
+        sVmPacket.put("_udp.local.", "c01c");
+    }
+
+    /**
+     * This constructor is only used in newInstance().
+     *
+     * @param queryList
+     */
+    private WifiP2pBonjourServiceInfo(List<String> queryList) {
+        super(queryList);
+    }
+
+    /**
+     * Create Bonjour service information object.
+     *
+     * @param instanceName instance name.<br>
+     *  e.g) "MyPrinter"
+     * @param registrationType registration type.<br>
+     *  e.g) "_ipp._tcp.local."
+     * @param txtRecord text record.
+     * @return Bonjour service information object
+     */
+    public static WifiP2pBonjourServiceInfo newInstance(String instanceName,
+            String registrationType, DnsSdTxtRecord txtRecord) {
+        if (TextUtils.isEmpty(instanceName) || TextUtils.isEmpty(registrationType)) {
+            throw new IllegalArgumentException(
+                    "instance name or registration type cannot be empty");
+        }
+
+        if (txtRecord == null) {
+            txtRecord = new DnsSdTxtRecord();
+        }
+
+        ArrayList<String> queries = new ArrayList<String>();
+        queries.add(createPtrServiceQuery(instanceName, registrationType));
+        queries.add(createTxtServiceQuery(instanceName, registrationType, txtRecord));
+
+        return new WifiP2pBonjourServiceInfo(queries);
+    }
+
+    /**
+     * Create wpa_supplicant service query for PTR record.
+     *
+     * @param instanceName instance name.<br>
+     *  e.g) "MyPrinter"
+     * @param registrationType registration type.<br>
+     *  e.g) "_ipp._tcp.local."
+     * @return wpa_supplicant service query.
+     */
+    private static String createPtrServiceQuery(String instanceName,
+            String registrationType) {
+
+        StringBuffer sb = new StringBuffer();
+        sb.append("bonjour ");
+        sb.append(createRequest(registrationType, DNS_TYPE_PTR, VERSION_1));
+        sb.append(" ");
+
+        byte[] data = instanceName.getBytes();
+        sb.append(String.format("%02x", data.length));
+        sb.append(WifiP2pServiceInfo.bin2HexStr(data));
+        // This is the start point of this response.
+        // Therefore, it indicates the request domain name.
+        sb.append("c027");
+        return sb.toString();
+    }
+
+    /**
+     * Create wpa_supplicant service query for TXT record.
+     *
+     * @param instanceName instance name.<br>
+     *  e.g) "MyPrinter"
+     * @param registrationType registration type.<br>
+     *  e.g) "_ipp._tcp.local."
+     * @param txtRecord TXT record.<br>
+     * @return wpa_supplicant service query.
+     */
+    public static String createTxtServiceQuery(String instanceName,
+            String registrationType,
+            DnsSdTxtRecord txtRecord) {
+
+
+        StringBuffer sb = new StringBuffer();
+        sb.append("bonjour ");
+
+        sb.append(createRequest((instanceName + "." + registrationType),
+                DNS_TYPE_TXT, VERSION_1));
+        sb.append(" ");
+        byte[] rawData = txtRecord.getRawData();
+        if (rawData.length == 0) {
+            sb.append("00");
+        } else {
+            sb.append(bin2HexStr(rawData));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Create bonjour service discovery request.
+     *
+     * @param dnsName dns name
+     * @param dnsType dns type
+     * @param version version number
+     * @hide
+     */
+    static String createRequest(String dnsName, int dnsType, int version) {
+        StringBuffer sb = new StringBuffer();
+
+        /*
+         * The request format is as follows.
+         * ________________________________________________
+         * |  Encoded and Compressed dns name (variable)  |
+         * ________________________________________________
+         * |   Type (2)           | Version (1) |
+         */
+        if (dnsType == WifiP2pBonjourServiceInfo.DNS_TYPE_TXT) {
+            dnsName = dnsName.toLowerCase();
+        }
+        sb.append(compressDnsName(dnsName));
+        sb.append(String.format("%04x", dnsType));
+        sb.append(String.format("%02x", version));
+
+        return sb.toString();
+    }
+
+    /**
+     * Compress DNS data.
+     *
+     * see E.3 of the Wi-Fi Direct technical specification for the detail.
+     *
+     * @param dnsName dns name
+     * @return compressed dns name
+     */
+    private static String compressDnsName(String dnsName) {
+        StringBuffer sb = new StringBuffer();
+
+        // The domain name is replaced with a pointer to a prior
+        // occurrence of the same name in virtual memory packet.
+        while (true) {
+            String data = sVmPacket.get(dnsName);
+            if (data != null) {
+                sb.append(data);
+                break;
+            }
+            int i = dnsName.indexOf('.');
+            if (i == -1) {
+                if (dnsName.length() > 0) {
+                    sb.append(String.format("%02x", dnsName.length()));
+                    sb.append(WifiP2pServiceInfo.bin2HexStr(dnsName.getBytes()));
+                }
+                // for a sequence of labels ending in a zero octet
+                sb.append("00");
+                break;
+            }
+
+            String name = dnsName.substring(0, i);
+            dnsName = dnsName.substring(i + 1);
+            sb.append(String.format("%02x", name.length()));
+            sb.append(WifiP2pServiceInfo.bin2HexStr(name.getBytes()));
+        }
+        return sb.toString();
+    }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceRequest.java
new file mode 100644
index 0000000..d1635f1
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceRequest.java
@@ -0,0 +1,102 @@
+/*
+ * 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 android.net.wifi.p2p.nsd;
+
+
+/**
+ * A class for a request of bonjour service discovery.
+ * @hide
+ */
+public class WifiP2pBonjourServiceRequest extends WifiP2pServiceRequest {
+
+    /**
+     * This constructor is only used in newInstance().
+     *
+     * @param query The part of service specific query.
+     * @hide
+     */
+    private WifiP2pBonjourServiceRequest(String query) {
+        super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, query);
+    }
+
+    /**
+     * This constructor is only used in newInstance().
+     * @hide
+     */
+    private WifiP2pBonjourServiceRequest() {
+        super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, null);
+    }
+
+    private WifiP2pBonjourServiceRequest(String registrationType, int dnsType, int version) {
+        super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, WifiP2pBonjourServiceInfo.createRequest(
+                registrationType,
+                WifiP2pBonjourServiceInfo.DNS_TYPE_PTR,
+                WifiP2pBonjourServiceInfo.VERSION_1));
+    }
+
+    /**
+     * Create a service discovery request to search all Bonjour services.
+     *
+     * @return service request for Bonjour.
+     */
+    public static WifiP2pBonjourServiceRequest newInstance() {
+        return new WifiP2pBonjourServiceRequest();
+    }
+
+    /**
+     * Create a service discovery request to resolve the instance name with the specified
+     * registration type.
+     *
+     * @param registrationType registration type. Cannot be null <br>
+     * e.g) <br>
+     *  "_afpovertcp._tcp.local."(Apple File Sharing over TCP)<br>
+     *  "_ipp._tcp.local." (IP Printing over TCP)<br>
+     * @return service request for Bonjour.
+     */
+    public static WifiP2pBonjourServiceRequest newInstance(String registrationType) {
+        if (registrationType == null) {
+            throw new IllegalArgumentException("registration type cannot be null");
+        }
+        return new WifiP2pBonjourServiceRequest(registrationType,
+                WifiP2pBonjourServiceInfo.DNS_TYPE_PTR,
+                WifiP2pBonjourServiceInfo.VERSION_1);
+    }
+
+    /**
+     * Create a service discovery request to get the TXT data from the specified
+     * service.
+     *
+     * @param instanceName instance name. Cannot be null. <br>
+     *  "MyPrinter"
+     * @param registrationType registration type. Cannot be null. <br>
+     * e.g) <br>
+     *  "_afpovertcp._tcp.local."(Apple File Sharing over TCP)<br>
+     *  "_ipp._tcp.local." (IP Printing over TCP)<br>
+     * @return service request for Bonjour.
+     */
+    public static WifiP2pBonjourServiceRequest newInstance(String instanceName,
+            String registrationType) {
+        if (instanceName == null || registrationType == null) {
+            throw new IllegalArgumentException(
+                    "instance name or registration type cannot be null");
+        }
+        String fullDomainName = instanceName + "." + registrationType;
+        return new WifiP2pBonjourServiceRequest(fullDomainName,
+                WifiP2pBonjourServiceInfo.DNS_TYPE_TXT,
+                WifiP2pBonjourServiceInfo.VERSION_1);
+    }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceResponse.java
new file mode 100644
index 0000000..c511569
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceResponse.java
@@ -0,0 +1,313 @@
+/*
+ * 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 android.net.wifi.p2p.nsd;
+
+import android.net.nsd.DnsSdTxtRecord;
+import android.net.wifi.p2p.WifiP2pDevice;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class for a response of bonjour service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pBonjourServiceResponse extends WifiP2pServiceResponse {
+
+    /**
+     * DNS query name.
+     * e.g)
+     * for PTR
+     * "_ipp._tcp.local."
+     * for TXT
+     * "MyPrinter._ipp._tcp.local."
+     */
+    private String mDnsQueryName;
+
+    /**
+     * Service instance name.
+     * e.g) "MyPrinter"
+     * This field is only used when the dns type equals to
+     * {@link WifiP2pBonjourServiceInfo#DNS_TYPE_PTR}.
+     */
+    private String mInstanceName;
+
+    /**
+     * DNS Type.
+     * Should be {@link WifiP2pBonjourServiceInfo#DNS_TYPE_PTR} or
+     * {@link WifiP2pBonjourServiceInfo#DNS_TYPE_TXT}.
+     */
+    private int mDnsType;
+
+    /**
+     * Bonjour version number.
+     * Should be {@link WifiP2pBonjourServiceInfo#VERSION_1}.
+     */
+    private int mVersion;
+
+    /**
+     * Txt record.
+     * This field is only used when the dns type equals to
+     * {@link WifiP2pBonjourServiceInfo#DNS_TYPE_TXT}.
+     */
+    private DnsSdTxtRecord mTxtRecord;
+
+    /**
+     * Virtual memory packet.
+     * see E.3 of the Wi-Fi Direct technical specification for the detail.<br>
+     * The spec can be obtained from wi-fi.org
+     * Key: pointer Value: domain name.<br>
+     */
+    private final static Map<Integer, String> sVmpack;
+
+    static {
+        sVmpack = new HashMap<Integer, String>();
+        sVmpack.put(0x0c, "_tcp.local.");
+        sVmpack.put(0x11, "local.");
+        sVmpack.put(0x1c, "_udp.local.");
+    }
+
+    /**
+     * Returns query DNS name.
+     * @return DNS name.
+     */
+    public String getDnsQueryName() {
+        return mDnsQueryName;
+    }
+
+    /**
+     * Return query DNS type.
+     * @return DNS type.
+     */
+    public int getDnsType() {
+        return mDnsType;
+    }
+
+    /**
+     * Return bonjour version number.
+     * @return version number.
+     */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Return instance name.
+     * @return
+     */
+    public String getInstanceName() {
+        return mInstanceName;
+    }
+
+    /**
+     * Return TXT record data.
+     * @return TXT record data.
+     */
+    public DnsSdTxtRecord getTxtRecord() {
+        return mTxtRecord;
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sbuf = new StringBuffer();
+        sbuf.append("serviceType:Bonjour(").append(mServiceType).append(")");
+        sbuf.append(" status:").append(Status.toString(mStatus));
+        sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+        sbuf.append(" version:").append(String.format("%02x", mVersion));
+        sbuf.append(" dnsName:").append(mDnsQueryName);
+        if (mTxtRecord != null) {
+            sbuf.append(" TxtRecord:").append(mTxtRecord);
+        }
+        if (mInstanceName != null) {
+            sbuf.append(" InsName:").append(mInstanceName);
+        }
+        return sbuf.toString();
+    }
+
+    /**
+     * This is only used in framework.
+     * @param status status code.
+     * @param dev source device.
+     * @param data RDATA.
+     * @hide
+     */
+    protected WifiP2pBonjourServiceResponse(int status,
+            int tranId, WifiP2pDevice dev, byte[] data) {
+        super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR,
+                status, tranId, dev, data);
+        if (!parse()) {
+            throw new IllegalArgumentException("Malformed bonjour service response");
+        }
+    }
+
+    /**
+     * Parse Bonjour service discovery response.
+     *
+     * @return {@code true} if the operation succeeded
+     */
+    private boolean parse() {
+        /*
+         * The data format from Wi-Fi Direct spec is as follows.
+         * ________________________________________________
+         * |  encoded and compressed dns name (variable)  |
+         * ________________________________________________
+         * |       dnstype(2byte)      |  version(1byte)  |
+         * ________________________________________________
+         * |              RDATA (variable)                |
+         */
+        if (mData == null) {
+            // the empty is OK.
+            return true;
+        }
+
+        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(mData));
+
+        mDnsQueryName = readDnsName(dis);
+        if (mDnsQueryName == null) {
+            return false;
+        }
+
+        try {
+            mDnsType = dis.readUnsignedShort();
+            mVersion = dis.readUnsignedByte();
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+
+        if (mDnsType == WifiP2pBonjourServiceInfo.DNS_TYPE_PTR) {
+            String rData = readDnsName(dis);
+            if (rData == null) {
+                return false;
+            }
+            if (rData.length() <= mDnsQueryName.length()) {
+                return false;
+            }
+
+            mInstanceName = rData.substring(0,
+                    rData.length() - mDnsQueryName.length() -1);
+        } else if (mDnsType == WifiP2pBonjourServiceInfo.DNS_TYPE_TXT) {
+            mTxtRecord = readTxtData(dis);
+            if (mTxtRecord == null) {
+                return false;
+            }
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Read dns name.
+     *
+     * @param dis data input stream.
+     * @return dns name
+     */
+    private String readDnsName(DataInputStream dis) {
+        StringBuffer sb = new StringBuffer();
+
+        // copy virtual memory packet.
+        HashMap<Integer, String> vmpack = new HashMap<Integer, String>(sVmpack);
+        if (mDnsQueryName != null) {
+            vmpack.put(0x27, mDnsQueryName);
+        }
+        try {
+            while (true) {
+                int i = dis.readUnsignedByte();
+                if (i == 0x00) {
+                    return sb.toString();
+                } else if (i == 0xc0) {
+                    // refer to pointer.
+                    String ref = vmpack.get(dis.readUnsignedByte());
+                    if (ref == null) {
+                        //invalid.
+                        return null;
+                    }
+                    sb.append(ref);
+                    return sb.toString();
+                } else {
+                    byte[] data = new byte[i];
+                    dis.readFully(data);
+                    sb.append(new String(data));
+                    sb.append(".");
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Read TXT record data.
+     *
+     * @param dis
+     * @return TXT record data
+     */
+    private DnsSdTxtRecord readTxtData(DataInputStream dis) {
+        DnsSdTxtRecord txtRecord = new DnsSdTxtRecord();
+        try {
+            while (dis.available() > 0) {
+                int len = dis.readUnsignedByte();
+                if (len == 0) {
+                    break;
+                }
+                byte[] data = new byte[len];
+                dis.readFully(data);
+                String[] keyVal = new String(data).split("=");
+                if (keyVal.length != 2) {
+                    return null;
+                }
+                txtRecord.set(keyVal[0], keyVal[1]);
+            }
+            return txtRecord;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Creates Bonjour service response.
+     *  This is only called from WifiP2pServiceResponse
+     *
+     * @param status status code.
+     * @param dev source device.
+     * @param data Bonjour response data.
+     * @return Bonjour service response data.
+     * @hide
+     */
+    static WifiP2pBonjourServiceResponse newInstance(int status,
+            int transId, WifiP2pDevice dev, byte[] data) {
+        if (status != WifiP2pServiceResponse.Status.SUCCESS) {
+            return new WifiP2pBonjourServiceResponse(status,
+                    transId, dev, null);
+        }
+        try {
+            return new WifiP2pBonjourServiceResponse(status,
+                    transId, dev, data);
+        } catch (IllegalArgumentException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
new file mode 100644
index 0000000..cf2cb4a
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.net.wifi.p2p.servicediscovery;
+
+parcelable WifiP2pServiceInfo;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
new file mode 100644
index 0000000..aed5616
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
@@ -0,0 +1,183 @@
+/*
+ * 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 android.net.wifi.p2p.nsd;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The class for service information.
+ *
+ * <p>Currently UPnP and Bonjour are only supported.
+ *
+ * @see WifiP2pUpnpServiceInfo
+ * @see WifiP2pBonjourServiceInfo
+ * @hide
+ */
+public class WifiP2pServiceInfo implements Parcelable {
+
+    /**
+     * All service protocol types.
+     */
+    public static final int SERVICE_TYPE_ALL             = 0;
+
+    /**
+     * Bonjour protocol.
+     */
+    public static final int SERVICE_TYPE_BONJOUR         = 1;
+
+    /**
+     * UPnP protocol.
+     */
+    public static final int SERVICE_TYPE_UPNP            = 2;
+
+    /**
+     * WS-Discovery protocol
+     */
+    public static final int SERVICE_TYPE_WS_DISCOVERY    = 3;
+
+    /**
+     * Vendor Specific protocol
+     */
+    public static final int SERVICE_TYPE_VENDOR_SPECIFIC = 255;
+
+    /**
+     * the list of query string for wpa_supplicant
+     *
+     * e.g)
+     * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
+     * {"bonjour", "045f697070c00c000c01", "094d795072696e746572c027"
+     *
+     * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
+     * {"bonjour", "096d797072696e746572045f697070c00c001001",
+     *  "09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074"}
+     *
+     * [UPnP]
+     * # UPnP uuid
+     * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012"}
+     *
+     * # UPnP rootdevice
+     * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"}
+     *
+     * # UPnP device
+     * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
+     * -org:device:InternetGatewayDevice:1"}
+     *
+     *  # UPnP service
+     * {"upnp", "10", "uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
+     * -org:service:ContentDirectory:2"}
+     */
+    private List<String> mQueryList;
+
+    /**
+     * This is only used in subclass.
+     *
+     * @param queryList query string for wpa_supplicant
+     * @hide
+     */
+    protected WifiP2pServiceInfo(List<String> queryList) {
+        if (queryList == null) {
+            throw new IllegalArgumentException("query list cannot be null");
+        }
+        mQueryList = queryList;
+    }
+
+   /**
+    * Return the list of the query string for wpa_supplicant.
+    *
+    * @return the list of the query string for wpa_supplicant.
+    * @hide
+    */
+   public List<String> getSupplicantQueryList() {
+       return mQueryList;
+   }
+
+   /**
+    * Converts byte array to hex string.
+    *
+    * @param data
+    * @return hex string.
+    * @hide
+    */
+   static String bin2HexStr(byte[] data) {
+       StringBuffer sb = new StringBuffer();
+
+       for (byte b: data) {
+           String s = null;
+           try {
+               s = Integer.toHexString(b & 0xff);
+           } catch (Exception e) {
+               e.printStackTrace();
+               return null;
+           }
+           //add 0 padding
+           if (s.length() == 1) {
+               sb.append('0');
+           }
+           sb.append(s);
+       }
+       return sb.toString();
+   }
+
+   @Override
+   public boolean equals(Object o) {
+       if (o == this) {
+           return true;
+       }
+       if (!(o instanceof WifiP2pServiceInfo)) {
+           return false;
+       }
+
+       WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)o;
+       return  mQueryList.equals(servInfo.mQueryList);
+   }
+
+   @Override
+   public int hashCode() {
+       int result = 17;
+       result = 31 * result + (mQueryList == null ? 0 : mQueryList.hashCode());
+       return result;
+   }
+
+    /** Implement the Parcelable interface {@hide} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStringList(mQueryList);
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public static final Creator<WifiP2pServiceInfo> CREATOR =
+        new Creator<WifiP2pServiceInfo>() {
+            public WifiP2pServiceInfo createFromParcel(Parcel in) {
+
+                List<String> data = new ArrayList<String>();
+                in.readStringList(data);
+                return new WifiP2pServiceInfo(data);
+            }
+
+            public WifiP2pServiceInfo[] newArray(int size) {
+                return new WifiP2pServiceInfo[size];
+            }
+        };
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
new file mode 100644
index 0000000..d5a1e8f
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.net.wifi.p2p.servicediscovery;
+
+parcelable WifiP2pServiceRequest;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
new file mode 100644
index 0000000..e41d9aa
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
@@ -0,0 +1,283 @@
+/*
+ * 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 android.net.wifi.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class for a request of service discovery.
+ *
+ * <p>This class is used when you create customized service discovery request.
+ * e.g) vendor specific request/ws discovery etc.
+ *
+ * <p>If you want to create UPnP or Bonjour service request, then you had better
+ * use {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pBonjourServiceRequest}.
+ *
+ * @see WifiP2pUpnpServiceRequest
+ * @see WifiP2pBonjourServiceRequest
+ * @hide
+ */
+public class WifiP2pServiceRequest implements Parcelable {
+
+    /**
+     * Service type. It's defined in table63 in Wi-Fi Direct specification.
+     */
+    private int mServiceType;
+
+    /**
+     * The length of the service request TLV.
+     * The value is equal to 2 plus the number of octets in the
+     * query data field.
+     */
+    private int mLength;
+
+    /**
+     * Service transaction ID.
+     * This is a nonzero value used to match the service request/response TLVs.
+     */
+    private int mTransId;
+
+    /**
+     * The hex dump string of query data for the requested service information.
+     *
+     * e.g) Bonjour apple file sharing over tcp (dns name=_afpovertcp._tcp.local.)
+     * 0b5f6166706f766572746370c00c000c01
+     */
+    private String mQuery;
+
+    /**
+     * This constructor is only used in newInstance().
+     *
+     * @param serviceType service discovery type.
+     * @param query The part of service specific query.
+     * @hide
+     */
+    protected WifiP2pServiceRequest(int serviceType, String query) {
+        validateQuery(query);
+
+        mServiceType = serviceType;
+        mQuery = query;
+        if (query != null) {
+            mLength = query.length()/2 + 2;
+        } else {
+            mLength = 2;
+        }
+    }
+
+    /**
+     * This constructor is only used in Parcelable.
+     *
+     * @param serviceType service discovery type.
+     * @param length the length of service discovery packet.
+     * @param transId the transaction id
+     * @param query The part of service specific query.
+     */
+    private WifiP2pServiceRequest(int serviceType, int length,
+            int transId, String query) {
+        mServiceType = serviceType;
+        mLength = length;
+        mTransId = transId;
+        mQuery = query;
+    }
+
+    /**
+     * Return transaction id.
+     *
+     * @return transaction id
+     * @hide
+     */
+    public int getTransactionId() {
+        return mTransId;
+    }
+
+    /**
+     * Set transaction id.
+     *
+     * @param id
+     * @hide
+     */
+    public void setTransactionId(int id) {
+        mTransId = id;
+    }
+
+    /**
+     * Return wpa_supplicant request string.
+     *
+     * The format is the hex dump of the following frame.
+     * <pre>
+     * _______________________________________________________________
+     * |        Length (2)        |   Type (1)   | Transaction ID (1) |
+     * |                  Query Data (variable)                       |
+     * </pre>
+     *
+     * @return wpa_supplicant request string.
+     * @hide
+     */
+    public String getSupplicantQuery() {
+        StringBuffer sb = new StringBuffer();
+        // length is retained as little endian format.
+        sb.append(String.format("%02x", (mLength) & 0xff));
+        sb.append(String.format("%02x", (mLength >> 8) & 0xff));
+        sb.append(String.format("%02x", mServiceType));
+        sb.append(String.format("%02x", mTransId));
+        if (mQuery != null) {
+            sb.append(mQuery);
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Validate query.
+     *
+     * <p>If invalid, throw IllegalArgumentException.
+     * @param query The part of service specific query.
+     */
+    private void validateQuery(String query) {
+        if (query == null) {
+            return;
+        }
+
+        int UNSIGNED_SHORT_MAX = 0xffff;
+        if (query.length()%2 == 1) {
+            throw new IllegalArgumentException(
+                    "query size is invalid. query=" + query);
+        }
+        if (query.length()/2 > UNSIGNED_SHORT_MAX) {
+            throw new IllegalArgumentException(
+                    "query size is too large. len=" + query.length());
+        }
+
+        // check whether query is hex string.
+        query = query.toLowerCase();
+        char[] chars = query.toCharArray();
+        for (char c: chars) {
+            if (!((c >= '0' && c <= '9') ||
+                    (c >= 'a' && c <= 'f'))){
+                throw new IllegalArgumentException(
+                        "query should be hex string. query=" + query);
+            }
+        }
+    }
+
+    /**
+     * Create service discovery request.
+     *
+     * <p>The created instance is set to framework by
+     * {@link WifiP2pManager#addLocalService}.
+     *
+     * @param serviceType service type.<br>
+     * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL},
+     *  {@link WifiP2pServiceInfo#SERVICE_TYPE_WS_DISCOVERY},
+     * {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
+     * If you want to use UPnP or Bonjour, you create  the request by
+     * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pBonjourServiceRequest}
+     *
+     * @param query hex string. if null, all specified services are requested.
+     * @return service discovery request.
+     */
+    public static WifiP2pServiceRequest newInstance(int serviceType, String query) {
+        return new WifiP2pServiceRequest(serviceType, query);
+    }
+
+    /**
+     * Create all service discovery request.
+     *
+     * <p>The created instance is set to framework by
+     * {@link WifiP2pManager#addLocalService}.
+     *
+     * @param serviceType service type.<br>
+     * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL},
+     *  {@link WifiP2pServiceInfo#SERVICE_TYPE_WS_DISCOVERY},
+     * {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
+     * If you want to use UPnP or Bonjour, you create  the request by
+     * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pBonjourServiceRequest}
+     *
+     * @return service discovery request.
+     */
+    public static WifiP2pServiceRequest newInstance(int serviceType) {
+        return new WifiP2pServiceRequest(serviceType, null);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (!(o instanceof WifiP2pServiceRequest)) {
+            return false;
+        }
+
+        WifiP2pServiceRequest req = (WifiP2pServiceRequest)o;
+
+        /*
+         * Not compare transaction id.
+         * Transaction id may be changed on each service discovery operation.
+         */
+        if ((req.mServiceType != mServiceType) ||
+                (req.mLength != mLength)) {
+            return false;
+        }
+
+        if (req.mQuery == null && mQuery == null) {
+            return true;
+        } else if (req.mQuery != null) {
+            return req.mQuery.equals(mQuery);
+        }
+        return false;
+   }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + mServiceType;
+        result = 31 * result + mLength;
+        result = 31 * result + (mQuery == null ? 0 : mQuery.hashCode());
+        return result;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mServiceType);
+        dest.writeInt(mLength);
+        dest.writeInt(mTransId);
+        dest.writeString(mQuery);
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public static final Creator<WifiP2pServiceRequest> CREATOR =
+        new Creator<WifiP2pServiceRequest>() {
+            public WifiP2pServiceRequest createFromParcel(Parcel in) {
+                int servType = in.readInt();
+                int length = in.readInt();
+                int transId = in.readInt();
+                String query = in.readString();
+                return new WifiP2pServiceRequest(servType, length, transId, query);
+            }
+
+            public WifiP2pServiceRequest[] newArray(int size) {
+                return new WifiP2pServiceRequest[size];
+            }
+        };
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl
new file mode 100644
index 0000000..c81d1f9
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.net.wifi.p2p.servicediscovery;
+
+parcelable WifiP2pServiceResponse;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java
new file mode 100644
index 0000000..0855eae
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java
@@ -0,0 +1,389 @@
+/*
+ * 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 android.net.wifi.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The class for a response of service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pServiceResponse implements Parcelable {
+
+    private static int MAX_BUF_SIZE = 1024;
+
+    /**
+     * Service type. It's defined in table63 in Wi-Fi Direct specification.
+     */
+    protected int mServiceType;
+
+    /**
+     * Status code of service discovery response.
+     * It's defined in table65 in Wi-Fi Direct specification.
+     * @see Status
+     */
+    protected int mStatus;
+
+    /**
+     * Service transaction ID.
+     * This is a nonzero value used to match the service request/response TLVs.
+     */
+    protected int mTransId;
+
+    /**
+     * Source device.
+     */
+    protected WifiP2pDevice mDevice;
+
+    /**
+     * Service discovery response data based on the requested on
+     * the service protocol type. The protocol format depends on the service type.
+     */
+    protected byte[] mData;
+
+
+    /**
+     * The status code of service discovery response.
+     * Currently 4 status codes are defined and the status codes from  4 to 255
+     * are reserved.
+     *
+     * See Wi-Fi Direct specification for the detail.
+     */
+    public static class Status {
+        /** success */
+        public static final int SUCCESS = 0;
+
+        /** the service protocol type is not available */
+        public static final int SERVICE_PROTOCOL_NOT_AVAILABLE = 1;
+
+        /** the requested information is not available */
+        public static final int REQUESTED_INFORMATION_NOT_AVAILABLE = 2;
+
+        /** bad request */
+        public static final int BAD_REQUEST = 3;
+
+        /** @hide */
+        public static String toString(int status) {
+            switch(status) {
+            case SUCCESS:
+                return "SUCCESS";
+            case SERVICE_PROTOCOL_NOT_AVAILABLE:
+                return "SERVICE_PROTOCOL_NOT_AVAILABLE";
+            case REQUESTED_INFORMATION_NOT_AVAILABLE:
+                return "REQUESTED_INFORMATION_NOT_AVAILABLE";
+            case BAD_REQUEST:
+                return "BAD_REQUEST";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        /** not used */
+        private Status() {}
+    }
+
+    /**
+     * Hidden constructor. This is only used in framework.
+     *
+     * @param serviceType service discovery type.
+     * @param status status code.
+     * @param transId transaction id.
+     * @param device source device.
+     * @param data query data.
+     */
+    protected WifiP2pServiceResponse(int serviceType, int status, int transId,
+            WifiP2pDevice device, byte[] data) {
+        mServiceType = serviceType;
+        mStatus = status;
+        mTransId = transId;
+        mDevice = device;
+        mData = data;
+    }
+
+    /**
+     * Return the service type of service discovery response.
+     *
+     * @return service discovery type.<br>
+     * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_BONJOUR}
+     */
+    public int getServiceType() {
+        return mServiceType;
+    }
+
+    /**
+     * Return the status code of service discovery response.
+     *
+     * @return status code.
+     * @see Status
+     */
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * Return the transaction id of service discovery response.
+     *
+     * @return transaction id.
+     * @hide
+     */
+    public int getTransactionId() {
+        return mTransId;
+    }
+
+    /**
+     * Return response data.
+     *
+     * <pre>Data format depends on service type
+     *
+     * @return a query or response data.
+     */
+    public byte[] getRawData() {
+        return mData;
+    }
+
+    /**
+     * Returns the source device of service discovery response.
+     *
+     * <pre>This is valid only when service discovery response.
+     *
+     * @return the source device of service discovery response.
+     */
+    public WifiP2pDevice getSrcDevice() {
+        return mDevice;
+    }
+
+    /** @hide */
+    public void setSrcDevice(WifiP2pDevice dev) {
+        if (dev == null) return;
+        this.mDevice = dev;
+    }
+
+
+    /**
+     * Create the list of  WifiP2pServiceResponse instance from supplicant event.
+     *
+     * <pre>The format is as follows.
+     * P2P-SERV-DISC-RESP &lt;address&gt; &lt;update indicator&gt; &lt;response data&gt;
+     * e.g) P2P-SERV-DISC-RESP 02:03:7f:11:62:da 1 0300000101
+     *
+     * @param supplicantEvent wpa_supplicant event string.
+     * @return if parse failed, return null
+     * @hide
+     */
+    public static List<WifiP2pServiceResponse> newInstance(String supplicantEvent) {
+
+        List<WifiP2pServiceResponse> respList = new ArrayList<WifiP2pServiceResponse>();
+        String[] args = supplicantEvent.split(" ");
+        if (args.length != 4) {
+            return null;
+        }
+        WifiP2pDevice dev = new WifiP2pDevice();
+        String srcAddr = args[1];
+        dev.deviceAddress = srcAddr;
+        //String updateIndicator = args[2];//not used.
+        byte[] bin = hexStr2Bin(args[3]);
+        if (bin == null) {
+            return null;
+        }
+
+        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bin));
+        try {
+            while (dis.available() > 0) {
+                /*
+                 * Service discovery header is as follows.
+                 * ______________________________________________________________
+                 * |           Length(2byte)     | Type(1byte) | TransId(1byte)}|
+                 * ______________________________________________________________
+                 * | status(1byte)  |            vendor specific(variable)      |
+                 */
+                // The length equals to 3 plus the number of octets in the vendor
+                // specific content field. And this is little endian.
+                int length = ((dis.readByte() & 0xff) +
+                        ((dis.readByte() & 0xff) << 8)) - 3;
+                int type = dis.readUnsignedByte();
+                byte transId = dis.readByte();
+                int status = dis.readUnsignedByte();
+                if (length < 0) {
+                    return null;
+                }
+                if (length == 0) {
+                    if (status == Status.SUCCESS) {
+                        respList.add(new WifiP2pServiceResponse(type, status,
+                            transId, dev, null));
+                    }
+                    continue;
+                }
+                if (length > MAX_BUF_SIZE) {
+                    dis.skip(length);
+                    continue;
+                }
+                byte[] data = new byte[length];
+                dis.readFully(data);
+
+                WifiP2pServiceResponse resp;
+                if (type ==  WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) {
+                    resp = WifiP2pBonjourServiceResponse.newInstance(status,
+                            transId, dev, data);
+                } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) {
+                    resp = WifiP2pUpnpServiceResponse.newInstance(status,
+                            transId, dev, data);
+                } else {
+                    resp = new WifiP2pServiceResponse(type, status, transId, dev, data);
+                }
+                if (resp != null && resp.getStatus() == Status.SUCCESS) {
+                    respList.add(resp);
+                }
+            }
+            return respList;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        if (respList.size() > 0) {
+            return respList;
+        }
+        return null;
+    }
+
+    /**
+     * Converts hex string to byte array.
+     *
+     * @param hex hex string. if invalid, return null.
+     * @return binary data.
+     */
+    private static byte[] hexStr2Bin(String hex) {
+        int sz = hex.length()/2;
+        byte[] b = new byte[hex.length()/2];
+
+        for (int i=0;i<sz;i++) {
+            try {
+                b[i] = (byte)Integer.parseInt(hex.substring(i*2, i*2+2), 16);
+            } catch (Exception e) {
+                e.printStackTrace();
+                return null;
+            }
+        }
+        return b;
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sbuf = new StringBuffer();
+        sbuf.append("serviceType:").append(mServiceType);
+        sbuf.append(" status:").append(Status.toString(mStatus));
+        sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+        sbuf.append(" data:").append(mData);
+        return sbuf.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (!(o instanceof WifiP2pServiceResponse)) {
+            return false;
+        }
+
+        WifiP2pServiceResponse req = (WifiP2pServiceResponse)o;
+
+        return (req.mServiceType == mServiceType) &&
+            (req.mStatus == mStatus) &&
+                equals(req.mDevice.deviceAddress, mDevice.deviceAddress) &&
+                Arrays.equals(req.mData, mData);
+    }
+
+    private boolean equals(Object a, Object b) {
+        if (a == null && b == null) {
+            return true;
+        } else if (a != null) {
+            return a.equals(b);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + mServiceType;
+        result = 31 * result + mStatus;
+        result = 31 * result + mTransId;
+        result = 31 * result + (mDevice.deviceAddress == null ?
+                0 : mDevice.deviceAddress.hashCode());
+        result = 31 * result + (mData == null ? 0 : mData.hashCode());
+        return result;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mServiceType);
+        dest.writeInt(mStatus);
+        dest.writeInt(mTransId);
+        dest.writeParcelable(mDevice, flags);
+        if (mData == null || mData.length == 0) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(mData.length);
+            dest.writeByteArray(mData);
+        }
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public static final Creator<WifiP2pServiceResponse> CREATOR =
+        new Creator<WifiP2pServiceResponse>() {
+            public WifiP2pServiceResponse createFromParcel(Parcel in) {
+
+                int type = in.readInt();
+                int status = in.readInt();
+                int transId = in.readInt();
+                WifiP2pDevice dev = (WifiP2pDevice)in.readParcelable(null);
+                int len = in.readInt();
+                byte[] data = null;
+                if (len > 0) {
+                    data = new byte[len];
+                    in.readByteArray(data);
+                }
+                if (type ==  WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) {
+                    return WifiP2pBonjourServiceResponse.newInstance(status,
+                            transId, dev, data);
+                } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) {
+                    return WifiP2pUpnpServiceResponse.newInstance(status,
+                            transId, dev, data);
+                }
+                return new WifiP2pServiceResponse(type, status, transId, dev, data);
+            }
+
+            public WifiP2pServiceResponse[] newArray(int size) {
+                return new WifiP2pServiceResponse[size];
+            }
+        };
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java
new file mode 100644
index 0000000..4d40e81
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java
@@ -0,0 +1,105 @@
+/*
+ * 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 android.net.wifi.p2p.nsd;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * The class for UPnP service information.
+ * @hide
+ */
+public class WifiP2pUpnpServiceInfo extends WifiP2pServiceInfo {
+
+    /**
+     * UPnP version 1.0.
+     *
+     * <pre>Query Version should always be set to 0x10 if the query values are
+     * compatible with UPnP Device Architecture 1.0.
+     * @hide
+     */
+    public static final int VERSION_1_0 = 0x10;
+
+    /**
+     * This constructor is only used in newInstance().
+     *
+     * @param queryList
+     */
+    private WifiP2pUpnpServiceInfo(List<String> queryList) {
+        super(queryList);
+    }
+
+    /**
+     * Create UPnP service information object.
+     *
+     * @param uuid a string representation of this UUID in the following format,
+     *  as per <a href="http://www.ietf.org/rfc/rfc4122.txt">RFC 4122</a>.<br>
+     *  e.g) 6859dede-8574-59ab-9332-123456789012
+     * @param device a string representation of this device in the following format,
+     * as per
+     * <a href="http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf">
+     *  UPnP Device Architecture1.1</a><br>
+     *  e.g) urn:schemas-upnp-org:device:MediaServer:1
+     * @param services a string representation of this service in the following format,
+     * as per
+     * <a href="http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf">
+     *  UPnP Device Architecture1.1</a><br>
+     *  e.g) urn:schemas-upnp-org:service:ContentDirectory:1
+     * @return UPnP service information object.
+     */
+    public static WifiP2pUpnpServiceInfo newInstance(String uuid,
+            String device, List<String> services) {
+        if (uuid == null || device == null) {
+            throw new IllegalArgumentException("uuid or device cannnot be null");
+        }
+        UUID.fromString(uuid);
+
+        ArrayList<String> info = new ArrayList<String>();
+
+        info.add(createSupplicantQuery(uuid, null));
+        info.add(createSupplicantQuery(uuid, "upnp:rootdevice"));
+        info.add(createSupplicantQuery(uuid, device));
+        if (services != null) {
+            for (String service:services) {
+                info.add(createSupplicantQuery(uuid, service));
+            }
+        }
+
+        return new WifiP2pUpnpServiceInfo(info);
+    }
+
+    /**
+     * Create wpa_supplicant service query for upnp.
+     *
+     * @param uuid
+     * @param data
+     * @return wpa_supplicant service query for upnp
+     */
+    private static String createSupplicantQuery(String uuid, String data) {
+        StringBuffer sb = new StringBuffer();
+        sb.append("upnp ");
+        sb.append(String.format("%02x ", VERSION_1_0));
+        sb.append("uuid:");
+        sb.append(uuid);
+        if (data != null) {
+            sb.append("::");
+            sb.append(data);
+        }
+        return sb.toString();
+    }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java
new file mode 100644
index 0000000..b97637a
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 android.net.wifi.p2p.nsd;
+
+/**
+ * The class for a request of upnp service discovery.
+ * @hide
+ */
+public class WifiP2pUpnpServiceRequest extends WifiP2pServiceRequest {
+
+    /**
+     * This constructor is only used in newInstance().
+     *
+     * @param query The part of service specific query.
+     * @hide
+     */
+    protected WifiP2pUpnpServiceRequest(String query) {
+        super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, query);
+    }
+
+    /**
+     * This constructor is only used in newInstance().
+     * @hide
+     */
+    protected WifiP2pUpnpServiceRequest() {
+        super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, null);
+    }
+
+    /**
+     * Create a service discovery request to search all UPnP services.
+     *
+     * @return service request for UPnP.
+     */
+    public static WifiP2pUpnpServiceRequest newInstance() {
+        return new WifiP2pUpnpServiceRequest();
+    }
+    /**
+     * Create a service discovery request to search specified UPnP services.
+     *
+     * @param st ssdp search target.  Cannot be null.<br>
+     * e.g ) <br>
+     * <ul>
+     * <li>"ssdp:all"
+     * <li>"upnp:rootdevice"
+     * <li>"urn:schemas-upnp-org:device:MediaServer:2"
+     * <li>"urn:schemas-upnp-org:service:ContentDirectory:2"
+     * <li>"uuid:6859dede-8574-59ab-9332-123456789012"
+     * </ul>
+     * @return service request for UPnP.
+     */
+    public static WifiP2pUpnpServiceRequest newInstance(String st) {
+        if (st == null) {
+            throw new IllegalArgumentException("search target cannot be null");
+        }
+        StringBuffer sb = new StringBuffer();
+        sb.append(String.format("%02x", WifiP2pUpnpServiceInfo.VERSION_1_0));
+        sb.append(WifiP2pServiceInfo.bin2HexStr(st.getBytes()));
+        return new WifiP2pUpnpServiceRequest(sb.toString());
+    }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java
new file mode 100644
index 0000000..ab95af6f6
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java
@@ -0,0 +1,159 @@
+/*
+ * 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 android.net.wifi.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pDevice;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class for a response of upnp service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pUpnpServiceResponse extends WifiP2pServiceResponse {
+
+    /**
+     * UPnP version. should be {@link WifiP2pUpnpServiceInfo#VERSION_1_0}
+     */
+    private int mVersion;
+
+    /**
+     * The list of Unique Service Name.
+     * e.g)
+     *{"uuid:6859dede-8574-59ab-9332-123456789012",
+     *"uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"}
+     */
+    private List<String> mUniqueServiceNames;
+
+    /**
+     * Return UPnP version number.
+     *
+     * @return version number.
+     * @see WifiP2pUpnpServiceInfo#VERSION_1_0
+     */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Return Unique Service Name strings.
+     *
+     * @return Unique Service Name.<br>
+     * e.g ) <br>
+     * <ul>
+     * <li>"uuid:6859dede-8574-59ab-9332-123456789012"
+     * <li>"uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"
+     * <li>"uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:device:
+     * MediaServer:2"
+     * <li>"uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:service:
+     * ContentDirectory:2"
+     * </ul>
+     */
+    public List<String> getUniqueServiceNames() {
+        return mUniqueServiceNames;
+    }
+
+    /**
+     * hidden constructor.
+     *
+     * @param status status code
+     * @param transId transaction id
+     * @param dev source device
+     * @param data UPnP response data.
+     */
+    protected WifiP2pUpnpServiceResponse(int status,
+            int transId, WifiP2pDevice dev, byte[] data) {
+        super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP,
+                status, transId, dev, data);
+        if (!parse()) {
+            throw new IllegalArgumentException("Malformed upnp service response");
+        }
+    }
+
+    /**
+     * Parse UPnP service discovery response
+     *
+     * @return {@code true} if the operation succeeded
+     */
+    private boolean parse() {
+        /*
+         * The data format is as follows.
+         *
+         * ______________________________________________________
+         * |  Version (1)  |          USN (Variable)            |
+         */
+        if (mData == null) {
+            // the empty is OK.
+            return true;
+        }
+
+        if (mData.length < 1) {
+            return false;
+        }
+
+        mVersion = mData[0] & 0xff;
+        String[] names = new String(mData, 1, mData.length-1).split(",");
+        mUniqueServiceNames = new ArrayList<String>();
+        for (String name : names) {
+            mUniqueServiceNames.add(name);
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sbuf = new StringBuffer();
+        sbuf.append("serviceType:UPnP(").append(mServiceType).append(")");
+        sbuf.append(" status:").append(Status.toString(mStatus));
+        sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+        sbuf.append(" version:").append(String.format("%02x", mVersion));
+        if (mUniqueServiceNames != null) {
+            for (String name : mUniqueServiceNames) {
+                sbuf.append(" usn:").append(name);
+            }
+        }
+        return sbuf.toString();
+    }
+
+    /**
+     * Create upnp service response.
+     *
+     * <pre>This is only used in{@link WifiP2pServiceResponse}
+     *
+     * @param status status code.
+     * @param transId transaction id.
+     * @param device source device.
+     * @param data UPnP response data.
+     * @return UPnP service response data.
+     * @hide
+     */
+    static WifiP2pUpnpServiceResponse newInstance(int status,
+            int transId, WifiP2pDevice device, byte[] data) {
+        if (status != WifiP2pServiceResponse.Status.SUCCESS) {
+            return new WifiP2pUpnpServiceResponse(status, transId, device, null);
+        }
+
+        try {
+            return new WifiP2pUpnpServiceResponse(status, transId, device, data);
+        } catch (IllegalArgumentException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+}