Merge "HID usage should take precedence over scan code."
diff --git a/api/16.txt b/api/16.txt
index 8ff7675..aa23b4d 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -25204,7 +25204,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 +41831,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 +41889,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 +41908,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 +41945,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 ee964f7..bb313cd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19697,12 +19697,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);
   }
 
@@ -19744,6 +19744,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 {
@@ -25795,7 +25796,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();
@@ -42498,15 +42498,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 {
@@ -42558,9 +42564,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();
@@ -42577,21 +42583,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);
   }
 
 }
@@ -42608,13 +42617,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/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 241fecb..2b643c2 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -1869,9 +1869,12 @@
         File systemDir = Environment.getSystemSecureDirectory();
         File databaseFile = new File(systemDir, "users/" + userId + "/" + DATABASE_NAME);
         if (userId == 0) {
-            // Migrate old file, if it exists, to the new location
+            // Migrate old file, if it exists, to the new location.
+            // Make sure the new file doesn't already exist. A dummy file could have been
+            // accidentally created in the old location, causing the new one to become corrupted
+            // as well.
             File oldFile = new File(systemDir, DATABASE_NAME);
-            if (oldFile.exists()) {
+            if (oldFile.exists() && !databaseFile.exists()) {
                 // Check for use directory; create if it doesn't exist, else renameTo will fail
                 File userDir = new File(systemDir, "users/" + userId);
                 if (!userDir.exists()) {
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/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/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/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b06b4a5..5d890d4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1095,6 +1095,18 @@
     /** {@hide} */
     public static final int ENFORCEMENT_YES = 1;
 
+    /** {@hide} */
+    public static String enforcementToString(int enforcement) {
+        switch (enforcement) {
+            case ENFORCEMENT_DEFAULT:
+                return "DEFAULT";
+            case ENFORCEMENT_YES:
+                return "YES";
+            default:
+                return Integer.toString(enforcement);
+        }
+    }
+
     /**
      * Retrieve overall information about an application package that is
      * installed on the system.
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/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 77fd8d2..e51ba3d 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2715,6 +2715,67 @@
     }
 
     /**
+     * Adds all of the movement samples of the specified event to this one if
+     * it is compatible.  To be compatible, the event must have the same device id,
+     * source, action, flags, pointer count, pointer properties.
+     *
+     * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
+     *
+     * @param event The event whose movements samples should be added to this one
+     * if possible.
+     * @return True if batching was performed or false if batching was not possible.
+     * @hide
+     */
+    public final boolean addBatch(MotionEvent event) {
+        final int action = nativeGetAction(mNativePtr);
+        if (action != ACTION_MOVE && action != ACTION_HOVER_MOVE) {
+            return false;
+        }
+        if (action != nativeGetAction(event.mNativePtr)) {
+            return false;
+        }
+
+        if (nativeGetDeviceId(mNativePtr) != nativeGetDeviceId(event.mNativePtr)
+                || nativeGetSource(mNativePtr) != nativeGetSource(event.mNativePtr)
+                || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)) {
+            return false;
+        }
+
+        final int pointerCount = nativeGetPointerCount(mNativePtr);
+        if (pointerCount != nativeGetPointerCount(event.mNativePtr)) {
+            return false;
+        }
+
+        synchronized (gSharedTempLock) {
+            ensureSharedTempPointerCapacity(Math.max(pointerCount, 2));
+            final PointerProperties[] pp = gSharedTempPointerProperties;
+            final PointerCoords[] pc = gSharedTempPointerCoords;
+
+            for (int i = 0; i < pointerCount; i++) {
+                nativeGetPointerProperties(mNativePtr, i, pp[0]);
+                nativeGetPointerProperties(event.mNativePtr, i, pp[1]);
+                if (!pp[0].equals(pp[1])) {
+                    return false;
+                }
+            }
+
+            final int metaState = nativeGetMetaState(event.mNativePtr);
+            final int historySize = nativeGetHistorySize(event.mNativePtr);
+            for (int h = 0; h <= historySize; h++) {
+                final int historyPos = (h == historySize ? HISTORY_CURRENT : h);
+
+                for (int i = 0; i < pointerCount; i++) {
+                    nativeGetPointerCoords(event.mNativePtr, i, historyPos, pc[i]);
+                }
+
+                final long eventTimeNanos = nativeGetEventTimeNanos(event.mNativePtr, historyPos);
+                nativeAddBatch(mNativePtr, eventTimeNanos, pc, metaState);
+            }
+        }
+        return true;
+    }
+
+    /**
      * Returns true if all points in the motion event are completely within the specified bounds.
      * @hide
      */
@@ -3416,5 +3477,22 @@
             id = other.id;
             toolType = other.toolType;
         }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof PointerProperties) {
+                return equals((PointerProperties)other);
+            }
+            return false;
+        }
+
+        private boolean equals(PointerProperties other) {
+            return other != null && id == other.id && toolType == other.toolType;
+        }
+
+        @Override
+        public int hashCode() {
+            return id | (toolType << 8);
+        }
     }
 }
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 3cd8b71..ba62e65 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -204,7 +204,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/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 84632c6..d1cfc6b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1539,6 +1539,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 d7ebf5c..5dc2681 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -57,6 +57,7 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
 import android.provider.Settings;
@@ -100,7 +101,6 @@
 import android.webkit.WebViewCore.DrawData;
 import android.webkit.WebViewCore.EventHub;
 import android.webkit.WebViewCore.TextFieldInitData;
-import android.webkit.WebViewCore.TouchEventData;
 import android.webkit.WebViewCore.TouchHighlightData;
 import android.webkit.WebViewCore.WebKitHitTest;
 import android.widget.AbsoluteLayout;
@@ -981,6 +981,8 @@
 
     /**
      * Touch mode
+     * TODO: Some of this is now unnecessary as it is handled by
+     * WebInputTouchDispatcher (such as click, long press, and double tap).
      */
     private int mTouchMode = TOUCH_DONE_MODE;
     private static final int TOUCH_INIT_MODE = 1;
@@ -994,35 +996,10 @@
     private static final int TOUCH_DRAG_LAYER_MODE = 9;
     private static final int TOUCH_DRAG_TEXT_MODE = 10;
 
-    // Whether to forward the touch events to WebCore
-    // Can only be set by WebKit via JNI.
-    private boolean mForwardTouchEvents = false;
-
-    // Whether to prevent default during touch. The initial value depends on
-    // mForwardTouchEvents. If WebCore wants all the touch events, it says yes
-    // for touch down. Otherwise UI will wait for the answer of the first
-    // confirmed move before taking over the control.
-    private static final int PREVENT_DEFAULT_NO = 0;
-    private static final int PREVENT_DEFAULT_MAYBE_YES = 1;
-    private static final int PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN = 2;
-    private static final int PREVENT_DEFAULT_YES = 3;
-    private static final int PREVENT_DEFAULT_IGNORE = 4;
-    private int mPreventDefault = PREVENT_DEFAULT_IGNORE;
-
     // true when the touch movement exceeds the slop
     private boolean mConfirmMove;
     private boolean mTouchInEditText;
 
-    // if true, touch events will be first processed by WebCore, if prevent
-    // default is not set, the UI will continue handle them.
-    private boolean mDeferTouchProcess;
-
-    // to avoid interfering with the current touch events, track them
-    // separately. Currently no snapping or fling in the deferred process mode
-    private int mDeferTouchMode = TOUCH_DONE_MODE;
-    private float mLastDeferTouchX;
-    private float mLastDeferTouchY;
-
     // Whether or not to draw the cursor ring.
     private boolean mDrawCursorRing = true;
 
@@ -1410,7 +1387,7 @@
     private boolean mSentAutoScrollMessage = false;
 
     // used for serializing asynchronously handled touch events.
-    private final TouchEventQueue mTouchEventQueue = new TouchEventQueue();
+    private WebViewInputDispatcher mInputDispatcher;
 
     // Used to track whether picture updating was paused due to a window focus change.
     private boolean mPictureUpdatePausedForFocusChange = false;
@@ -1500,6 +1477,68 @@
 
     }
 
+    private void onHandleUiEvent(MotionEvent event, int eventType, int flags) {
+        switch (eventType) {
+        case WebViewInputDispatcher.EVENT_TYPE_LONG_PRESS:
+            HitTestResult hitTest = getHitTestResult();
+            if (hitTest != null
+                    && hitTest.getType() != HitTestResult.UNKNOWN_TYPE) {
+                performLongClick();
+            }
+            break;
+        case WebViewInputDispatcher.EVENT_TYPE_DOUBLE_TAP:
+            mZoomManager.handleDoubleTap(event.getX(), event.getY());
+            break;
+        case WebViewInputDispatcher.EVENT_TYPE_TOUCH:
+            onHandleUiTouchEvent(event);
+            break;
+        }
+    }
+
+    private void onHandleUiTouchEvent(MotionEvent ev) {
+        final ScaleGestureDetector detector =
+                mZoomManager.getMultiTouchGestureDetector();
+
+        float x = ev.getX();
+        float y = ev.getY();
+
+        if (detector != null) {
+            detector.onTouchEvent(ev);
+            if (detector.isInProgress()) {
+                mLastTouchTime = ev.getEventTime();
+                x = detector.getFocusX();
+                y = detector.getFocusY();
+
+                mWebView.cancelLongPress();
+                mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+                if (!mZoomManager.supportsPanDuringZoom()) {
+                    return;
+                }
+                mTouchMode = TOUCH_DRAG_MODE;
+                if (mVelocityTracker == null) {
+                    mVelocityTracker = VelocityTracker.obtain();
+                }
+            }
+        }
+
+        int action = ev.getActionMasked();
+        if (action == MotionEvent.ACTION_POINTER_DOWN) {
+            cancelTouch();
+            action = MotionEvent.ACTION_DOWN;
+        } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) {
+            // set mLastTouchX/Y to the remaining points for multi-touch.
+            mLastTouchX = Math.round(x);
+            mLastTouchY = Math.round(y);
+        } else if (action == MotionEvent.ACTION_MOVE) {
+            // negative x or y indicate it is on the edge, skip it.
+            if (x < 0 || y < 0) {
+                return;
+            }
+        }
+
+        handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
+    }
+
     // The webview that is bound to this WebViewClassic instance. Primarily needed for supplying
     // as the first param in the WebViewClient and WebChromeClient callbacks.
     final private WebView mWebView;
@@ -4372,8 +4411,7 @@
         boolean animateScroll = ((!mScroller.isFinished()
                 || mVelocityTracker != null)
                 && (mTouchMode != TOUCH_DRAG_MODE ||
-                mHeldMotionless != MOTIONLESS_TRUE))
-                || mDeferTouchMode == TOUCH_DRAG_MODE;
+                mHeldMotionless != MOTIONLESS_TRUE));
         if (mTouchMode == TOUCH_DRAG_MODE) {
             if (mHeldMotionless == MOTIONLESS_PENDING) {
                 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
@@ -5573,7 +5611,6 @@
 
         addAccessibilityApisToJavaScript();
 
-        mTouchEventQueue.reset();
         updateHwAccelerated();
     }
 
@@ -5822,20 +5859,6 @@
      */
     private static final float MMA_WEIGHT_N = 5;
 
-    private boolean hitFocusedPlugin(int contentX, int contentY) {
-        // TODO: Figure out what to do with this (b/6111517)
-        return false;
-    }
-
-    private boolean shouldForwardTouchEvent() {
-        if (mFullScreenHolder != null) return true;
-        if (mBlockWebkitViewMessages) return false;
-        return mForwardTouchEvents
-                && !mSelectingText
-                && mPreventDefault != PREVENT_DEFAULT_IGNORE
-                && mPreventDefault != PREVENT_DEFAULT_NO;
-    }
-
     private boolean inFullScreenMode() {
         return mFullScreenHolder != null;
     }
@@ -5905,23 +5928,17 @@
             mWebView.requestFocus();
         }
 
-        if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, ev + " at " + ev.getEventTime()
-                + " mTouchMode=" + mTouchMode
-                + " numPointers=" + ev.getPointerCount());
+        if (mInputDispatcher == null) {
+            return false;
         }
 
-        // If WebKit wasn't interested in this multitouch gesture, enqueue
-        // the event for handling directly rather than making the round trip
-        // to WebKit and back.
-        if (ev.getPointerCount() > 1 && mPreventDefault != PREVENT_DEFAULT_NO) {
-            passMultiTouchToWebKit(ev, mTouchEventQueue.nextTouchSequence());
+        if (mInputDispatcher.postPointerEvent(ev, getScrollX(),
+                getScrollY() - getTitleHeight(), mZoomManager.getInvScale())) {
+            return true;
         } else {
-            mTouchEventQueue.enqueueTouchEvent(ev);
+            Log.w(LOGTAG, "mInputDispatcher rejected the event!");
+            return false;
         }
-
-        // Since all events are handled asynchronously, we always want the gesture stream.
-        return true;
     }
 
     private float calculateDragAngle(int dx, int dy) {
@@ -5931,12 +5948,14 @@
     }
 
     /*
-     * Common code for single touch and multi-touch.
-     * (x, y) denotes current focus point, which is the touch point for single touch
-     * and the middle point for multi-touch.
-     */
-    private boolean handleTouchEventCommon(MotionEvent ev, int action, int x, int y) {
-        long eventTime = ev.getEventTime();
+    * Common code for single touch and multi-touch.
+    * (x, y) denotes current focus point, which is the touch point for single touch
+    * and the middle point for multi-touch.
+    */
+    private void handleTouchEventCommon(MotionEvent event, int action, int x, int y) {
+        ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
+
+        long eventTime = event.getEventTime();
 
         // Due to the touch screen edge effect, a touch closer to the edge
         // always snapped to the edge. As getViewWidth() can be different from
@@ -5952,7 +5971,6 @@
 
         switch (action) {
             case MotionEvent.ACTION_DOWN: {
-                mPreventDefault = PREVENT_DEFAULT_NO;
                 mConfirmMove = false;
                 mInitialHitTestResult = null;
                 if (!mEditTextScroller.isFinished()) {
@@ -5972,20 +5990,11 @@
                     if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
                         mTouchMode = TOUCH_DOUBLE_TAP_MODE;
                     } else {
-                        // commit the short press action for the previous tap
-                        doShortPress();
                         mTouchMode = TOUCH_INIT_MODE;
-                        mDeferTouchProcess = !mBlockWebkitViewMessages
-                                && (!inFullScreenMode() && mForwardTouchEvents)
-                                ? hitFocusedPlugin(contentX, contentY)
-                                : false;
                     }
                 } else { // the normal case
                     mTouchMode = TOUCH_INIT_MODE;
-                    mDeferTouchProcess = !mBlockWebkitViewMessages
-                            && (!inFullScreenMode() && mForwardTouchEvents)
-                            ? hitFocusedPlugin(contentX, contentY)
-                            : false;
+                    // TODO: Have WebViewInputDispatch handle this
                     TouchHighlightData data = new TouchHighlightData();
                     data.mX = contentX;
                     data.mY = contentY;
@@ -5999,19 +6008,6 @@
                         mWebViewCore.sendMessageAtFrontOfQueue(
                                 EventHub.HIT_TEST, data);
                     }
-                    if (DEBUG_TOUCH_HIGHLIGHT) {
-                        if (getSettings().getNavDump()) {
-                            mTouchHighlightX = x + getScrollX();
-                            mTouchHighlightY = y + getScrollY();
-                            mPrivateHandler.postDelayed(new Runnable() {
-                                @Override
-                                public void run() {
-                                    mTouchHighlightX = mTouchHighlightY = 0;
-                                    invalidate();
-                                }
-                            }, TOUCH_HIGHLIGHT_ELAPSE_TIME);
-                        }
-                    }
                     if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
                         EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
                                 (eventTime - mLastTouchUpTime), eventTime);
@@ -6058,43 +6054,6 @@
                             SWITCH_TO_SHORTPRESS, TAP_TIMEOUT);
                     mPrivateHandler.sendEmptyMessageDelayed(
                             SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT);
-                    if (inFullScreenMode() || mDeferTouchProcess) {
-                        mPreventDefault = PREVENT_DEFAULT_YES;
-                    } else if (!mBlockWebkitViewMessages && mForwardTouchEvents) {
-                        mPreventDefault = PREVENT_DEFAULT_MAYBE_YES;
-                    } else {
-                        mPreventDefault = PREVENT_DEFAULT_NO;
-                    }
-                    // pass the touch events from UI thread to WebCore thread
-                    if (shouldForwardTouchEvent()) {
-                        TouchEventData ted = new TouchEventData();
-                        ted.mAction = action;
-                        ted.mIds = new int[1];
-                        ted.mIds[0] = ev.getPointerId(0);
-                        ted.mPoints = new Point[1];
-                        ted.mPoints[0] = new Point(contentX, contentY);
-                        ted.mPointsInView = new Point[1];
-                        ted.mPointsInView[0] = new Point(x, y);
-                        ted.mMetaState = ev.getMetaState();
-                        ted.mReprocess = mDeferTouchProcess;
-                        ted.mNativeLayer = nativeScrollableLayer(
-                                contentX, contentY, ted.mNativeLayerRect, null);
-                        ted.mSequence = mTouchEventQueue.nextTouchSequence();
-                        mTouchEventQueue.preQueueTouchEventData(ted);
-                        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
-                        if (mDeferTouchProcess) {
-                            // still needs to set them for compute deltaX/Y
-                            mLastTouchX = x;
-                            mLastTouchY = y;
-                            break;
-                        }
-                        if (!inFullScreenMode()) {
-                            mPrivateHandler.removeMessages(PREVENT_DEFAULT_TIMEOUT);
-                            mPrivateHandler.sendMessageDelayed(mPrivateHandler
-                                    .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
-                                            action, 0), TAP_TIMEOUT);
-                        }
-                    }
                 }
                 startTouch(x, y, eventTime);
                 if (mIsEditingText) {
@@ -6104,13 +6063,11 @@
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
-                boolean firstMove = false;
                 if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY)
                         >= mTouchSlopSquare) {
                     mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
                     mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
                     mConfirmMove = true;
-                    firstMove = true;
                     if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
                         mTouchMode = TOUCH_INIT_MODE;
                     }
@@ -6149,48 +6106,16 @@
                     break;
                 }
 
-                // pass the touch events from UI thread to WebCore thread
-                if (shouldForwardTouchEvent() && mConfirmMove && (firstMove
-                        || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) {
-                    TouchEventData ted = new TouchEventData();
-                    ted.mAction = action;
-                    ted.mIds = new int[1];
-                    ted.mIds[0] = ev.getPointerId(0);
-                    ted.mPoints = new Point[1];
-                    ted.mPoints[0] = new Point(contentX, contentY);
-                    ted.mPointsInView = new Point[1];
-                    ted.mPointsInView[0] = new Point(x, y);
-                    ted.mMetaState = ev.getMetaState();
-                    ted.mReprocess = mDeferTouchProcess;
-                    ted.mNativeLayer = mCurrentScrollingLayerId;
-                    ted.mNativeLayerRect.set(mScrollingLayerRect);
-                    ted.mMotionEvent = MotionEvent.obtain(ev);
-                    ted.mSequence = mTouchEventQueue.nextTouchSequence();
-                    mTouchEventQueue.preQueueTouchEventData(ted);
-                    mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
-                    mLastSentTouchTime = eventTime;
-                    if (mDeferTouchProcess) {
-                        break;
-                    }
-                    if (firstMove && !inFullScreenMode()) {
-                        mPrivateHandler.sendMessageDelayed(mPrivateHandler
-                                .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
-                                        action, 0), TAP_TIMEOUT);
-                    }
-                }
-                if (mTouchMode == TOUCH_DONE_MODE
-                        || mPreventDefault == PREVENT_DEFAULT_YES) {
+                if (mTouchMode == TOUCH_DONE_MODE) {
                     // no dragging during scroll zoom animation, or when prevent
                     // default is yes
                     break;
                 }
                 if (mVelocityTracker == null) {
                     Log.e(LOGTAG, "Got null mVelocityTracker when "
-                            + "mPreventDefault = " + mPreventDefault
-                            + " mDeferTouchProcess = " + mDeferTouchProcess
                             + " mTouchMode = " + mTouchMode);
                 } else {
-                    mVelocityTracker.addMovement(ev);
+                    mVelocityTracker.addMovement(event);
                 }
 
                 if (mTouchMode != TOUCH_DRAG_MODE &&
@@ -6201,19 +6126,9 @@
                         break;
                     }
 
-                    if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES
-                            || mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
-                        // track mLastTouchTime as we may need to do fling at
-                        // ACTION_UP
-                        mLastTouchTime = eventTime;
-                        break;
-                    }
-
                     // Only lock dragging to one axis if we don't have a scale in progress.
                     // Scaling implies free-roaming movement. Note this is only ever a question
                     // if mZoomManager.supportsPanDuringZoom() is true.
-                    final ScaleGestureDetector detector =
-                      mZoomManager.getMultiTouchGestureDetector();
                     mAverageAngle = calculateDragAngle(deltaX, deltaY);
                     if (detector == null || !detector.isInProgress()) {
                         // if it starts nearly horizontal or vertical, enforce it
@@ -6239,10 +6154,9 @@
                 }
 
                 // do pan
-                boolean done = false;
                 boolean keepScrollBarsVisible = false;
                 if (deltaX == 0 && deltaY == 0) {
-                    keepScrollBarsVisible = done = true;
+                    keepScrollBarsVisible = true;
                 } else {
                     mAverageAngle +=
                         (calculateDragAngle(deltaX, deltaY) - mAverageAngle)
@@ -6319,7 +6233,7 @@
                             ViewConfiguration.getScrollDefaultDelay());
                     // return false to indicate that we can't pan out of the
                     // view space
-                    return !done;
+                    return;
                 } else {
                     mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
                 }
@@ -6332,24 +6246,6 @@
                     stopTouch();
                     break;
                 }
-                // pass the touch events from UI thread to WebCore thread
-                if (shouldForwardTouchEvent()) {
-                    TouchEventData ted = new TouchEventData();
-                    ted.mIds = new int[1];
-                    ted.mIds[0] = ev.getPointerId(0);
-                    ted.mAction = action;
-                    ted.mPoints = new Point[1];
-                    ted.mPoints[0] = new Point(contentX, contentY);
-                    ted.mPointsInView = new Point[1];
-                    ted.mPointsInView[0] = new Point(x, y);
-                    ted.mMetaState = ev.getMetaState();
-                    ted.mReprocess = mDeferTouchProcess;
-                    ted.mNativeLayer = mCurrentScrollingLayerId;
-                    ted.mNativeLayerRect.set(mScrollingLayerRect);
-                    ted.mSequence = mTouchEventQueue.nextTouchSequence();
-                    mTouchEventQueue.preQueueTouchEventData(ted);
-                    mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
-                }
                 mLastTouchUpTime = eventTime;
                 if (mSentAutoScrollMessage) {
                     mAutoScrollX = mAutoScrollY = 0;
@@ -6358,66 +6254,14 @@
                     case TOUCH_DOUBLE_TAP_MODE: // double tap
                         mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
                         mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-                        if (inFullScreenMode() || mDeferTouchProcess) {
-                            TouchEventData ted = new TouchEventData();
-                            ted.mIds = new int[1];
-                            ted.mIds[0] = ev.getPointerId(0);
-                            ted.mAction = WebViewCore.ACTION_DOUBLETAP;
-                            ted.mPoints = new Point[1];
-                            ted.mPoints[0] = new Point(contentX, contentY);
-                            ted.mPointsInView = new Point[1];
-                            ted.mPointsInView[0] = new Point(x, y);
-                            ted.mMetaState = ev.getMetaState();
-                            ted.mReprocess = mDeferTouchProcess;
-                            ted.mNativeLayer = nativeScrollableLayer(
-                                    contentX, contentY,
-                                    ted.mNativeLayerRect, null);
-                            ted.mSequence = mTouchEventQueue.nextTouchSequence();
-                            mTouchEventQueue.preQueueTouchEventData(ted);
-                            mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
-                        } else if (mPreventDefault != PREVENT_DEFAULT_YES){
-                            mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
-                            mTouchMode = TOUCH_DONE_MODE;
-                        }
+                        mTouchMode = TOUCH_DONE_MODE;
                         break;
                     case TOUCH_INIT_MODE: // tap
                     case TOUCH_SHORTPRESS_START_MODE:
                     case TOUCH_SHORTPRESS_MODE:
                         mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
                         mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-                        if (mConfirmMove) {
-                            Log.w(LOGTAG, "Miss a drag as we are waiting for" +
-                                    " WebCore's response for touch down.");
-                            if (mPreventDefault != PREVENT_DEFAULT_YES
-                                    && (computeMaxScrollX() > 0
-                                            || computeMaxScrollY() > 0)) {
-                                // If the user has performed a very quick touch
-                                // sequence it is possible that we may get here
-                                // before WebCore has had a chance to process the events.
-                                // In this case, any call to preventDefault in the
-                                // JS touch handler will not have been executed yet.
-                                // Hence we will see both the UI (now) and WebCore
-                                // (when context switches) handling the event,
-                                // regardless of whether the web developer actually
-                                // doeses preventDefault in their touch handler. This
-                                // is the nature of our asynchronous touch model.
-
-                                // we will not rewrite drag code here, but we
-                                // will try fling if it applies.
-                                WebViewCore.reducePriority();
-                                // to get better performance, pause updating the
-                                // picture
-                                WebViewCore.pauseUpdatePicture(mWebViewCore);
-                                // fall through to TOUCH_DRAG_MODE
-                            } else {
-                                // WebKit may consume the touch event and modify
-                                // DOM. drawContentPicture() will be called with
-                                // animateSroll as true for better performance.
-                                // Force redraw in high-quality.
-                                invalidate();
-                                break;
-                            }
-                        } else {
+                        if (!mConfirmMove) {
                             if (mSelectingText) {
                                 // tapping on selection or controls does nothing
                                 if (!mSelectionStarted) {
@@ -6432,8 +6276,6 @@
                                 mPrivateHandler.sendEmptyMessageDelayed(
                                         RELEASE_SINGLE_TAP, ViewConfiguration
                                                 .getDoubleTapTimeout());
-                            } else {
-                                doShortPress();
                             }
                             break;
                         }
@@ -6446,13 +6288,9 @@
                         // up, we don't want to do a fling
                         if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
                             if (mVelocityTracker == null) {
-                                Log.e(LOGTAG, "Got null mVelocityTracker when "
-                                        + "mPreventDefault = "
-                                        + mPreventDefault
-                                        + " mDeferTouchProcess = "
-                                        + mDeferTouchProcess);
+                                Log.e(LOGTAG, "Got null mVelocityTracker");
                             } else {
-                                mVelocityTracker.addMovement(ev);
+                                mVelocityTracker.addMovement(event);
                             }
                             // set to MOTIONLESS_IGNORE so that it won't keep
                             // removing and sending message in
@@ -6491,128 +6329,10 @@
                             computeMaxScrollX(), 0, computeMaxScrollY());
                     invalidate();
                 }
-                cancelWebCoreTouchEvent(contentX, contentY, false);
                 cancelTouch();
                 break;
             }
         }
-        return true;
-    }
-
-    private void passMultiTouchToWebKit(MotionEvent ev, long sequence) {
-        TouchEventData ted = new TouchEventData();
-        ted.mAction = ev.getActionMasked();
-        final int count = ev.getPointerCount();
-        ted.mIds = new int[count];
-        ted.mPoints = new Point[count];
-        ted.mPointsInView = new Point[count];
-        for (int c = 0; c < count; c++) {
-            ted.mIds[c] = ev.getPointerId(c);
-            int x = viewToContentX((int) ev.getX(c) + getScrollX());
-            int y = viewToContentY((int) ev.getY(c) + getScrollY());
-            ted.mPoints[c] = new Point(x, y);
-            ted.mPointsInView[c] = new Point((int) ev.getX(c), (int) ev.getY(c));
-        }
-        if (ted.mAction == MotionEvent.ACTION_POINTER_DOWN
-            || ted.mAction == MotionEvent.ACTION_POINTER_UP) {
-            ted.mActionIndex = ev.getActionIndex();
-        }
-        ted.mMetaState = ev.getMetaState();
-        ted.mReprocess = true;
-        ted.mMotionEvent = MotionEvent.obtain(ev);
-        ted.mSequence = sequence;
-        mTouchEventQueue.preQueueTouchEventData(ted);
-        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
-        mWebView.cancelLongPress();
-        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-    }
-
-    void handleMultiTouchInWebView(MotionEvent ev) {
-        if (DebugFlags.WEB_VIEW) {
-            Log.v(LOGTAG, "multi-touch: " + ev + " at " + ev.getEventTime()
-                + " mTouchMode=" + mTouchMode
-                + " numPointers=" + ev.getPointerCount()
-                + " scrolloffset=(" + getScrollX() + "," + getScrollY() + ")");
-        }
-
-        final ScaleGestureDetector detector =
-            mZoomManager.getMultiTouchGestureDetector();
-
-        // A few apps use WebView but don't instantiate gesture detector.
-        // We don't need to support multi touch for them.
-        if (detector == null) return;
-
-        float x = ev.getX();
-        float y = ev.getY();
-
-        if (mPreventDefault != PREVENT_DEFAULT_YES) {
-            detector.onTouchEvent(ev);
-
-            if (detector.isInProgress()) {
-                if (DebugFlags.WEB_VIEW) {
-                    Log.v(LOGTAG, "detector is in progress");
-                }
-                mLastTouchTime = ev.getEventTime();
-                x = detector.getFocusX();
-                y = detector.getFocusY();
-
-                mWebView.cancelLongPress();
-                mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-                if (!mZoomManager.supportsPanDuringZoom()) {
-                    return;
-                }
-                mTouchMode = TOUCH_DRAG_MODE;
-                if (mVelocityTracker == null) {
-                    mVelocityTracker = VelocityTracker.obtain();
-                }
-            }
-        }
-
-        int action = ev.getActionMasked();
-        if (action == MotionEvent.ACTION_POINTER_DOWN) {
-            cancelTouch();
-            action = MotionEvent.ACTION_DOWN;
-        } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) {
-            // set mLastTouchX/Y to the remaining points for multi-touch.
-            mLastTouchX = Math.round(x);
-            mLastTouchY = Math.round(y);
-        } else if (action == MotionEvent.ACTION_MOVE) {
-            // negative x or y indicate it is on the edge, skip it.
-            if (x < 0 || y < 0) {
-                return;
-            }
-        }
-
-        handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
-    }
-
-    private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) {
-        if (shouldForwardTouchEvent()) {
-            if (removeEvents) {
-                mWebViewCore.removeMessages(EventHub.TOUCH_EVENT);
-            }
-            TouchEventData ted = new TouchEventData();
-            ted.mIds = new int[1];
-            ted.mIds[0] = 0;
-            ted.mPoints = new Point[1];
-            ted.mPoints[0] = new Point(x, y);
-            ted.mPointsInView = new Point[1];
-            int viewX = contentToViewX(x) - getScrollX();
-            int viewY = contentToViewY(y) - getScrollY();
-            ted.mPointsInView[0] = new Point(viewX, viewY);
-            ted.mAction = MotionEvent.ACTION_CANCEL;
-            ted.mNativeLayer = nativeScrollableLayer(
-                    x, y, ted.mNativeLayerRect, null);
-            ted.mSequence = mTouchEventQueue.nextTouchSequence();
-            mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
-            mPreventDefault = PREVENT_DEFAULT_IGNORE;
-
-            if (removeEvents) {
-                // Mark this after sending the message above; we should
-                // be willing to ignore the cancel event that we just sent.
-                mTouchEventQueue.ignoreCurrentlyMissingEvents();
-            }
-        }
     }
 
     private void startTouch(float x, float y, long eventTime) {
@@ -7249,39 +6969,6 @@
         return mZoomManager.zoomOut();
     }
 
-    private void doShortPress() {
-        if (mNativeClass == 0) {
-            return;
-        }
-        if (mPreventDefault == PREVENT_DEFAULT_YES) {
-            return;
-        }
-        mTouchMode = TOUCH_DONE_MODE;
-        switchOutDrawHistory();
-        if (!mTouchHighlightRegion.isEmpty()) {
-            // set mTouchHighlightRequested to 0 to cause an immediate
-            // drawing of the touch rings
-            mTouchHighlightRequested = 0;
-            mWebView.invalidate(mTouchHighlightRegion.getBounds());
-            mPrivateHandler.postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    removeTouchHighlight();
-                }
-            }, ViewConfiguration.getPressedStateDuration());
-        }
-        if (mFocusedNode != null && mFocusedNode.mIntentUrl != null) {
-            mWebView.playSoundEffect(SoundEffectConstants.CLICK);
-            overrideLoading(mFocusedNode.mIntentUrl);
-        } else {
-            WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
-            // use "0" as generation id to inform WebKit to use the same x/y as
-            // it used when processing GET_TOUCH_HIGHLIGHT_RECTS
-            touchUpData.mMoveGeneration = 0;
-            mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
-        }
-    }
-
     /*
      * Return true if the rect (e.g. plugin) is fully visible and maximized
      * inside the WebView.
@@ -7569,485 +7256,6 @@
         return Math.max(0, mEditTextContent.height() - mEditTextContentBounds.height());
     }
 
-    /**
-     * Used only by TouchEventQueue to store pending touch events.
-     */
-    private static class QueuedTouch {
-        long mSequence;
-        MotionEvent mEvent; // Optional
-        TouchEventData mTed; // Optional
-
-        QueuedTouch mNext;
-
-        public QueuedTouch set(TouchEventData ted) {
-            mSequence = ted.mSequence;
-            mTed = ted;
-            mEvent = null;
-            mNext = null;
-            return this;
-        }
-
-        public QueuedTouch set(MotionEvent ev, long sequence) {
-            mEvent = MotionEvent.obtain(ev);
-            mSequence = sequence;
-            mTed = null;
-            mNext = null;
-            return this;
-        }
-
-        public QueuedTouch add(QueuedTouch other) {
-            if (other.mSequence < mSequence) {
-                other.mNext = this;
-                return other;
-            }
-
-            QueuedTouch insertAt = this;
-            while (insertAt.mNext != null && insertAt.mNext.mSequence < other.mSequence) {
-                insertAt = insertAt.mNext;
-            }
-            other.mNext = insertAt.mNext;
-            insertAt.mNext = other;
-            return this;
-        }
-    }
-
-    /**
-     * WebView handles touch events asynchronously since some events must be passed to WebKit
-     * for potentially slower processing. TouchEventQueue serializes touch events regardless
-     * of which path they take to ensure that no events are ever processed out of order
-     * by WebView.
-     */
-    private class TouchEventQueue {
-        private long mNextTouchSequence = Long.MIN_VALUE + 1;
-        private long mLastHandledTouchSequence = Long.MIN_VALUE;
-        private long mIgnoreUntilSequence = Long.MIN_VALUE + 1;
-
-        // Events waiting to be processed.
-        private QueuedTouch mTouchEventQueue;
-
-        // Known events that are waiting on a response before being enqueued.
-        private QueuedTouch mPreQueue;
-
-        // Pool of QueuedTouch objects saved for later use.
-        private QueuedTouch mQueuedTouchRecycleBin;
-        private int mQueuedTouchRecycleCount;
-
-        private long mLastEventTime = Long.MAX_VALUE;
-        private static final int MAX_RECYCLED_QUEUED_TOUCH = 15;
-
-        // milliseconds until we abandon hope of getting all of a previous gesture
-        private static final int QUEUED_GESTURE_TIMEOUT = 1000;
-
-        private QueuedTouch obtainQueuedTouch() {
-            if (mQueuedTouchRecycleBin != null) {
-                QueuedTouch result = mQueuedTouchRecycleBin;
-                mQueuedTouchRecycleBin = result.mNext;
-                mQueuedTouchRecycleCount--;
-                return result;
-            }
-            return new QueuedTouch();
-        }
-
-        /**
-         * Allow events with any currently missing sequence numbers to be skipped in processing.
-         */
-        public void ignoreCurrentlyMissingEvents() {
-            mIgnoreUntilSequence = mNextTouchSequence;
-
-            // Run any events we have available and complete, pre-queued or otherwise.
-            runQueuedAndPreQueuedEvents();
-        }
-
-        private void runQueuedAndPreQueuedEvents() {
-            QueuedTouch qd = mPreQueue;
-            boolean fromPreQueue = true;
-            while (qd != null && qd.mSequence == mLastHandledTouchSequence + 1) {
-                handleQueuedTouch(qd);
-                QueuedTouch recycleMe = qd;
-                if (fromPreQueue) {
-                    mPreQueue = qd.mNext;
-                } else {
-                    mTouchEventQueue = qd.mNext;
-                }
-                recycleQueuedTouch(recycleMe);
-                mLastHandledTouchSequence++;
-
-                long nextPre = mPreQueue != null ? mPreQueue.mSequence : Long.MAX_VALUE;
-                long nextQueued = mTouchEventQueue != null ?
-                        mTouchEventQueue.mSequence : Long.MAX_VALUE;
-                fromPreQueue = nextPre < nextQueued;
-                qd = fromPreQueue ? mPreQueue : mTouchEventQueue;
-            }
-        }
-
-        /**
-         * Add a TouchEventData to the pre-queue.
-         *
-         * An event in the pre-queue is an event that we know about that
-         * has been sent to webkit, but that we haven't received back and
-         * enqueued into the normal touch queue yet. If webkit ever times
-         * out and we need to ignore currently missing events, we'll run
-         * events from the pre-queue to patch the holes.
-         *
-         * @param ted TouchEventData to pre-queue
-         */
-        public void preQueueTouchEventData(TouchEventData ted) {
-            QueuedTouch newTouch = obtainQueuedTouch().set(ted);
-            if (mPreQueue == null) {
-                mPreQueue = newTouch;
-            } else {
-                QueuedTouch insertionPoint = mPreQueue;
-                while (insertionPoint.mNext != null &&
-                        insertionPoint.mNext.mSequence < newTouch.mSequence) {
-                    insertionPoint = insertionPoint.mNext;
-                }
-                newTouch.mNext = insertionPoint.mNext;
-                insertionPoint.mNext = newTouch;
-            }
-        }
-
-        private void recycleQueuedTouch(QueuedTouch qd) {
-            if (mQueuedTouchRecycleCount < MAX_RECYCLED_QUEUED_TOUCH) {
-                qd.mNext = mQueuedTouchRecycleBin;
-                mQueuedTouchRecycleBin = qd;
-                mQueuedTouchRecycleCount++;
-            }
-        }
-
-        /**
-         * Reset the touch event queue. This will dump any pending events
-         * and reset the sequence numbering.
-         */
-        public void reset() {
-            mNextTouchSequence = Long.MIN_VALUE + 1;
-            mLastHandledTouchSequence = Long.MIN_VALUE;
-            mIgnoreUntilSequence = Long.MIN_VALUE + 1;
-            while (mTouchEventQueue != null) {
-                QueuedTouch recycleMe = mTouchEventQueue;
-                mTouchEventQueue = mTouchEventQueue.mNext;
-                recycleQueuedTouch(recycleMe);
-            }
-            while (mPreQueue != null) {
-                QueuedTouch recycleMe = mPreQueue;
-                mPreQueue = mPreQueue.mNext;
-                recycleQueuedTouch(recycleMe);
-            }
-        }
-
-        /**
-         * Return the next valid sequence number for tagging incoming touch events.
-         * @return The next touch event sequence number
-         */
-        public long nextTouchSequence() {
-            return mNextTouchSequence++;
-        }
-
-        /**
-         * Enqueue a touch event in the form of TouchEventData.
-         * The sequence number will be read from the mSequence field of the argument.
-         *
-         * If the touch event's sequence number is the next in line to be processed, it will
-         * be handled before this method returns. Any subsequent events that have already
-         * been queued will also be processed in their proper order.
-         *
-         * @param ted Touch data to be processed in order.
-         * @return true if the event was processed before returning, false if it was just enqueued.
-         */
-        public boolean enqueueTouchEvent(TouchEventData ted) {
-            // Remove from the pre-queue if present
-            QueuedTouch preQueue = mPreQueue;
-            if (preQueue != null) {
-                // On exiting this block, preQueue is set to the pre-queued QueuedTouch object
-                // if it was present in the pre-queue, and removed from the pre-queue itself.
-                if (preQueue.mSequence == ted.mSequence) {
-                    mPreQueue = preQueue.mNext;
-                } else {
-                    QueuedTouch prev = preQueue;
-                    preQueue = null;
-                    while (prev.mNext != null) {
-                        if (prev.mNext.mSequence == ted.mSequence) {
-                            preQueue = prev.mNext;
-                            prev.mNext = preQueue.mNext;
-                            break;
-                        } else {
-                            prev = prev.mNext;
-                        }
-                    }
-                }
-            }
-
-            if (ted.mSequence < mLastHandledTouchSequence) {
-                // Stale event and we already moved on; drop it. (Should not be common.)
-                Log.w(LOGTAG, "Stale touch event " + MotionEvent.actionToString(ted.mAction) +
-                        " received from webcore; ignoring");
-                return false;
-            }
-
-            if (dropStaleGestures(ted.mMotionEvent, ted.mSequence)) {
-                return false;
-            }
-
-            // dropStaleGestures above might have fast-forwarded us to
-            // an event we have already.
-            runNextQueuedEvents();
-
-            if (mLastHandledTouchSequence + 1 == ted.mSequence) {
-                if (preQueue != null) {
-                    recycleQueuedTouch(preQueue);
-                    preQueue = null;
-                }
-                handleQueuedTouchEventData(ted);
-
-                mLastHandledTouchSequence++;
-
-                // Do we have any more? Run them if so.
-                runNextQueuedEvents();
-            } else {
-                // Reuse the pre-queued object if we had it.
-                QueuedTouch qd = preQueue != null ? preQueue : obtainQueuedTouch().set(ted);
-                mTouchEventQueue = mTouchEventQueue == null ? qd : mTouchEventQueue.add(qd);
-            }
-            return true;
-        }
-
-        /**
-         * Enqueue a touch event in the form of a MotionEvent from the framework.
-         *
-         * If the touch event's sequence number is the next in line to be processed, it will
-         * be handled before this method returns. Any subsequent events that have already
-         * been queued will also be processed in their proper order.
-         *
-         * @param ev MotionEvent to be processed in order
-         */
-        public void enqueueTouchEvent(MotionEvent ev) {
-            final long sequence = nextTouchSequence();
-
-            if (dropStaleGestures(ev, sequence)) {
-                return;
-            }
-
-            // dropStaleGestures above might have fast-forwarded us to
-            // an event we have already.
-            runNextQueuedEvents();
-
-            if (mLastHandledTouchSequence + 1 == sequence) {
-                handleQueuedMotionEvent(ev);
-
-                mLastHandledTouchSequence++;
-
-                // Do we have any more? Run them if so.
-                runNextQueuedEvents();
-            } else {
-                QueuedTouch qd = obtainQueuedTouch().set(ev, sequence);
-                mTouchEventQueue = mTouchEventQueue == null ? qd : mTouchEventQueue.add(qd);
-            }
-        }
-
-        private void runNextQueuedEvents() {
-            QueuedTouch qd = mTouchEventQueue;
-            while (qd != null && qd.mSequence == mLastHandledTouchSequence + 1) {
-                handleQueuedTouch(qd);
-                QueuedTouch recycleMe = qd;
-                qd = qd.mNext;
-                recycleQueuedTouch(recycleMe);
-                mLastHandledTouchSequence++;
-            }
-            mTouchEventQueue = qd;
-        }
-
-        private boolean dropStaleGestures(MotionEvent ev, long sequence) {
-            if (ev != null && ev.getAction() == MotionEvent.ACTION_MOVE && !mConfirmMove) {
-                // This is to make sure that we don't attempt to process a tap
-                // or long press when webkit takes too long to get back to us.
-                // The movement will be properly confirmed when we process the
-                // enqueued event later.
-                final int dx = Math.round(ev.getX()) - mLastTouchX;
-                final int dy = Math.round(ev.getY()) - mLastTouchY;
-                if (dx * dx + dy * dy > mTouchSlopSquare) {
-                    mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
-                    mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-                }
-            }
-
-            if (mTouchEventQueue == null) {
-                return sequence <= mLastHandledTouchSequence;
-            }
-
-            // If we have a new down event and it's been a while since the last event
-            // we saw, catch up as best we can and keep going.
-            if (ev != null && ev.getAction() == MotionEvent.ACTION_DOWN) {
-                long eventTime = ev.getEventTime();
-                long lastHandledEventTime = mLastEventTime;
-                if (eventTime > lastHandledEventTime + QUEUED_GESTURE_TIMEOUT) {
-                    Log.w(LOGTAG, "Got ACTION_DOWN but still waiting on stale event. " +
-                            "Catching up.");
-                    runQueuedAndPreQueuedEvents();
-
-                    // Drop leftovers that we truly don't have.
-                    QueuedTouch qd = mTouchEventQueue;
-                    while (qd != null && qd.mSequence < sequence) {
-                        QueuedTouch recycleMe = qd;
-                        qd = qd.mNext;
-                        recycleQueuedTouch(recycleMe);
-                    }
-                    mTouchEventQueue = qd;
-                    mLastHandledTouchSequence = sequence - 1;
-                }
-            }
-
-            if (mIgnoreUntilSequence - 1 > mLastHandledTouchSequence) {
-                QueuedTouch qd = mTouchEventQueue;
-                while (qd != null && qd.mSequence < mIgnoreUntilSequence) {
-                    QueuedTouch recycleMe = qd;
-                    qd = qd.mNext;
-                    recycleQueuedTouch(recycleMe);
-                }
-                mTouchEventQueue = qd;
-                mLastHandledTouchSequence = mIgnoreUntilSequence - 1;
-            }
-
-            if (mPreQueue != null) {
-                // Drop stale prequeued events
-                QueuedTouch qd = mPreQueue;
-                while (qd != null && qd.mSequence < mIgnoreUntilSequence) {
-                    QueuedTouch recycleMe = qd;
-                    qd = qd.mNext;
-                    recycleQueuedTouch(recycleMe);
-                }
-                mPreQueue = qd;
-            }
-
-            return sequence <= mLastHandledTouchSequence;
-        }
-
-        private void handleQueuedTouch(QueuedTouch qt) {
-            if (qt.mTed != null) {
-                handleQueuedTouchEventData(qt.mTed);
-            } else {
-                handleQueuedMotionEvent(qt.mEvent);
-                qt.mEvent.recycle();
-            }
-        }
-
-        private void handleQueuedMotionEvent(MotionEvent ev) {
-            mLastEventTime = ev.getEventTime();
-            int action = ev.getActionMasked();
-            if (ev.getPointerCount() > 1) {  // Multi-touch
-                handleMultiTouchInWebView(ev);
-            } else {
-                final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
-                if (detector != null && mPreventDefault != PREVENT_DEFAULT_YES) {
-                    // ScaleGestureDetector needs a consistent event stream to operate properly.
-                    // It won't take any action with fewer than two pointers, but it needs to
-                    // update internal bookkeeping state.
-                    detector.onTouchEvent(ev);
-                }
-
-                handleTouchEventCommon(ev, action, Math.round(ev.getX()), Math.round(ev.getY()));
-            }
-        }
-
-        private void handleQueuedTouchEventData(TouchEventData ted) {
-            if (ted.mMotionEvent != null) {
-                mLastEventTime = ted.mMotionEvent.getEventTime();
-            }
-            if (!ted.mReprocess) {
-                if (ted.mAction == MotionEvent.ACTION_DOWN
-                        && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) {
-                    // if prevent default is called from WebCore, UI
-                    // will not handle the rest of the touch events any
-                    // more.
-                    mPreventDefault = ted.mNativeResult ? PREVENT_DEFAULT_YES
-                            : PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN;
-                } else if (ted.mAction == MotionEvent.ACTION_MOVE
-                        && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
-                    // the return for the first ACTION_MOVE will decide
-                    // whether UI will handle touch or not. Currently no
-                    // support for alternating prevent default
-                    mPreventDefault = ted.mNativeResult ? PREVENT_DEFAULT_YES
-                            : PREVENT_DEFAULT_NO;
-                }
-                if (mPreventDefault == PREVENT_DEFAULT_YES) {
-                    mTouchHighlightRegion.setEmpty();
-                }
-            } else {
-                if (ted.mPoints.length > 1) {  // multi-touch
-                    if (!ted.mNativeResult && mPreventDefault != PREVENT_DEFAULT_YES) {
-                        mPreventDefault = PREVENT_DEFAULT_NO;
-                        handleMultiTouchInWebView(ted.mMotionEvent);
-                    } else {
-                        mPreventDefault = PREVENT_DEFAULT_YES;
-                    }
-                    return;
-                }
-
-                // prevent default is not called in WebCore, so the
-                // message needs to be reprocessed in UI
-                if (!ted.mNativeResult) {
-                    // Following is for single touch.
-                    switch (ted.mAction) {
-                        case MotionEvent.ACTION_DOWN:
-                            mLastDeferTouchX = ted.mPointsInView[0].x;
-                            mLastDeferTouchY = ted.mPointsInView[0].y;
-                            mDeferTouchMode = TOUCH_INIT_MODE;
-                            break;
-                        case MotionEvent.ACTION_MOVE: {
-                            // no snapping in defer process
-                            int x = ted.mPointsInView[0].x;
-                            int y = ted.mPointsInView[0].y;
-
-                            if (mDeferTouchMode != TOUCH_DRAG_MODE) {
-                                mDeferTouchMode = TOUCH_DRAG_MODE;
-                                mLastDeferTouchX = x;
-                                mLastDeferTouchY = y;
-                                startScrollingLayer(x, y);
-                                startDrag();
-                            }
-                            int deltaX = pinLocX((int) (getScrollX()
-                                    + mLastDeferTouchX - x))
-                                    - getScrollX();
-                            int deltaY = pinLocY((int) (getScrollY()
-                                    + mLastDeferTouchY - y))
-                                    - getScrollY();
-                            doDrag(deltaX, deltaY);
-                            if (deltaX != 0) mLastDeferTouchX = x;
-                            if (deltaY != 0) mLastDeferTouchY = y;
-                            break;
-                        }
-                        case MotionEvent.ACTION_UP:
-                        case MotionEvent.ACTION_CANCEL:
-                            if (mDeferTouchMode == TOUCH_DRAG_MODE) {
-                                // no fling in defer process
-                                mScroller.springBack(getScrollX(), getScrollY(), 0,
-                                        computeMaxScrollX(), 0,
-                                        computeMaxScrollY());
-                                invalidate();
-                                WebViewCore.resumePriority();
-                                WebViewCore.resumeUpdatePicture(mWebViewCore);
-                            }
-                            mDeferTouchMode = TOUCH_DONE_MODE;
-                            break;
-                        case WebViewCore.ACTION_DOUBLETAP:
-                            // doDoubleTap() needs mLastTouchX/Y as anchor
-                            mLastDeferTouchX = ted.mPointsInView[0].x;
-                            mLastDeferTouchY = ted.mPointsInView[0].y;
-                            mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
-                            mDeferTouchMode = TOUCH_DONE_MODE;
-                            break;
-                        case WebViewCore.ACTION_LONGPRESS:
-                            HitTestResult hitTest = getHitTestResult();
-                            if (hitTest != null && hitTest.getType()
-                                    != HitTestResult.UNKNOWN_TYPE) {
-                                performLongClick();
-                            }
-                            mDeferTouchMode = TOUCH_DONE_MODE;
-                            break;
-                    }
-                }
-            }
-        }
-    }
-
     //-------------------------------------------------------------------------
     // Methods can be called from a separate thread, like WebViewCore
     // If it needs to call the View system, it has to send message.
@@ -8056,7 +7264,7 @@
     /**
      * General handler to receive message coming from webkit thread
      */
-    class PrivateHandler extends Handler {
+    class PrivateHandler extends Handler implements WebViewInputDispatcher.UiCallbacks {
         @Override
         public void handleMessage(Message msg) {
             // exclude INVAL_RECT_MSG_ID since it is frequently output
@@ -8097,20 +7305,6 @@
                     ((Message) msg.obj).sendToTarget();
                     break;
                 }
-                case PREVENT_DEFAULT_TIMEOUT: {
-                    // if timeout happens, cancel it so that it won't block UI
-                    // to continue handling touch events
-                    if ((msg.arg1 == MotionEvent.ACTION_DOWN
-                            && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES)
-                            || (msg.arg1 == MotionEvent.ACTION_MOVE
-                            && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN)) {
-                        cancelWebCoreTouchEvent(
-                                viewToContentX(mLastTouchX + getScrollX()),
-                                viewToContentY(mLastTouchY + getScrollY()),
-                                true);
-                    }
-                    break;
-                }
                 case SCROLL_SELECT_TEXT: {
                     if (mAutoScrollX == 0 && mAutoScrollY == 0) {
                         mSentAutoScrollMessage = false;
@@ -8126,48 +7320,6 @@
                             SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
                     break;
                 }
-                case SWITCH_TO_SHORTPRESS: {
-                    if (mTouchMode == TOUCH_INIT_MODE) {
-                        mTouchMode = TOUCH_SHORTPRESS_MODE;
-                    } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
-                        mTouchMode = TOUCH_DONE_MODE;
-                    }
-                    break;
-                }
-                case SWITCH_TO_LONGPRESS: {
-                    removeTouchHighlight();
-                    if (inFullScreenMode() || mDeferTouchProcess) {
-                        TouchEventData ted = new TouchEventData();
-                        ted.mAction = WebViewCore.ACTION_LONGPRESS;
-                        ted.mIds = new int[1];
-                        ted.mIds[0] = 0;
-                        ted.mPoints = new Point[1];
-                        ted.mPoints[0] = new Point(viewToContentX(mLastTouchX + getScrollX()),
-                                                   viewToContentY(mLastTouchY + getScrollY()));
-                        ted.mPointsInView = new Point[1];
-                        ted.mPointsInView[0] = new Point(mLastTouchX, mLastTouchY);
-                        // metaState for long press is tricky. Should it be the
-                        // state when the press started or when the press was
-                        // released? Or some intermediary key state? For
-                        // simplicity for now, we don't set it.
-                        ted.mMetaState = 0;
-                        ted.mReprocess = mDeferTouchProcess;
-                        ted.mNativeLayer = nativeScrollableLayer(
-                                ted.mPoints[0].x, ted.mPoints[0].y,
-                                ted.mNativeLayerRect, null);
-                        ted.mSequence = mTouchEventQueue.nextTouchSequence();
-                        mTouchEventQueue.preQueueTouchEventData(ted);
-                        mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
-                    } else if (mPreventDefault != PREVENT_DEFAULT_YES) {
-                        mTouchMode = TOUCH_DONE_MODE;
-                        performLongClick();
-                    }
-                    break;
-                }
-                case RELEASE_SINGLE_TAP: {
-                    doShortPress();
-                    break;
-                }
                 case SCROLL_TO_MSG_ID: {
                     // arg1 = animate, arg2 = onlyIfImeIsShowing
                     // obj = Point(x, y)
@@ -8225,6 +7377,8 @@
                     if (mIsPaused) {
                         nativeSetPauseDrawing(mNativeClass, true);
                     }
+                    mInputDispatcher = new WebViewInputDispatcher(this,
+                            mWebViewCore.getInputDispatcherCallbacks());
                     break;
                 case UPDATE_TEXTFIELD_TEXT_MSG_ID:
                     // Make sure that the textfield is currently focused
@@ -8281,20 +7435,7 @@
                     break;
 
                 case WEBCORE_NEED_TOUCH_EVENTS:
-                    mForwardTouchEvents = (msg.arg1 != 0);
-                    break;
-
-                case PREVENT_TOUCH_ID:
-                    if (inFullScreenMode()) {
-                        break;
-                    }
-                    TouchEventData ted = (TouchEventData) msg.obj;
-
-                    if (mTouchEventQueue.enqueueTouchEvent(ted)) {
-                        // WebCore is responding to us; remove pending timeout.
-                        // It will be re-posted when needed.
-                        removeMessages(PREVENT_DEFAULT_TIMEOUT);
-                    }
+                    mInputDispatcher.setWebKitWantsTouchEvents(msg.arg1 != 0);
                     break;
 
                 case REQUEST_KEYBOARD:
@@ -8559,6 +7700,21 @@
                     break;
             }
         }
+
+        @Override
+        public Looper getUiLooper() {
+            return getLooper();
+        }
+
+        @Override
+        public void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
+            onHandleUiEvent(event, eventType, flags);
+        }
+
+        @Override
+        public Context getContext() {
+            return WebViewClassic.this.getContext();
+        }
     }
 
     private void setHitTestTypeFromUrl(String url) {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 2caec01..dd05e8c 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -40,6 +40,7 @@
 import android.view.SurfaceView;
 import android.view.View;
 import android.webkit.WebViewClassic.FocusNodeHref;
+import android.webkit.WebViewInputDispatcher.WebKitCallbacks;
 
 import junit.framework.Assert;
 
@@ -259,6 +260,10 @@
         return mBrowserFrame;
     }
 
+    public WebKitCallbacks getInputDispatcherCallbacks() {
+        return mEventHub;
+    }
+
     //-------------------------------------------------------------------------
     // Common methods
     //-------------------------------------------------------------------------
@@ -611,9 +616,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);
 
@@ -656,8 +658,7 @@
             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,
             int[] idArray, int[] xArray, int[] yArray, int count,
@@ -1020,8 +1021,8 @@
             "REQUEST_CURSOR_HREF", // = 137;
             "ADD_JS_INTERFACE", // = 138;
             "LOAD_DATA", // = 139;
-            "TOUCH_UP", // = 140;
-            "TOUCH_EVENT", // = 141;
+            "", // = 140;
+            "", // = 141;
             "SET_ACTIVE", // = 142;
             "ON_PAUSE",     // = 143
             "ON_RESUME",    // = 144
@@ -1046,7 +1047,7 @@
     /**
      * @hide
      */
-    public class EventHub {
+    public class EventHub implements WebViewInputDispatcher.WebKitCallbacks {
         // Message Ids
         static final int REVEAL_SELECTION = 96;
         static final int SCROLL_TEXT_INPUT = 99;
@@ -1067,10 +1068,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;
@@ -1091,11 +1090,6 @@
         static final int ADD_JS_INTERFACE = 138;
         static final int LOAD_DATA = 139;
 
-        // motion
-        static final int TOUCH_UP = 140;
-        // message used to pass UI touch events to WebCore
-        static final int TOUCH_EVENT = 141;
-
         // Used to tell the focus controller not to draw the blinking cursor,
         // based on whether the WebView has focus and whether the WebView's
         // cursor matches the webpage's focus.
@@ -1370,14 +1364,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;
@@ -1490,45 +1476,6 @@
                             nativeCloseIdleConnections(mNativeClass);
                             break;
 
-                        case TOUCH_UP:
-                            TouchUpData touchUpData = (TouchUpData) msg.obj;
-                            if (touchUpData.mNativeLayer != 0) {
-                                nativeScrollLayer(mNativeClass,
-                                        touchUpData.mNativeLayer,
-                                        touchUpData.mNativeLayerRect);
-                            }
-                            nativeTouchUp(mNativeClass,
-                                    touchUpData.mMoveGeneration,
-                                    touchUpData.mFrame, touchUpData.mNode,
-                                    touchUpData.mX, touchUpData.mY);
-                            break;
-
-                        case TOUCH_EVENT: {
-                            TouchEventData ted = (TouchEventData) msg.obj;
-                            final int count = ted.mPoints.length;
-                            int[] xArray = new int[count];
-                            int[] yArray = new int[count];
-                            for (int c = 0; c < count; c++) {
-                                xArray[c] = ted.mPoints[c].x;
-                                yArray[c] = ted.mPoints[c].y;
-                            }
-                            if (ted.mNativeLayer != 0) {
-                                nativeScrollLayer(mNativeClass,
-                                        ted.mNativeLayer, ted.mNativeLayerRect);
-                            }
-                            ted.mNativeResult = nativeHandleTouchEvent(
-                                    mNativeClass, ted.mAction, ted.mIds, xArray,
-                                    yArray, count, ted.mActionIndex,
-                                    ted.mMetaState);
-                            Message.obtain(
-                                    mWebViewClassic.mPrivateHandler,
-                                    WebViewClassic.PREVENT_TOUCH_ID,
-                                    ted.mAction,
-                                    ted.mNativeResult ? 1 : 0,
-                                    ted).sendToTarget();
-                            break;
-                        }
-
                         case SET_ACTIVE:
                             nativeSetFocusControllerActive(mNativeClass, msg.arg1 == 1);
                             break;
@@ -1812,6 +1759,38 @@
             }
         }
 
+        @Override
+        public Looper getWebKitLooper() {
+            return mHandler.getLooper();
+        }
+
+        @Override
+        public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
+            switch (eventType) {
+                case WebViewInputDispatcher.EVENT_TYPE_CLICK:
+                    return nativeMouseClick(mNativeClass);
+
+                case WebViewInputDispatcher.EVENT_TYPE_TOUCH: {
+                    int count = event.getPointerCount();
+                    int[] idArray = new int[count];
+                    int[] xArray = new int[count];
+                    int[] yArray = new int[count];
+                    for (int i = 0; i < count; i++) {
+                        idArray[i] = event.getPointerId(i);
+                        xArray[i] = (int) event.getX(i);
+                        yArray[i] = (int) event.getY(i);
+                    }
+                    return nativeHandleTouchEvent(mNativeClass,
+                            event.getActionMasked(),
+                            idArray, xArray, yArray, count,
+                            event.getActionIndex(), event.getMetaState());
+                }
+
+                default:
+                    return false;
+            }
+        }
+
         /**
          * Send a message internally to the queue or to the handler
          */
diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java
new file mode 100644
index 0000000..e7024d9
--- /dev/null
+++ b/core/java/android/webkit/WebViewInputDispatcher.java
@@ -0,0 +1,1151 @@
+/*
+ * 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.webkit;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * Perform asynchronous dispatch of input events in a {@link WebView}.
+ *
+ * This dispatcher is shared by the UI thread ({@link WebViewClassic}) and web kit
+ * thread ({@link WebViewCore}).  The UI thread enqueues events for
+ * processing, waits for the web kit thread to handle them, and then performs
+ * additional processing depending on the outcome.
+ *
+ * How it works:
+ *
+ * 1. The web view thread receives an input event from the input system on the UI
+ * thread in its {@link WebViewClassic#onTouchEvent} handler.  It sends the input event
+ * to the dispatcher, then immediately returns true to the input system to indicate that
+ * it will handle the event.
+ *
+ * 2. The web kit thread is notified that an event has been enqueued.  Meanwhile additional
+ * events may be enqueued from the UI thread.  In some cases, the dispatcher may decide to
+ * coalesce motion events into larger batches or to cancel events that have been
+ * sitting in the queue for too long.
+ *
+ * 3. The web kit thread wakes up and handles all input events that are waiting for it.
+ * After processing each input event, it informs the dispatcher whether the web application
+ * has decided to handle the event itself and to prevent default event handling.
+ *
+ * 4. If web kit indicates that it wants to prevent default event handling, then web kit
+ * consumes the remainder of the gesture and web view receives a cancel event if
+ * needed.  Otherwise, the web view handles the gesture on the UI thread normally.
+ *
+ * 5. If the web kit thread takes too long to handle an input event, then it loses the
+ * right to handle it.  The dispatcher synthesizes a cancellation event for web kit and
+ * then tells the web view on the UI thread to handle the event that timed out along
+ * with the rest of the gesture.
+ *
+ * One thing to keep in mind about the dispatcher is that what goes into the dispatcher
+ * is not necessarily what the web kit or UI thread will see.  As mentioned above, the
+ * dispatcher may tweak the input event stream to improve responsiveness.  Both web view and
+ * web kit are guaranteed to perceive a consistent stream of input events but
+ * they might not always see the same events (especially if one decides
+ * to prevent the other from handling a particular gesture).
+ *
+ * This implementation very deliberately does not refer to the {@link WebViewClassic}
+ * or {@link WebViewCore} classes, preferring to communicate with them only via
+ * interfaces to avoid unintentional coupling to their implementation details.
+ *
+ * Currently, the input dispatcher only handles pointer events (includes touch,
+ * hover and scroll events).  In principle, it could be extended to handle trackball
+ * and key events if needed.
+ *
+ * @hide
+ */
+final class WebViewInputDispatcher {
+    private static final String TAG = "WebViewInputDispatcher";
+    private static final boolean DEBUG = false;
+    // This enables batching of MotionEvents. It will combine multiple MotionEvents
+    // together into a single MotionEvent if more events come in while we are
+    // still waiting on the processing of a previous event.
+    // If this is set to false, we will instead opt to drop ACTION_MOVE
+    // events we cannot keep up with.
+    // TODO: If batching proves to be working well, remove this
+    private static final boolean ENABLE_EVENT_BATCHING = true;
+
+    private final Object mLock = new Object();
+
+    // Pool of queued input events.  (guarded by mLock)
+    private static final int MAX_DISPATCH_EVENT_POOL_SIZE = 10;
+    private DispatchEvent mDispatchEventPool;
+    private int mDispatchEventPoolSize;
+
+    // Posted state, tracks events posted to the dispatcher.  (guarded by mLock)
+    private final TouchStream mPostTouchStream = new TouchStream();
+    private boolean mPostSendTouchEventsToWebKit;
+    private boolean mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
+    private boolean mPostLongPressScheduled;
+    private boolean mPostClickScheduled;
+    private int mPostLastWebKitXOffset;
+    private int mPostLastWebKitYOffset;
+    private float mPostLastWebKitScale;
+
+    // State for event tracking (click, longpress, double tap, etc..)
+    private boolean mIsDoubleTapCandidate;
+    private boolean mIsTapCandidate;
+    private float mInitialDownX;
+    private float mInitialDownY;
+    private float mTouchSlopSquared;
+    private float mDoubleTapSlopSquared;
+
+    // Web kit state, tracks events observed by web kit.  (guarded by mLock)
+    private final DispatchEventQueue mWebKitDispatchEventQueue = new DispatchEventQueue();
+    private final TouchStream mWebKitTouchStream = new TouchStream();
+    private final WebKitCallbacks mWebKitCallbacks;
+    private final WebKitHandler mWebKitHandler;
+    private boolean mWebKitDispatchScheduled;
+    private boolean mWebKitTimeoutScheduled;
+    private long mWebKitTimeoutTime;
+
+    // UI state, tracks events observed by the UI.  (guarded by mLock)
+    private final DispatchEventQueue mUiDispatchEventQueue = new DispatchEventQueue();
+    private final TouchStream mUiTouchStream = new TouchStream();
+    private final UiCallbacks mUiCallbacks;
+    private final UiHandler mUiHandler;
+    private boolean mUiDispatchScheduled;
+
+    // Give up on web kit handling of input events when this timeout expires.
+    private static final long WEBKIT_TIMEOUT_MILLIS = 200;
+    private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
+    private static final int LONG_PRESS_TIMEOUT =
+            ViewConfiguration.getLongPressTimeout() + TAP_TIMEOUT;
+    private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
+
+    /**
+     * Event type: Indicates a touch event type.
+     *
+     * This event is delivered together with a {@link MotionEvent} with one of the
+     * following actions: {@link MotionEvent#ACTION_DOWN}, {@link MotionEvent#ACTION_MOVE},
+     * {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_POINTER_DOWN},
+     * {@link MotionEvent#ACTION_POINTER_UP}, {@link MotionEvent#ACTION_CANCEL}.
+     */
+    public static final int EVENT_TYPE_TOUCH = 0;
+
+    /**
+     * Event type: Indicates a hover event type.
+     *
+     * This event is delivered together with a {@link MotionEvent} with one of the
+     * following actions: {@link MotionEvent#ACTION_HOVER_ENTER},
+     * {@link MotionEvent#ACTION_HOVER_MOVE}, {@link MotionEvent#ACTION_HOVER_MOVE}.
+     */
+    public static final int EVENT_TYPE_HOVER = 1;
+
+    /**
+     * Event type: Indicates a scroll event type.
+     *
+     * This event is delivered together with a {@link MotionEvent} with action
+     * {@link MotionEvent#ACTION_SCROLL}.
+     */
+    public static final int EVENT_TYPE_SCROLL = 2;
+
+    /**
+     * Event type: Indicates a long-press event type.
+     *
+     * This event is delivered in the middle of a sequence of {@link #EVENT_TYPE_TOUCH} events.
+     * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_MOVE}
+     * that indicates the current touch coordinates of the long-press.
+     *
+     * This event is sent when the current touch gesture has been held longer than
+     * the long-press interval.
+     */
+    public static final int EVENT_TYPE_LONG_PRESS = 3;
+
+    /**
+     * Event type: Indicates a click event type.
+     *
+     * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
+     * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
+     * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
+     * that indicates the location of the click.
+     *
+     * This event is sent shortly after the end of a touch after the double-tap
+     * interval has expired to indicate a click.
+     */
+    public static final int EVENT_TYPE_CLICK = 4;
+
+    /**
+     * Event type: Indicates a double-tap event type.
+     *
+     * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
+     * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
+     * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
+     * that indicates the location of the double-tap.
+     *
+     * This event is sent immediately after a sequence of two touches separated
+     * in time by no more than the double-tap interval and separated in space
+     * by no more than the double-tap slop.
+     */
+    public static final int EVENT_TYPE_DOUBLE_TAP = 5;
+
+    /**
+     * Flag: This event is private to this queue.  Do not forward it.
+     */
+    public static final int FLAG_PRIVATE = 1 << 0;
+
+    /**
+     * Flag: This event is currently being processed by web kit.
+     * If a timeout occurs, make a copy of it before forwarding the event to another queue.
+     */
+    public static final int FLAG_WEBKIT_IN_PROGRESS = 1 << 1;
+
+    /**
+     * Flag: A timeout occurred while waiting for web kit to process this input event.
+     */
+    public static final int FLAG_WEBKIT_TIMEOUT = 1 << 2;
+
+    /**
+     * Flag: Indicates that the event was transformed for delivery to web kit.
+     * The event must be transformed back before being delivered to the UI.
+     */
+    public static final int FLAG_WEBKIT_TRANSFORMED_EVENT = 1 << 3;
+
+    public WebViewInputDispatcher(UiCallbacks uiCallbacks, WebKitCallbacks webKitCallbacks) {
+        this.mUiCallbacks = uiCallbacks;
+        mUiHandler = new UiHandler(uiCallbacks.getUiLooper());
+
+        this.mWebKitCallbacks = webKitCallbacks;
+        mWebKitHandler = new WebKitHandler(webKitCallbacks.getWebKitLooper());
+
+        ViewConfiguration config = ViewConfiguration.get(mUiCallbacks.getContext());
+        mDoubleTapSlopSquared = config.getScaledDoubleTapSlop();
+        mDoubleTapSlopSquared = (mDoubleTapSlopSquared * mDoubleTapSlopSquared);
+        mTouchSlopSquared = config.getScaledTouchSlop();
+        mTouchSlopSquared = (mTouchSlopSquared * mTouchSlopSquared);
+    }
+
+    /**
+     * Sets whether web kit wants to receive touch events.
+     *
+     * @param enable True to enable dispatching of touch events to web kit, otherwise
+     * web kit will be skipped.
+     */
+    public void setWebKitWantsTouchEvents(boolean enable) {
+        if (DEBUG) {
+            Log.d(TAG, "webkitWantsTouchEvents: " + enable);
+        }
+        synchronized (mLock) {
+            if (mPostSendTouchEventsToWebKit != enable) {
+                if (!enable) {
+                    enqueueWebKitCancelTouchEventIfNeededLocked();
+                }
+                mPostSendTouchEventsToWebKit = enable;
+            }
+        }
+    }
+
+    /**
+     * Posts a pointer event to the dispatch queue.
+     *
+     * @param event The event to post.
+     * @param webKitXOffset X offset to apply to events before dispatching them to web kit.
+     * @param webKitYOffset Y offset to apply to events before dispatching them to web kit.
+     * @param webKitScale The scale factor to apply to translated events before dispatching
+     * them to web kit.
+     * @return True if the dispatcher will handle the event, false if the event is unsupported.
+     */
+    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 (DEBUG) {
+            Log.d(TAG, "postPointerEvent: " + event);
+        }
+
+        final int action = event.getActionMasked();
+        final int eventType;
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE:
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_POINTER_DOWN:
+            case MotionEvent.ACTION_POINTER_UP:
+            case MotionEvent.ACTION_CANCEL:
+                eventType = EVENT_TYPE_TOUCH;
+                break;
+            case MotionEvent.ACTION_SCROLL:
+                eventType = EVENT_TYPE_SCROLL;
+                break;
+            case MotionEvent.ACTION_HOVER_ENTER:
+            case MotionEvent.ACTION_HOVER_MOVE:
+            case MotionEvent.ACTION_HOVER_EXIT:
+                eventType = EVENT_TYPE_HOVER;
+                break;
+            default:
+                return false; // currently unsupported event type
+        }
+
+        synchronized (mLock) {
+            // Ensure that the event is consistent and should be delivered.
+            MotionEvent eventToEnqueue = event;
+            if (eventType == EVENT_TYPE_TOUCH) {
+                eventToEnqueue = mPostTouchStream.update(event);
+                if (eventToEnqueue == null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "postPointerEvent: dropped event " + event);
+                    }
+                    unscheduleLongPressLocked();
+                    unscheduleClickLocked();
+                    return false;
+                }
+
+                if (mPostSendTouchEventsToWebKit
+                        && mPostDoNotSendTouchEventsToWebKitUntilNextGesture
+                        && action == MotionEvent.ACTION_DOWN) {
+                    // Recover from a previous web kit timeout.
+                    mPostDoNotSendTouchEventsToWebKitUntilNextGesture = false;
+                }
+            }
+
+            // Copy the event because we need to retain ownership.
+            if (eventToEnqueue == event) {
+                eventToEnqueue = event.copy();
+            }
+
+            DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, eventType, 0,
+                    webKitXOffset, webKitYOffset, webKitScale);
+            enqueueEventLocked(d);
+        }
+        return true;
+    }
+
+    private void scheduleLongPressLocked() {
+        unscheduleLongPressLocked();
+        mPostLongPressScheduled = true;
+        mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_LONG_PRESS,
+                LONG_PRESS_TIMEOUT);
+    }
+
+    private void unscheduleLongPressLocked() {
+        if (mPostLongPressScheduled) {
+            mPostLongPressScheduled = false;
+            mUiHandler.removeMessages(UiHandler.MSG_LONG_PRESS);
+        }
+    }
+
+    private void postLongPress() {
+        synchronized (mLock) {
+            if (!mPostLongPressScheduled) {
+                return;
+            }
+            mPostLongPressScheduled = false;
+
+            MotionEvent event = mPostTouchStream.getLastEvent();
+            if (event == null) {
+                return;
+            }
+
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                case MotionEvent.ACTION_MOVE:
+                case MotionEvent.ACTION_POINTER_DOWN:
+                case MotionEvent.ACTION_POINTER_UP:
+                    break;
+                default:
+                    return;
+            }
+
+            MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+            eventToEnqueue.setAction(MotionEvent.ACTION_MOVE);
+            DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_LONG_PRESS, 0,
+                    mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+            enqueueEventLocked(d);
+        }
+    }
+
+    private void scheduleClickLocked() {
+        unscheduleClickLocked();
+        mPostClickScheduled = true;
+        mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_CLICK, DOUBLE_TAP_TIMEOUT);
+    }
+
+    private void unscheduleClickLocked() {
+        if (mPostClickScheduled) {
+            mPostClickScheduled = false;
+            mUiHandler.removeMessages(UiHandler.MSG_CLICK);
+        }
+    }
+
+    private void postClick() {
+        synchronized (mLock) {
+            if (!mPostClickScheduled) {
+                return;
+            }
+            mPostClickScheduled = false;
+
+            MotionEvent event = mPostTouchStream.getLastEvent();
+            if (event == null || event.getAction() != MotionEvent.ACTION_UP) {
+                return;
+            }
+
+            MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+            DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_CLICK, 0,
+                    mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+            enqueueEventLocked(d);
+        }
+    }
+
+    private void checkForDoubleTapOnDownLocked(MotionEvent event) {
+        mIsDoubleTapCandidate = false;
+        if (!mPostClickScheduled) {
+            return;
+        }
+        int deltaX = (int) mInitialDownX - (int) event.getX();
+        int deltaY = (int) mInitialDownY - (int) event.getY();
+        if ((deltaX * deltaX + deltaY * deltaY) < mDoubleTapSlopSquared) {
+            unscheduleClickLocked();
+            mIsDoubleTapCandidate = true;
+        }
+    }
+
+    private boolean isClickCandidateLocked(MotionEvent event) {
+        if (event == null
+                || event.getActionMasked() != MotionEvent.ACTION_UP
+                || !mIsTapCandidate) {
+            return false;
+        }
+        long downDuration = event.getEventTime() - event.getDownTime();
+        return downDuration < TAP_TIMEOUT;
+    }
+
+    private void enqueueDoubleTapLocked(MotionEvent event) {
+        unscheduleClickLocked();
+        MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+        DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_DOUBLE_TAP, 0,
+                mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+        enqueueEventLocked(d);
+        mIsDoubleTapCandidate = false;
+    }
+
+    private void checkForSlopLocked(MotionEvent event) {
+        if (!mIsTapCandidate) {
+            return;
+        }
+        int deltaX = (int) mInitialDownX - (int) event.getX();
+        int deltaY = (int) mInitialDownY - (int) event.getY();
+        if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquared) {
+            unscheduleLongPressLocked();
+            mIsTapCandidate = false;
+        }
+    }
+
+    private void updateStateTrackersLocked(DispatchEvent d, MotionEvent event) {
+        mPostLastWebKitXOffset = d.mWebKitXOffset;
+        mPostLastWebKitYOffset = d.mWebKitYOffset;
+        mPostLastWebKitScale = d.mWebKitScale;
+        int action = event != null ? event.getAction() : MotionEvent.ACTION_CANCEL;
+        if (d.mEventType != EVENT_TYPE_TOUCH) {
+            return;
+        }
+
+        if (action == MotionEvent.ACTION_CANCEL
+                || event.getPointerCount() > 1) {
+            unscheduleLongPressLocked();
+            unscheduleClickLocked();
+            mIsDoubleTapCandidate = false;
+            mIsTapCandidate = false;
+        } else if (action == MotionEvent.ACTION_DOWN) {
+            checkForDoubleTapOnDownLocked(event);
+            scheduleLongPressLocked();
+            mIsTapCandidate = true;
+            mInitialDownX = event.getX();
+            mInitialDownY = event.getY();
+        } else if (action == MotionEvent.ACTION_UP) {
+            unscheduleLongPressLocked();
+            if (isClickCandidateLocked(event)) {
+                if (mIsDoubleTapCandidate) {
+                    enqueueDoubleTapLocked(event);
+                } else {
+                    scheduleClickLocked();
+                }
+            }
+        } else if (action == MotionEvent.ACTION_MOVE) {
+            checkForSlopLocked(event);
+        }
+    }
+
+    /**
+     * Dispatches pending web kit events.
+     * Must only be called from the web kit thread.
+     *
+     * This method may be used to flush the queue of pending input events
+     * immediately.  This method may help to reduce input dispatch latency
+     * if called before certain expensive operations such as drawing.
+     */
+    public void dispatchWebKitEvents() {
+        dispatchWebKitEvents(false);
+    }
+
+    private void dispatchWebKitEvents(boolean calledFromHandler) {
+        for (;;) {
+            // Get the next event, but leave it in the queue so we can move it to the UI
+            // queue if a timeout occurs.
+            DispatchEvent d;
+            MotionEvent event;
+            final int eventType;
+            int flags;
+            synchronized (mLock) {
+                if (!ENABLE_EVENT_BATCHING) {
+                    drainStaleWebKitEventsLocked();
+                }
+                d = mWebKitDispatchEventQueue.mHead;
+                if (d == null) {
+                    if (mWebKitDispatchScheduled) {
+                        mWebKitDispatchScheduled = false;
+                        if (!calledFromHandler) {
+                            mWebKitHandler.removeMessages(
+                                    WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
+                        }
+                    }
+                    return;
+                }
+
+                event = d.mEvent;
+                if (event != null) {
+                    event.offsetLocation(d.mWebKitXOffset, d.mWebKitYOffset);
+                    event.scale(d.mWebKitScale);
+                    d.mFlags |= FLAG_WEBKIT_TRANSFORMED_EVENT;
+                }
+
+                eventType = d.mEventType;
+                if (eventType == EVENT_TYPE_TOUCH) {
+                    event = mWebKitTouchStream.update(event);
+                    if (DEBUG && event == null && d.mEvent != null) {
+                        Log.d(TAG, "dispatchWebKitEvents: dropped event " + d.mEvent);
+                    }
+                }
+
+                d.mFlags |= FLAG_WEBKIT_IN_PROGRESS;
+                flags = d.mFlags;
+            }
+
+            // Handle the event.
+            final boolean preventDefault;
+            if (event == null) {
+                preventDefault = false;
+            } else {
+                preventDefault = dispatchWebKitEvent(event, eventType, flags);
+            }
+
+            synchronized (mLock) {
+                flags = d.mFlags;
+                d.mFlags = flags & ~FLAG_WEBKIT_IN_PROGRESS;
+                boolean recycleEvent = event != d.mEvent;
+
+                if ((flags & FLAG_WEBKIT_TIMEOUT) != 0) {
+                    // A timeout occurred!
+                    recycleDispatchEventLocked(d);
+                } else {
+                    // Web kit finished in a timely manner.  Dequeue the event.
+                    assert mWebKitDispatchEventQueue.mHead == d;
+                    mWebKitDispatchEventQueue.dequeue();
+
+                    updateWebKitTimeoutLocked();
+
+                    if ((flags & FLAG_PRIVATE) != 0) {
+                        // Event was intended for web kit only.  All done.
+                        recycleDispatchEventLocked(d);
+                    } else if (preventDefault) {
+                        // Web kit has decided to consume the event!
+                        if (d.mEventType == EVENT_TYPE_TOUCH) {
+                            enqueueUiCancelTouchEventIfNeededLocked();
+                        }
+                    } else {
+                        // Web kit is being friendly.  Pass the event to the UI.
+                        enqueueUiEventUnbatchedLocked(d);
+                    }
+                }
+
+                if (event != null && recycleEvent) {
+                    event.recycle();
+                }
+            }
+        }
+    }
+
+    // Runs on web kit thread.
+    private boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
+        if (DEBUG) {
+            Log.d(TAG, "dispatchWebKitEvent: event=" + event
+                    + ", eventType=" + eventType + ", flags=" + flags);
+        }
+        boolean preventDefault = mWebKitCallbacks.dispatchWebKitEvent(
+                event, eventType, flags);
+        if (DEBUG) {
+            Log.d(TAG, "dispatchWebKitEvent: preventDefault=" + preventDefault);
+        }
+        return preventDefault;
+    }
+
+    private boolean isMoveEventLocked(DispatchEvent d) {
+        return d.mEvent != null
+                && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
+    }
+
+    private void drainStaleWebKitEventsLocked() {
+        DispatchEvent d = mWebKitDispatchEventQueue.mHead;
+        while (d != null && d.mNext != null
+                && isMoveEventLocked(d)
+                && isMoveEventLocked(d.mNext)) {
+            DispatchEvent next = d.mNext;
+            skipWebKitEventLocked(d);
+            d = next;
+        }
+        mWebKitDispatchEventQueue.mHead = d;
+    }
+
+    // Runs on UI thread in response to the web kit thread appearing to be unresponsive.
+    private void handleWebKitTimeout() {
+        synchronized (mLock) {
+            if (!mWebKitTimeoutScheduled) {
+                return;
+            }
+            mWebKitTimeoutScheduled = false;
+
+            if (DEBUG) {
+                Log.d(TAG, "handleWebKitTimeout: timeout occurred!");
+            }
+
+            // Drain the web kit event queue.
+            DispatchEvent d = mWebKitDispatchEventQueue.dequeueList();
+
+            // If web kit was processing an event (must be at the head of the list because
+            // it can only do one at a time), then clone it or ignore it.
+            if ((d.mFlags & FLAG_WEBKIT_IN_PROGRESS) != 0) {
+                d.mFlags |= FLAG_WEBKIT_TIMEOUT;
+                if ((d.mFlags & FLAG_PRIVATE) != 0) {
+                    d = d.mNext; // the event is private to web kit, ignore it
+                } else {
+                    d = copyDispatchEventLocked(d);
+                    d.mFlags &= ~FLAG_WEBKIT_IN_PROGRESS;
+                }
+            }
+
+            // Enqueue all non-private events for handling by the UI thread.
+            while (d != null) {
+                DispatchEvent next = d.mNext;
+                skipWebKitEventLocked(d);
+                d = next;
+            }
+
+            // Tell web kit to cancel all pending touches.
+            // This also prevents us from sending web kit any more touches until the
+            // next gesture begins.  (As required to ensure touch event stream consistency.)
+            enqueueWebKitCancelTouchEventIfNeededLocked();
+        }
+    }
+
+    private void skipWebKitEventLocked(DispatchEvent d) {
+        d.mNext = null;
+        if ((d.mFlags & FLAG_PRIVATE) != 0) {
+            recycleDispatchEventLocked(d);
+        } else {
+            d.mFlags |= FLAG_WEBKIT_TIMEOUT;
+            enqueueUiEventUnbatchedLocked(d);
+        }
+    }
+
+    /**
+     * Dispatches pending UI events.
+     * Must only be called from the UI thread.
+     *
+     * This method may be used to flush the queue of pending input events
+     * immediately.  This method may help to reduce input dispatch latency
+     * if called before certain expensive operations such as drawing.
+     */
+    public void dispatchUiEvents() {
+        dispatchUiEvents(false);
+    }
+
+    private void dispatchUiEvents(boolean calledFromHandler) {
+        for (;;) {
+            MotionEvent event;
+            final int eventType;
+            final int flags;
+            synchronized (mLock) {
+                DispatchEvent d = mUiDispatchEventQueue.dequeue();
+                if (d == null) {
+                    if (mUiDispatchScheduled) {
+                        mUiDispatchScheduled = false;
+                        if (!calledFromHandler) {
+                            mUiHandler.removeMessages(UiHandler.MSG_DISPATCH_UI_EVENTS);
+                        }
+                    }
+                    return;
+                }
+
+                event = d.mEvent;
+                if (event != null && (d.mFlags & FLAG_WEBKIT_TRANSFORMED_EVENT) != 0) {
+                    event.scale(1.0f / d.mWebKitScale);
+                    event.offsetLocation(-d.mWebKitXOffset, -d.mWebKitYOffset);
+                    d.mFlags &= ~FLAG_WEBKIT_TRANSFORMED_EVENT;
+                }
+
+                eventType = d.mEventType;
+                if (eventType == EVENT_TYPE_TOUCH) {
+                    event = mUiTouchStream.update(event);
+                    if (DEBUG && event == null && d.mEvent != null) {
+                        Log.d(TAG, "dispatchUiEvents: dropped event " + d.mEvent);
+                    }
+                }
+
+                flags = d.mFlags;
+
+                updateStateTrackersLocked(d, event);
+                if (event == d.mEvent) {
+                    d.mEvent = null; // retain ownership of event, don't recycle it yet
+                }
+                recycleDispatchEventLocked(d);
+            }
+
+            // Handle the event.
+            if (event != null) {
+                dispatchUiEvent(event, eventType, flags);
+                event.recycle();
+            }
+        }
+    }
+
+    // Runs on UI thread.
+    private void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
+        if (DEBUG) {
+            Log.d(TAG, "dispatchUiEvent: event=" + event
+                    + ", eventType=" + eventType + ", flags=" + flags);
+        }
+        mUiCallbacks.dispatchUiEvent(event, eventType, flags);
+    }
+
+    private void enqueueEventLocked(DispatchEvent d) {
+        if (!shouldSkipWebKit(d.mEventType)) {
+            enqueueWebKitEventLocked(d);
+        } else {
+            enqueueUiEventLocked(d);
+        }
+    }
+
+    private boolean shouldSkipWebKit(int eventType) {
+        switch (eventType) {
+            case EVENT_TYPE_CLICK:
+            case EVENT_TYPE_HOVER:
+            case EVENT_TYPE_SCROLL:
+                return false;
+            case EVENT_TYPE_TOUCH:
+                return !mPostSendTouchEventsToWebKit
+                        || mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
+        }
+        return true;
+    }
+
+    private void enqueueWebKitCancelTouchEventIfNeededLocked() {
+        // We want to cancel touch events that were delivered to web kit.
+        // Enqueue a null event at the end of the queue if needed.
+        if (mWebKitTouchStream.isCancelNeeded() || !mWebKitDispatchEventQueue.isEmpty()) {
+            DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
+                    0, 0, 1.0f);
+            enqueueWebKitEventUnbatchedLocked(d);
+            mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
+        }
+    }
+
+    private void enqueueWebKitEventLocked(DispatchEvent d) {
+        if (batchEventLocked(d, mWebKitDispatchEventQueue.mTail)) {
+            if (DEBUG) {
+                Log.d(TAG, "enqueueWebKitEventLocked: batched event " + d.mEvent);
+            }
+            recycleDispatchEventLocked(d);
+        } else {
+            enqueueWebKitEventUnbatchedLocked(d);
+        }
+    }
+
+    private void enqueueWebKitEventUnbatchedLocked(DispatchEvent d) {
+        if (DEBUG) {
+            Log.d(TAG, "enqueueWebKitEventUnbatchedLocked: enqueued event " + d.mEvent);
+        }
+        mWebKitDispatchEventQueue.enqueue(d);
+        scheduleWebKitDispatchLocked();
+        updateWebKitTimeoutLocked();
+    }
+
+    private void scheduleWebKitDispatchLocked() {
+        if (!mWebKitDispatchScheduled) {
+            mWebKitHandler.sendEmptyMessage(WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
+            mWebKitDispatchScheduled = true;
+        }
+    }
+
+    private void updateWebKitTimeoutLocked() {
+        DispatchEvent d = mWebKitDispatchEventQueue.mHead;
+        if (d != null && mWebKitTimeoutScheduled && mWebKitTimeoutTime == d.mTimeoutTime) {
+            return;
+        }
+        if (mWebKitTimeoutScheduled) {
+            mUiHandler.removeMessages(UiHandler.MSG_WEBKIT_TIMEOUT);
+            mWebKitTimeoutScheduled = false;
+        }
+        if (d != null) {
+            mUiHandler.sendEmptyMessageAtTime(UiHandler.MSG_WEBKIT_TIMEOUT, d.mTimeoutTime);
+            mWebKitTimeoutScheduled = true;
+            mWebKitTimeoutTime = d.mTimeoutTime;
+        }
+    }
+
+    private void enqueueUiCancelTouchEventIfNeededLocked() {
+        // We want to cancel touch events that were delivered to the UI.
+        // Enqueue a null event at the end of the queue if needed.
+        if (mUiTouchStream.isCancelNeeded() || !mUiDispatchEventQueue.isEmpty()) {
+            DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
+                    0, 0, 1.0f);
+            enqueueUiEventUnbatchedLocked(d);
+        }
+    }
+
+    private void enqueueUiEventLocked(DispatchEvent d) {
+        if (batchEventLocked(d, mUiDispatchEventQueue.mTail)) {
+            if (DEBUG) {
+                Log.d(TAG, "enqueueUiEventLocked: batched event " + d.mEvent);
+            }
+            recycleDispatchEventLocked(d);
+        } else {
+            enqueueUiEventUnbatchedLocked(d);
+        }
+    }
+
+    private void enqueueUiEventUnbatchedLocked(DispatchEvent d) {
+        if (DEBUG) {
+            Log.d(TAG, "enqueueUiEventUnbatchedLocked: enqueued event " + d.mEvent);
+        }
+        mUiDispatchEventQueue.enqueue(d);
+        scheduleUiDispatchLocked();
+    }
+
+    private void scheduleUiDispatchLocked() {
+        if (!mUiDispatchScheduled) {
+            mUiHandler.sendEmptyMessage(UiHandler.MSG_DISPATCH_UI_EVENTS);
+            mUiDispatchScheduled = true;
+        }
+    }
+
+    private boolean batchEventLocked(DispatchEvent in, DispatchEvent tail) {
+        if (!ENABLE_EVENT_BATCHING) {
+            return false;
+        }
+        if (tail != null && tail.mEvent != null && in.mEvent != null
+                && in.mEventType == tail.mEventType
+                && in.mFlags == tail.mFlags
+                && in.mWebKitXOffset == tail.mWebKitXOffset
+                && in.mWebKitYOffset == tail.mWebKitYOffset
+                && in.mWebKitScale == tail.mWebKitScale) {
+            return tail.mEvent.addBatch(in.mEvent);
+        }
+        return false;
+    }
+
+    private DispatchEvent obtainDispatchEventLocked(MotionEvent event,
+            int eventType, int flags, int webKitXOffset, int webKitYOffset, float webKitScale) {
+        DispatchEvent d = obtainUninitializedDispatchEventLocked();
+        d.mEvent = event;
+        d.mEventType = eventType;
+        d.mFlags = flags;
+        d.mTimeoutTime = SystemClock.uptimeMillis() + WEBKIT_TIMEOUT_MILLIS;
+        d.mWebKitXOffset = webKitXOffset;
+        d.mWebKitYOffset = webKitYOffset;
+        d.mWebKitScale = webKitScale;
+        if (DEBUG) {
+            Log.d(TAG, "Timeout time: " + (d.mTimeoutTime - SystemClock.uptimeMillis()));
+        }
+        return d;
+    }
+
+    private DispatchEvent copyDispatchEventLocked(DispatchEvent d) {
+        DispatchEvent copy = obtainUninitializedDispatchEventLocked();
+        if (d.mEvent != null) {
+            copy.mEvent = d.mEvent.copy();
+        }
+        copy.mEventType = d.mEventType;
+        copy.mFlags = d.mFlags;
+        copy.mTimeoutTime = d.mTimeoutTime;
+        copy.mWebKitXOffset = d.mWebKitXOffset;
+        copy.mWebKitYOffset = d.mWebKitYOffset;
+        copy.mWebKitScale = d.mWebKitScale;
+        copy.mNext = d.mNext;
+        return copy;
+    }
+
+    private DispatchEvent obtainUninitializedDispatchEventLocked() {
+        DispatchEvent d = mDispatchEventPool;
+        if (d != null) {
+            mDispatchEventPoolSize -= 1;
+            mDispatchEventPool = d.mNext;
+            d.mNext = null;
+        } else {
+            d = new DispatchEvent();
+        }
+        return d;
+    }
+
+    private void recycleDispatchEventLocked(DispatchEvent d) {
+        if (d.mEvent != null) {
+            d.mEvent.recycle();
+            d.mEvent = null;
+        }
+
+        if (mDispatchEventPoolSize < MAX_DISPATCH_EVENT_POOL_SIZE) {
+            mDispatchEventPoolSize += 1;
+            d.mNext = mDispatchEventPool;
+            mDispatchEventPool = d;
+        }
+    }
+
+    /* Implemented by {@link WebViewClassic} to perform operations on the UI thread. */
+    public static interface UiCallbacks {
+        /**
+         * Gets the UI thread's looper.
+         * @return The looper.
+         */
+        public Looper getUiLooper();
+
+        /**
+         * Gets the UI's context
+         * @return The context
+         */
+        public Context getContext();
+
+        /**
+         * Dispatches an event to the UI.
+         * @param event The event.
+         * @param eventType The event type.
+         * @param flags The event's dispatch flags.
+         */
+        public void dispatchUiEvent(MotionEvent event, int eventType, int flags);
+    }
+
+    /* Implemented by {@link WebViewCore} to perform operations on the web kit thread. */
+    public static interface WebKitCallbacks {
+        /**
+         * Gets the web kit thread's looper.
+         * @return The looper.
+         */
+        public Looper getWebKitLooper();
+
+        /**
+         * Dispatches an event to web kit.
+         * @param event The event.
+         * @param eventType The event type.
+         * @param flags The event's dispatch flags.
+         * @return True if web kit wants to prevent default event handling.
+         */
+        public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags);
+    }
+
+    // Runs on UI thread.
+    private final class UiHandler extends Handler {
+        public static final int MSG_DISPATCH_UI_EVENTS = 1;
+        public static final int MSG_WEBKIT_TIMEOUT = 2;
+        public static final int MSG_LONG_PRESS = 3;
+        public static final int MSG_CLICK = 4;
+
+        public UiHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DISPATCH_UI_EVENTS:
+                    dispatchUiEvents(true);
+                    break;
+                case MSG_WEBKIT_TIMEOUT:
+                    handleWebKitTimeout();
+                    break;
+                case MSG_LONG_PRESS:
+                    postLongPress();
+                    break;
+                case MSG_CLICK:
+                    postClick();
+                    break;
+                default:
+                    throw new IllegalStateException("Unknown message type: " + msg.what);
+            }
+        }
+    }
+
+    // Runs on web kit thread.
+    private final class WebKitHandler extends Handler {
+        public static final int MSG_DISPATCH_WEBKIT_EVENTS = 1;
+
+        public WebKitHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DISPATCH_WEBKIT_EVENTS:
+                    dispatchWebKitEvents(true);
+                    break;
+                default:
+                    throw new IllegalStateException("Unknown message type: " + msg.what);
+            }
+        }
+    }
+
+    private static final class DispatchEvent {
+        public DispatchEvent mNext;
+
+        public MotionEvent mEvent;
+        public int mEventType;
+        public int mFlags;
+        public long mTimeoutTime;
+        public int mWebKitXOffset;
+        public int mWebKitYOffset;
+        public float mWebKitScale;
+    }
+
+    private static final class DispatchEventQueue {
+        public DispatchEvent mHead;
+        public DispatchEvent mTail;
+
+        public boolean isEmpty() {
+            return mHead != null;
+        }
+
+        public void enqueue(DispatchEvent d) {
+            if (mHead == null) {
+                mHead = d;
+                mTail = d;
+            } else {
+                mTail.mNext = d;
+                mTail = d;
+            }
+        }
+
+        public DispatchEvent dequeue() {
+            DispatchEvent d = mHead;
+            if (d != null) {
+                DispatchEvent next = d.mNext;
+                if (next == null) {
+                    mHead = null;
+                    mTail = null;
+                } else {
+                    mHead = next;
+                    d.mNext = null;
+                }
+            }
+            return d;
+        }
+
+        public DispatchEvent dequeueList() {
+            DispatchEvent d = mHead;
+            if (d != null) {
+                mHead = null;
+                mTail = null;
+            }
+            return d;
+        }
+    }
+
+    /**
+     * Keeps track of a stream of touch events so that we can discard touch
+     * events that would make the stream inconsistent.
+     */
+    private static final class TouchStream {
+        private MotionEvent mLastEvent;
+
+        /**
+         * Gets the last touch event that was delivered.
+         * @return The last touch event, or null if none.
+         */
+        public MotionEvent getLastEvent() {
+            return mLastEvent;
+        }
+
+        /**
+         * Updates the touch event stream.
+         * @param event The event that we intend to send, or null to cancel the
+         * touch event stream.
+         * @return The event that we should actually send, or null if no event should
+         * be sent because the proposed event would make the stream inconsistent.
+         */
+        public MotionEvent update(MotionEvent event) {
+            if (event == null) {
+                if (isCancelNeeded()) {
+                    event = mLastEvent;
+                    if (event != null) {
+                        event.setAction(MotionEvent.ACTION_CANCEL);
+                        mLastEvent = null;
+                    }
+                }
+                return event;
+            }
+
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_MOVE:
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_POINTER_DOWN:
+                case MotionEvent.ACTION_POINTER_UP:
+                    if (mLastEvent == null
+                            || mLastEvent.getAction() == MotionEvent.ACTION_UP) {
+                        return null;
+                    }
+                    updateLastEvent(event);
+                    return event;
+
+                case MotionEvent.ACTION_DOWN:
+                    updateLastEvent(event);
+                    return event;
+
+                case MotionEvent.ACTION_CANCEL:
+                    if (mLastEvent == null) {
+                        return null;
+                    }
+                    updateLastEvent(null);
+                    return event;
+
+                default:
+                    return null;
+            }
+        }
+
+        /**
+         * Returns true if there is a gesture in progress that may need to be canceled.
+         * @return True if cancel is needed.
+         */
+        public boolean isCancelNeeded() {
+            return mLastEvent != null && mLastEvent.getAction() != MotionEvent.ACTION_UP;
+        }
+
+        private void updateLastEvent(MotionEvent event) {
+            if (mLastEvent != null) {
+                mLastEvent.recycle();
+            }
+            mLastEvent = event != null ? MotionEvent.obtainNoHistory(event) : null;
+        }
+    }
+}
\ No newline at end of file
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/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/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/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/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index d42f96a..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 || force) {
-                    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/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java
index 16eeb7ba..b943c09 100644
--- a/services/java/com/android/server/DeviceStorageMonitorService.java
+++ b/services/java/com/android/server/DeviceStorageMonitorService.java
@@ -25,6 +25,7 @@
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageManager;
 import android.os.Binder;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Process;
@@ -336,7 +337,9 @@
         //log the event to event log with the amount of free storage(in bytes) left on the device
         EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
         //  Pack up the values and broadcast them to everyone
-        Intent lowMemIntent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
+        Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated()
+                ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS
+                : Intent.ACTION_MANAGE_PACKAGE_STORAGE);
         lowMemIntent.putExtra("memory", mFreeMem);
         lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         NotificationManager mNotificationMgr =
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index bb7f4fc..bb73b29 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -21,6 +21,7 @@
 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;
@@ -2558,9 +2559,13 @@
             if (p.perm != null) {
                 pw.print("    perm="); pw.println(p.perm);
             }
+            if (READ_EXTERNAL_STORAGE.equals(p.name)) {
+                pw.print("    enforcement=");
+                pw.println(PackageManager.enforcementToString(mReadExternalStorageEnforcement));
+            }
         }
     }
-    
+
     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/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index d9425aab..f48b56d1 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -3974,7 +3974,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 +3985,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 +4050,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 +4918,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 +4958,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 +4971,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 +7269,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:");
@@ -9333,7 +9341,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 +9543,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/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 e7e9698..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 543168f..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;
+    }
+
+
 }