Merge "Do not bother to remove pending installs" into jb-dev
diff --git a/api/16.txt b/api/16.txt
index 6aab939..7e9794f 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -22737,6 +22737,17 @@
     method public void onPrepareSubMenu(android.view.SubMenu);
   }
 
+  public final class Choreographer {
+    method public static android.view.Choreographer getInstance();
+    method public void postFrameCallback(android.view.Choreographer.FrameCallback);
+    method public void postFrameCallbackDelayed(android.view.Choreographer.FrameCallback, long);
+    method public void removeFrameCallback(android.view.Choreographer.FrameCallback);
+  }
+
+  public static abstract interface Choreographer.FrameCallback {
+    method public abstract void doFrame(long);
+  }
+
   public abstract interface CollapsibleActionView {
     method public abstract void onActionViewCollapsed();
     method public abstract void onActionViewExpanded();
diff --git a/api/current.txt b/api/current.txt
index 6aab939..7e9794f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22737,6 +22737,17 @@
     method public void onPrepareSubMenu(android.view.SubMenu);
   }
 
+  public final class Choreographer {
+    method public static android.view.Choreographer getInstance();
+    method public void postFrameCallback(android.view.Choreographer.FrameCallback);
+    method public void postFrameCallbackDelayed(android.view.Choreographer.FrameCallback, long);
+    method public void removeFrameCallback(android.view.Choreographer.FrameCallback);
+  }
+
+  public static abstract interface Choreographer.FrameCallback {
+    method public abstract void doFrame(long);
+  }
+
   public abstract interface CollapsibleActionView {
     method public abstract void onActionViewCollapsed();
     method public abstract void onActionViewExpanded();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c7afd2b2..618f1f8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.media.AudioManager;
 import android.net.Uri;
@@ -31,6 +32,8 @@
 import android.text.TextUtils;
 import android.util.IntProperty;
 import android.util.Log;
+import android.util.Slog;
+import android.util.TypedValue;
 import android.view.View;
 import android.widget.ProgressBar;
 import android.widget.RemoteViews;
@@ -1378,8 +1381,8 @@
 
         private RemoteViews applyStandardTemplate(int resId) {
             RemoteViews contentView = new RemoteViews(mContext.getPackageName(), resId);
-            boolean hasLine3 = false;
-            boolean hasLine2 = false;
+            boolean showLine3 = false;
+            boolean showLine2 = false;
             int smallIconImageViewId = R.id.icon;
             if (mLargeIcon != null) {
                 contentView.setImageViewBitmap(R.id.icon, mLargeIcon);
@@ -1401,15 +1404,13 @@
                 contentView.setTextViewText(R.id.title, mContentTitle);
             }
             if (mContentText != null) {
-                contentView.setTextViewText(
-                        (mSubText != null) ? R.id.text2 : R.id.text, 
-                        mContentText);
-                hasLine3 = true;
+                contentView.setTextViewText(R.id.text, mContentText);
+                showLine3 = true;
             }
             if (mContentInfo != null) {
                 contentView.setTextViewText(R.id.info, mContentInfo);
                 contentView.setViewVisibility(R.id.info, View.VISIBLE);
-                hasLine3 = true;
+                showLine3 = true;
             } else if (mNumber > 0) {
                 final int tooBig = mContext.getResources().getInteger(
                         R.integer.status_bar_notification_info_maxnum);
@@ -1421,25 +1422,42 @@
                     contentView.setTextViewText(R.id.info, f.format(mNumber));
                 }
                 contentView.setViewVisibility(R.id.info, View.VISIBLE);
-                hasLine3 = true;
+                showLine3 = true;
             } else {
                 contentView.setViewVisibility(R.id.info, View.GONE);
             }
 
+            // Need to show three lines?
             if (mSubText != null) {
                 contentView.setTextViewText(R.id.text, mSubText);
-                contentView.setViewVisibility(R.id.text2,
-                        mContentText != null ? View.VISIBLE : View.GONE);
+                if (mContentText != null) {
+                    contentView.setTextViewText(R.id.text2, mContentText);
+                    // need to shrink all the type to make sure everything fits
+                    contentView.setViewVisibility(R.id.text2, View.VISIBLE);
+                    showLine2 = true;
+                } else {
+                    contentView.setViewVisibility(R.id.text2, View.GONE);
+                }
             } else {
                 contentView.setViewVisibility(R.id.text2, View.GONE);
                 if (mProgressMax != 0 || mProgressIndeterminate) {
                     contentView.setProgressBar(
                             R.id.progress, mProgressMax, mProgress, mProgressIndeterminate);
                     contentView.setViewVisibility(R.id.progress, View.VISIBLE);
+                    showLine2 = true;
                 } else {
                     contentView.setViewVisibility(R.id.progress, View.GONE);
                 }
             }
+            if (showLine2) {
+                final Resources res = mContext.getResources();
+                final float subTextSize = res.getDimensionPixelSize(
+                        R.dimen.notification_subtext_size);
+                contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize);
+                // vertical centering
+                contentView.setViewPadding(R.id.line1, 0, 0, 0, 0);
+            }
+
             if (mWhen != 0) {
                 if (mUseChronometer) {
                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
@@ -1451,7 +1469,7 @@
                     contentView.setLong(R.id.time, "setTime", mWhen);
                 }
             }
-            contentView.setViewVisibility(R.id.line3, hasLine3 ? View.VISIBLE : View.GONE);
+            contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
             return contentView;
         }
 
@@ -1629,18 +1647,8 @@
                 mBuilder.setContentTitle(mBigContentTitle);
             }
 
-            if (mBuilder.mSubText == null) {
-                mBuilder.setContentText(null);
-            }
-
             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId);
 
-            if (mBuilder.mSubText == null) {
-                contentView.setViewVisibility(R.id.line3, View.GONE);
-            } else {
-                contentView.setViewVisibility(R.id.line3, View.VISIBLE);
-            }
-
             if (mBigContentTitle != null && mBigContentTitle.equals("")) {
                 contentView.setViewVisibility(R.id.line1, View.GONE);
             } else {
@@ -1650,8 +1658,10 @@
             if (mSummaryText != null && !mSummaryText.equals("")) {
                 contentView.setViewVisibility(R.id.overflow_title, View.VISIBLE);
                 contentView.setTextViewText(R.id.overflow_title, mSummaryText);
+                contentView.setViewVisibility(R.id.line3, View.GONE);
             } else {
                 contentView.setViewVisibility(R.id.overflow_title, View.GONE);
+                contentView.setViewVisibility(R.id.line3, View.VISIBLE);
             }
 
             return contentView;
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 183cb88..aaa081c 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -24,16 +24,46 @@
 import android.util.Log;
 
 /**
- * Coordinates animations and drawing for UI on a particular thread.
- *
- * This object is thread-safe.  Other threads can post callbacks to run at a later time
- * on the UI thread.
- *
- * Ensuring thread-safety is a little tricky because the {@link DisplayEventReceiver}
- * can only be accessed from the UI thread so operations that touch the event receiver
- * are posted to the UI thread if needed.
- *
- * @hide
+ * Coordinates the timing of animations, input and drawing.
+ * <p>
+ * The choreographer receives timing pulses (such as vertical synchronization)
+ * from the display subsystem then schedules work to occur as part of rendering
+ * the next display frame.
+ * </p><p>
+ * Applications typically interact with the choreographer indirectly using
+ * higher level abstractions in the animation framework or the view hierarchy.
+ * Here are some examples of things you can do using the higher-level APIs.
+ * </p>
+ * <ul>
+ * <li>To post an animation to be processed on a regular time basis synchronized with
+ * display frame rendering, use {@link android.animation.ValueAnimator#start}.</li>
+ * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
+ * frame, use {@link View#postOnAnimation}.</li>
+ * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
+ * frame after a delay, use {@link View#postOnAnimationDelayed}.</li>
+ * <li>To post a call to {@link View#invalidate()} to occur once at the beginning of the
+ * next display frame, use {@link View#postInvalidateOnAnimation()} or
+ * {@link View#postInvalidateOnAnimation(int, int, int, int)}.</li>
+ * <li>To ensure that the contents of a {@link View} scroll smoothly and are drawn in
+ * sync with display frame rendering, do nothing.  This already happens automatically.
+ * {@link View#onDraw} will be called at the appropriate time.</li>
+ * </ul>
+ * <p>
+ * However, there are a few cases where you might want to use the functions of the
+ * choreographer directly in your application.  Here are some examples.
+ * </p>
+ * <ul>
+ * <li>If your application does its rendering in a different thread, possibly using GL,
+ * or does not use the animation framework or view hierarchy at all
+ * and you want to ensure that it is appropriately synchronized with the display, then use
+ * {@link Choreographer#postFrameCallback}.</li>
+ * <li>... and that's about it.</li>
+ * </ul>
+ * <p>
+ * Each {@link Looper} thread has its own choreographer.  Other threads can
+ * post callbacks to run on the choreographer but they will run on the {@link Looper}
+ * to which the choreographer belongs.
+ * </p>
  */
 public final class Choreographer {
     private static final String TAG = "Choreographer";
@@ -79,13 +109,22 @@
     private static final int MSG_DO_SCHEDULE_VSYNC = 1;
     private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
 
+    // All frame callbacks posted by applications have this token.
+    private static final Object FRAME_CALLBACK_TOKEN = new Object() {
+        public String toString() { return "FRAME_CALLBACK_TOKEN"; }
+    };
+
     private final Object mLock = new Object();
 
     private final Looper mLooper;
     private final FrameHandler mHandler;
+
+    // The display event receiver can only be accessed by the looper thread to which
+    // it is attached.  We take care to ensure that we post message to the looper
+    // if appropriate when interacting with the display event receiver.
     private final FrameDisplayEventReceiver mDisplayEventReceiver;
 
-    private Callback mCallbackPool;
+    private CallbackRecord mCallbackPool;
 
     private final CallbackQueue[] mCallbackQueues;
 
@@ -96,17 +135,20 @@
 
     /**
      * Callback type: Input callback.  Runs first.
+     * @hide
      */
     public static final int CALLBACK_INPUT = 0;
 
     /**
      * Callback type: Animation callback.  Runs before traversals.
+     * @hide
      */
     public static final int CALLBACK_ANIMATION = 1;
 
     /**
      * Callback type: Traversal callback.  Handles layout and draw.  Runs last
      * after all other asynchronous messages have been handled.
+     * @hide
      */
     public static final int CALLBACK_TRAVERSAL = 2;
 
@@ -138,32 +180,38 @@
     }
 
     /**
-     * The amount of time, in milliseconds, between each frame of the animation. This is a
-     * requested time that the animation will attempt to honor, but the actual delay between
-     * frames may be different, depending on system load and capabilities. This is a static
+     * The amount of time, in milliseconds, between each frame of the animation.
+     * <p>
+     * This is a requested time that the animation will attempt to honor, but the actual delay
+     * between frames may be different, depending on system load and capabilities. This is a static
      * function because the same delay will be applied to all animations, since they are all
      * run off of a single timing loop.
-     *
+     * </p><p>
      * The frame delay may be ignored when the animation system uses an external timing
      * source, such as the display refresh rate (vsync), to govern animations.
+     * </p>
      *
      * @return the requested time between frames, in milliseconds
+     * @hide
      */
     public static long getFrameDelay() {
         return sFrameDelay;
     }
 
     /**
-     * The amount of time, in milliseconds, between each frame of the animation. This is a
-     * requested time that the animation will attempt to honor, but the actual delay between
-     * frames may be different, depending on system load and capabilities. This is a static
+     * The amount of time, in milliseconds, between each frame of the animation.
+     * <p>
+     * This is a requested time that the animation will attempt to honor, but the actual delay
+     * between frames may be different, depending on system load and capabilities. This is a static
      * function because the same delay will be applied to all animations, since they are all
      * run off of a single timing loop.
-     *
+     * </p><p>
      * The frame delay may be ignored when the animation system uses an external timing
      * source, such as the display refresh rate (vsync), to govern animations.
+     * </p>
      *
      * @param frameDelay the requested time between frames, in milliseconds
+     * @hide
      */
     public static void setFrameDelay(long frameDelay) {
         sFrameDelay = frameDelay;
@@ -171,23 +219,25 @@
 
     /**
      * Subtracts typical frame delay time from a delay interval in milliseconds.
-     *
+     * <p>
      * This method can be used to compensate for animation delay times that have baked
      * in assumptions about the frame delay.  For example, it's quite common for code to
      * assume a 60Hz frame time and bake in a 16ms delay.  When we call
      * {@link #postAnimationCallbackDelayed} we want to know how long to wait before
      * posting the animation callback but let the animation timer take care of the remaining
      * frame delay time.
-     *
+     * </p><p>
      * This method is somewhat conservative about how much of the frame delay it
      * subtracts.  It uses the same value returned by {@link #getFrameDelay} which by
      * default is 10ms even though many parts of the system assume 16ms.  Consequently,
      * we might still wait 6ms before posting an animation callback that we want to run
      * on the next frame, but this is much better than waiting a whole 16ms and likely
      * missing the deadline.
+     * </p>
      *
      * @param delayMillis The original delay time including an assumed frame delay.
      * @return The adjusted delay time with the assumed frame delay subtracted out.
+     * @hide
      */
     public static long subtractFrameDelay(long delayMillis) {
         final long frameDelay = sFrameDelay;
@@ -196,21 +246,26 @@
 
     /**
      * Posts a callback to run on the next frame.
-     * The callback only runs once and then is automatically removed.
+     * <p>
+     * The callback runs once then is automatically removed.
+     * </p>
      *
      * @param callbackType The callback type.
      * @param action The callback action to run during the next frame.
      * @param token The callback token, or null if none.
      *
      * @see #removeCallbacks
+     * @hide
      */
     public void postCallback(int callbackType, Runnable action, Object token) {
         postCallbackDelayed(callbackType, action, token, 0);
     }
 
     /**
-     * Posts a callback to run on the next frame following the specified delay.
-     * The callback only runs once and then is automatically removed.
+     * Posts a callback to run on the next frame after the specified delay.
+     * <p>
+     * The callback runs once then is automatically removed.
+     * </p>
      *
      * @param callbackType The callback type.
      * @param action The callback action to run during the next frame after the specified delay.
@@ -218,6 +273,7 @@
      * @param delayMillis The delay time in milliseconds.
      *
      * @see #removeCallback
+     * @hide
      */
     public void postCallbackDelayed(int callbackType,
             Runnable action, Object token, long delayMillis) {
@@ -228,6 +284,11 @@
             throw new IllegalArgumentException("callbackType is invalid");
         }
 
+        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
+    }
+
+    private void postCallbackDelayedInternal(int callbackType,
+            Object action, Object token, long delayMillis) {
         if (DEBUG) {
             Log.d(TAG, "PostCallback: type=" + callbackType
                     + ", action=" + action + ", token=" + token
@@ -261,12 +322,17 @@
      *
      * @see #postCallback
      * @see #postCallbackDelayed
+     * @hide
      */
     public void removeCallbacks(int callbackType, Runnable action, Object token) {
         if (callbackType < 0 || callbackType > CALLBACK_LAST) {
             throw new IllegalArgumentException("callbackType is invalid");
         }
 
+        removeCallbacksInternal(callbackType, action, token);
+    }
+
+    private void removeCallbacksInternal(int callbackType, Object action, Object token) {
         if (DEBUG) {
             Log.d(TAG, "RemoveCallbacks: type=" + callbackType
                     + ", action=" + action + ", token=" + token);
@@ -281,17 +347,81 @@
     }
 
     /**
-     * Gets the time when the current frame started.  The frame time should be used
-     * instead of {@link SystemClock#uptimeMillis()} to synchronize animations.
-     * This helps to reduce inter-frame jitter because the frame time is fixed at the
-     * time the frame was scheduled to start, regardless of when the animations or
-     * drawing code actually ran.
+     * Posts a frame callback to run on the next frame.
+     * <p>
+     * The callback runs once then is automatically removed.
+     * </p>
      *
+     * @param callback The frame callback to run during the next frame.
+     *
+     * @see #postFrameCallbackDelayed
+     * @see #removeFrameCallback
+     */
+    public void postFrameCallback(FrameCallback callback) {
+        postFrameCallbackDelayed(callback, 0);
+    }
+
+    /**
+     * Posts a frame callback to run on the next frame after the specified delay.
+     * <p>
+     * The callback runs once then is automatically removed.
+     * </p>
+     *
+     * @param callback The frame callback to run during the next frame.
+     * @param delayMillis The delay time in milliseconds.
+     *
+     * @see #postFrameCallback
+     * @see #removeFrameCallback
+     */
+    public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+
+        postCallbackDelayedInternal(CALLBACK_ANIMATION,
+                callback, FRAME_CALLBACK_TOKEN, delayMillis);
+    }
+
+    /**
+     * Removes a previously posted frame callback.
+     *
+     * @param callback The frame callback to remove.
+     *
+     * @see #postFrameCallback
+     * @see #postFrameCallbackDelayed
+     */
+    public void removeFrameCallback(FrameCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback must not be null");
+        }
+
+        removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
+    }
+
+    /**
+     * Gets the time when the current frame started.
+     * <p>
+     * This method provides the time in nanoseconds when the frame started being rendered.
+     * The frame time provides a stable time base for synchronizing animations
+     * and drawing.  It should be used instead of {@link SystemClock#uptimeMillis()}
+     * or {@link System#nanoTime()} for animations and drawing in the UI.  Using the frame
+     * time helps to reduce inter-frame jitter because the frame time is fixed at the time
+     * the frame was scheduled to start, regardless of when the animations or drawing
+     * callback actually runs.  All callbacks that run as part of rendering a frame will
+     * observe the same frame time so using the frame time also helps to synchronize effects
+     * that are performed by different callbacks.
+     * </p><p>
+     * Please note that the framework already takes care to process animations and
+     * drawing using the frame time as a stable time base.  Most applications should
+     * not need to use the frame time information directly.
+     * </p><p>
      * This method should only be called from within a callback.
+     * </p>
      *
      * @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
      *
      * @throws IllegalStateException if no frame is in progress.
+     * @hide
      */
     public long getFrameTime() {
         return getFrameTimeNanos() / NANOS_PER_MS;
@@ -303,6 +433,7 @@
      * @return The frame start time, in the {@link System#nanoTime()} time base.
      *
      * @throws IllegalStateException if no frame is in progress.
+     * @hide
      */
     public long getFrameTimeNanos() {
         synchronized (mLock) {
@@ -345,7 +476,7 @@
         }
     }
 
-    void doFrame(long timestampNanos, int frame) {
+    void doFrame(long frameTimeNanos, int frame) {
         final long startNanos;
         synchronized (mLock) {
             if (!mFrameScheduled) {
@@ -353,7 +484,7 @@
             }
 
             startNanos = System.nanoTime();
-            final long jitterNanos = startNanos - timestampNanos;
+            final long jitterNanos = startNanos - frameTimeNanos;
             if (jitterNanos >= mFrameIntervalNanos) {
                 final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                 if (DEBUG) {
@@ -363,10 +494,10 @@
                             + "Setting frame time to " + (lastFrameOffset * 0.000001f)
                             + " ms in the past.");
                 }
-                timestampNanos = startNanos - lastFrameOffset;
+                frameTimeNanos = startNanos - lastFrameOffset;
             }
 
-            if (timestampNanos < mLastFrameTimeNanos) {
+            if (frameTimeNanos < mLastFrameTimeNanos) {
                 if (DEBUG) {
                     Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                             + "previously skipped frame.  Waiting for next vsync");
@@ -376,24 +507,27 @@
             }
 
             mFrameScheduled = false;
-            mLastFrameTimeNanos = timestampNanos;
+            mLastFrameTimeNanos = frameTimeNanos;
         }
 
-        doCallbacks(Choreographer.CALLBACK_INPUT);
-        doCallbacks(Choreographer.CALLBACK_ANIMATION);
-        doCallbacks(Choreographer.CALLBACK_TRAVERSAL);
+        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
 
         if (DEBUG) {
             final long endNanos = System.nanoTime();
             Log.d(TAG, "Frame " + frame + ": Finished, took "
                     + (endNanos - startNanos) * 0.000001f + " ms, latency "
-                    + (startNanos - timestampNanos) * 0.000001f + " ms.");
+                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
         }
     }
 
-    void doCallbacks(int callbackType) {
-        Callback callbacks;
+    void doCallbacks(int callbackType, long frameTimeNanos) {
+        CallbackRecord callbacks;
         synchronized (mLock) {
+            // We use "now" to determine when callbacks become due because it's possible
+            // for earlier processing phases in a frame to post callbacks that should run
+            // in a following phase, such as an input event that causes an animation to start.
             final long now = SystemClock.uptimeMillis();
             callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);
             if (callbacks == null) {
@@ -402,19 +536,19 @@
             mCallbacksRunning = true;
         }
         try {
-            for (Callback c = callbacks; c != null; c = c.next) {
+            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                 if (DEBUG) {
                     Log.d(TAG, "RunCallback: type=" + callbackType
                             + ", action=" + c.action + ", token=" + c.token
                             + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
                 }
-                c.action.run();
+                c.run(frameTimeNanos);
             }
         } finally {
             synchronized (mLock) {
                 mCallbacksRunning = false;
                 do {
-                    final Callback next = callbacks.next;
+                    final CallbackRecord next = callbacks.next;
                     recycleCallbackLocked(callbacks);
                     callbacks = next;
                 } while (callbacks != null);
@@ -449,10 +583,10 @@
         return Looper.myLooper() == mLooper;
     }
 
-    private Callback obtainCallbackLocked(long dueTime, Runnable action, Object token) {
-        Callback callback = mCallbackPool;
+    private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
+        CallbackRecord callback = mCallbackPool;
         if (callback == null) {
-            callback = new Callback();
+            callback = new CallbackRecord();
         } else {
             mCallbackPool = callback.next;
             callback.next = null;
@@ -463,13 +597,44 @@
         return callback;
     }
 
-    private void recycleCallbackLocked(Callback callback) {
+    private void recycleCallbackLocked(CallbackRecord callback) {
         callback.action = null;
         callback.token = null;
         callback.next = mCallbackPool;
         mCallbackPool = callback;
     }
 
+    /**
+     * Implement this interface to receive a callback when a new display frame is
+     * being rendered.  The callback is invoked on the {@link Looper} thread to
+     * which the {@link Choreographer} is attached.
+     */
+    public interface FrameCallback {
+        /**
+         * Called when a new display frame is being rendered.
+         * <p>
+         * This method provides the time in nanoseconds when the frame started being rendered.
+         * The frame time provides a stable time base for synchronizing animations
+         * and drawing.  It should be used instead of {@link SystemClock#uptimeMillis()}
+         * or {@link System#nanoTime()} for animations and drawing in the UI.  Using the frame
+         * time helps to reduce inter-frame jitter because the frame time is fixed at the time
+         * the frame was scheduled to start, regardless of when the animations or drawing
+         * callback actually runs.  All callbacks that run as part of rendering a frame will
+         * observe the same frame time so using the frame time also helps to synchronize effects
+         * that are performed by different callbacks.
+         * </p><p>
+         * Please note that the framework already takes care to process animations and
+         * drawing using the frame time as a stable time base.  Most applications should
+         * not need to use the frame time information directly.
+         * </p>
+         *
+         * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
+         * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
+         * to convert it to the {@link SystemClock#uptimeMillis()} time base.
+         */
+        public void doFrame(long frameTimeNanos);
+    }
+
     private final class FrameHandler extends Handler {
         public FrameHandler(Looper looper) {
             super(looper);
@@ -520,28 +685,36 @@
         }
     }
 
-    private static final class Callback {
-        public Callback next;
+    private static final class CallbackRecord {
+        public CallbackRecord next;
         public long dueTime;
-        public Runnable action;
+        public Object action; // Runnable or FrameCallback
         public Object token;
+
+        public void run(long frameTimeNanos) {
+            if (token == FRAME_CALLBACK_TOKEN) {
+                ((FrameCallback)action).doFrame(frameTimeNanos);
+            } else {
+                ((Runnable)action).run();
+            }
+        }
     }
 
     private final class CallbackQueue {
-        private Callback mHead;
+        private CallbackRecord mHead;
 
         public boolean hasDueCallbacksLocked(long now) {
             return mHead != null && mHead.dueTime <= now;
         }
 
-        public Callback extractDueCallbacksLocked(long now) {
-            Callback callbacks = mHead;
+        public CallbackRecord extractDueCallbacksLocked(long now) {
+            CallbackRecord callbacks = mHead;
             if (callbacks == null || callbacks.dueTime > now) {
                 return null;
             }
 
-            Callback last = callbacks;
-            Callback next = last.next;
+            CallbackRecord last = callbacks;
+            CallbackRecord next = last.next;
             while (next != null) {
                 if (next.dueTime > now) {
                     last.next = null;
@@ -554,9 +727,9 @@
             return callbacks;
         }
 
-        public void addCallbackLocked(long dueTime, Runnable action, Object token) {
-            Callback callback = obtainCallbackLocked(dueTime, action, token);
-            Callback entry = mHead;
+        public void addCallbackLocked(long dueTime, Object action, Object token) {
+            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
+            CallbackRecord entry = mHead;
             if (entry == null) {
                 mHead = callback;
                 return;
@@ -576,10 +749,10 @@
             entry.next = callback;
         }
 
-        public void removeCallbacksLocked(Runnable action, Object token) {
-            Callback predecessor = null;
-            for (Callback callback = mHead; callback != null;) {
-                final Callback next = callback.next;
+        public void removeCallbacksLocked(Object action, Object token) {
+            CallbackRecord predecessor = null;
+            for (CallbackRecord callback = mHead; callback != null;) {
+                final CallbackRecord next = callback.next;
                 if ((action == null || callback.action == action)
                         && (token == null || callback.token == token)) {
                     if (predecessor != null) {
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 411aed3..a719a01 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -374,6 +374,14 @@
             // tell mLayer about it and set the SurfaceTexture to use the
             // current view size.
             mUpdateSurface = false;
+
+            // Since we are updating the layer, force an update to ensure its
+            // parameters are correct (width, height, transform, etc.)
+            synchronized (mLock) {
+                mUpdateLayer = true;
+            }
+            mMatrixChanged = true;
+
             mAttachInfo.mHardwareRenderer.setSurfaceTexture(mLayer, mSurface);
             nSetDefaultBufferSize(mSurface, getWidth(), getHeight());
         }
@@ -471,7 +479,7 @@
     }
 
     private void applyTransformMatrix() {
-        if (mMatrixChanged) {
+        if (mMatrixChanged && mLayer != null) {
             mLayer.setTransform(mMatrix);
             mMatrixChanged = false;
         }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b69450c..832d575 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6212,15 +6212,6 @@
 
             // Clear the text navigation state.
             setAccessibilityCursorPosition(-1);
-
-            // Try to move accessibility focus to the input focus.
-            View rootView = getRootView();
-            if (rootView != null) {
-                View inputFocus = rootView.findFocus();
-                if (inputFocus != null) {
-                    inputFocus.requestAccessibilityFocus();
-                }
-            }
         }
     }
 
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 9134966..823befb 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -193,19 +193,12 @@
     private static final int MAXIMUM_FLING_VELOCITY = 8000;
 
     /**
-     * Distance in dips between a touch up event denoting the end of a touch exploration
-     * gesture and the touch up event of a subsequent tap for the latter tap to be
-     * considered as a tap i.e. to perform a click.
-     */
-    private static final int TOUCH_EXPLORE_TAP_SLOP = 80;
-
-    /**
      * Delay before dispatching a recurring accessibility event in milliseconds.
      * This delay guarantees that a recurring event will be send at most once
      * during the {@link #SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS} time
      * frame.
      */
-    private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 400;
+    private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 100;
 
     /**
      * The maximum size of View's drawing cache, expressed in bytes. This size
@@ -238,7 +231,6 @@
     private final int mDoubleTapTouchSlop;
     private final int mPagingTouchSlop;
     private final int mDoubleTapSlop;
-    private final int mScaledTouchExploreTapSlop;
     private final int mWindowTouchSlop;
     private final int mMaximumDrawingCacheSize;
     private final int mOverscrollDistance;
@@ -265,7 +257,6 @@
         mDoubleTapTouchSlop = DOUBLE_TAP_TOUCH_SLOP;
         mPagingTouchSlop = PAGING_TOUCH_SLOP;
         mDoubleTapSlop = DOUBLE_TAP_SLOP;
-        mScaledTouchExploreTapSlop = TOUCH_EXPLORE_TAP_SLOP;
         mWindowTouchSlop = WINDOW_TOUCH_SLOP;
         //noinspection deprecation
         mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
@@ -302,7 +293,6 @@
         mMaximumFlingVelocity = (int) (density * MAXIMUM_FLING_VELOCITY + 0.5f);
         mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
         mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
-        mScaledTouchExploreTapSlop = (int) (density * TOUCH_EXPLORE_TAP_SLOP + 0.5f);
         mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
 
         final Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
@@ -553,17 +543,6 @@
     }
 
     /**
-     * @return Distance in pixels between a touch up event denoting the end of a touch exploration
-     * gesture and the touch up event of a subsequent tap for the latter tap to be
-     * considered as a tap i.e. to perform a click.
-     *
-     * @hide
-     */
-    public int getScaledTouchExploreTapSlop() {
-        return mScaledTouchExploreTapSlop;
-    }
-
-    /**
      * Interval for dispatching a recurring accessibility event in milliseconds.
      * This interval guarantees that a recurring event will be send at most once
      * during the {@link #getSendRecurringAccessibilityEventsInterval()} time frame.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 512507b..c9a41ad 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2895,20 +2895,6 @@
                         if (hasWindowFocus) {
                             mView.sendAccessibilityEvent(
                                     AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-                            // Give accessibility focus to the view that has input
-                            // focus if such, otherwise to the first one.
-                            if (mView instanceof ViewGroup) {
-                                ViewGroup viewGroup = (ViewGroup) mView;
-                                View focused = viewGroup.findFocus();
-                                if (focused != null) {
-                                    focused.requestAccessibilityFocus();
-                                }
-                            }
-                            // There is no accessibility focus, despite our effort
-                            // above, now just give it to the first view.
-                            if (mAccessibilityFocusedHost == null) {
-                                mView.requestAccessibilityFocus();
-                            }
                         }
                     }
                 }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 42c4ed6..6cee0f3 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1456,7 +1456,7 @@
             final int lastVisiblePosition = getLastVisiblePosition();
             if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
                     && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
-                return;   
+                return;
             } else {
                 mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
                 mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
@@ -2278,28 +2278,37 @@
             super.onInitializeAccessibilityNodeInfo(host, info);
 
             final int position = getPositionForView(host);
+            final ListAdapter adapter = getAdapter();
 
-            if (position == INVALID_POSITION) {
+            if ((position == INVALID_POSITION) || (adapter == null)) {
+                // Cannot perform actions on invalid items.
+                info.setEnabled(false);
                 return;
             }
 
-            if (isClickable() && isEnabled()) {
-                info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
-                info.setClickable(true);
-            }
-
-            if (isLongClickable() && isEnabled()) {
-                info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
-                info.setLongClickable(true);
-            }
-
-            if (isEnabled()) {
-                info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
+            if (!isEnabled() || !adapter.isEnabled(position)) {
+                // Cannot perform actions on invalid items.
+                info.setEnabled(false);
+                return;
             }
 
             if (position == getSelectedItemPosition()) {
                 info.setSelected(true);
+                info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+            } else {
+                info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
             }
+
+            if (isClickable()) {
+                info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+                info.setClickable(true);
+            }
+
+            if (isLongClickable()) {
+                info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
+                info.setLongClickable(true);
+            }
+
         }
 
         @Override
@@ -2309,22 +2318,33 @@
             }
 
             final int position = getPositionForView(host);
+            final ListAdapter adapter = getAdapter();
 
-            if (position == INVALID_POSITION) {
+            if ((position == INVALID_POSITION) || (adapter == null)) {
+                // Cannot perform actions on invalid items.
                 return false;
             }
 
-            if (!isEnabled()) {
+            if (!isEnabled() || !adapter.isEnabled(position)) {
+                // Cannot perform actions on disabled items.
                 return false;
             }
 
             final long id = getItemIdAtPosition(position);
 
             switch (action) {
+                case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
+                    if (getSelectedItemPosition() == position) {
+                        setSelection(INVALID_POSITION);
+                        return true;
+                    }
+                } return false;
                 case AccessibilityNodeInfo.ACTION_SELECT: {
-                    setSelection(position);
-                    return true;
-                }
+                    if (getSelectedItemPosition() != position) {
+                        setSelection(position);
+                        return true;
+                    }
+                } return false;
                 case AccessibilityNodeInfo.ACTION_CLICK: {
                     if (isClickable()) {
                         return performItemClick(host, position, id);
diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
index 6136206..d6fb847 100644
--- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
@@ -67,6 +67,7 @@
         public void onReleased(View v, int handle);
         public void onTrigger(View v, int target);
         public void onGrabbedStateChange(View v, int handle);
+        public void onFinishFinalAnimation();
     }
 
     // Tuneable parameters for animation
@@ -77,9 +78,13 @@
     private static final int HIDE_ANIMATION_DELAY = 200;
     private static final int HIDE_ANIMATION_DURATION = 200;
     private static final int SHOW_ANIMATION_DURATION = 200;
-    private static final int SHOW_ANIMATION_DELAY = 0;
+    private static final int SHOW_ANIMATION_DELAY = 50;
     private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f;
-    private static final float TARGET_INITIAL_POSITION_SCALE = 0.8f;
+    private static final float TARGET_SCALE_SELECTED = 0.8f;
+    private static final long INITIAL_SHOW_HANDLE_DURATION = 200;
+    private static final float TARGET_SCALE_UNSELECTED = 1.0f;
+    private static final float RING_SCALE_UNSELECTED = 0.5f;
+    private static final float RING_SCALE_SELECTED = 1.5f;
 
     private TimeInterpolator mChevronAnimationInterpolator = Ease.Quad.easeOut;
 
@@ -126,6 +131,15 @@
             }
         }
 
+        public void cancel() {
+            final int count = size();
+            for (int i = 0; i < count; i++) {
+                Tweener anim = get(i);
+                anim.animator.cancel();
+            }
+            clear();
+        }
+
         public void stop() {
             final int count = size();
             for (int i = 0; i < count; i++) {
@@ -143,6 +157,7 @@
     private AnimatorListener mResetListener = new AnimatorListenerAdapter() {
         public void onAnimationEnd(Animator animator) {
             switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
+            dispatchOnFinishFinalAnimation();
         }
     };
 
@@ -150,6 +165,7 @@
         public void onAnimationEnd(Animator animator) {
             ping();
             switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
+            dispatchOnFinishFinalAnimation();
         }
     };
 
@@ -349,7 +365,7 @@
                 stopHandleAnimation();
                 deactivateTargets();
                 showTargets(true);
-                mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE);
+                activateHandle();
                 setGrabbedState(OnTriggerListener.CENTER_HANDLE);
                 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
                     announceTargets();
@@ -368,6 +384,19 @@
         }
     }
 
+    private void activateHandle() {
+        mHandleDrawable.setState(TargetDrawable.STATE_ACTIVE);
+        if (mAlwaysTrackFinger) {
+            mHandleAnimations.stop();
+            mHandleDrawable.setAlpha(0.0f);
+            mHandleAnimations.add(Tweener.to(mHandleDrawable, INITIAL_SHOW_HANDLE_DURATION,
+                    "ease", Ease.Cubic.easeIn,
+                    "alpha", 1.0f,
+                    "onUpdate", mUpdateListener));
+            mHandleAnimations.start();
+        }
+    }
+
     /**
      * Animation used to attract user's attention to the target button.
      * Assumes mChevronDrawables is an a list with an even number of chevrons filled with
@@ -463,6 +492,12 @@
         }
     }
 
+    private void dispatchOnFinishFinalAnimation() {
+        if (mOnTriggerListener != null) {
+            mOnTriggerListener.onFinishFinalAnimation();
+        }
+    }
+
     private void doFinish() {
         final int activeTarget = mActiveTarget;
         boolean targetHit =  activeTarget != -1;
@@ -471,8 +506,9 @@
         hideTargets(true);
 
         // Highlight the selected one
-        mHandleDrawable.setAlpha(targetHit ? 0.0f : 1.0f);
+        mHandleAnimations.cancel();
         if (targetHit) {
+            mHandleDrawable.setAlpha(0.0f);
             mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
             hideUnselected(activeTarget);
 
@@ -483,12 +519,11 @@
 
         // Animate handle back to the center based on current state.
         int delay = targetHit ? RETURN_TO_HOME_DELAY : 0;
-        int duration = targetHit ? 0 : RETURN_TO_HOME_DURATION;
-        mHandleAnimations.stop();
+        int duration = RETURN_TO_HOME_DURATION;
         mHandleAnimations.add(Tweener.to(mHandleDrawable, duration,
                 "ease", Ease.Quart.easeOut,
                 "delay", delay,
-                "alpha", 1.0f,
+                "alpha", mAlwaysTrackFinger ? 0.0f : 1.0f,
                 "x", 0,
                 "y", 0,
                 "onUpdate", mUpdateListener,
@@ -508,12 +543,15 @@
     }
 
     private void hideTargets(boolean animate) {
-        mTargetAnimations.stop();
+        mTargetAnimations.cancel();
         // Note: these animations should complete at the same time so that we can swap out
         // the target assets asynchronously from the setTargetResources() call.
         mAnimatingTargets = animate;
         final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
         final int delay = animate ? HIDE_ANIMATION_DELAY : 0;
+        final boolean targetSelected = mActiveTarget != -1;
+
+        final float targetScale = targetSelected ? TARGET_SCALE_SELECTED : TARGET_SCALE_UNSELECTED;
         final int length = mTargetDrawables.size();
         for (int i = 0; i < length; i++) {
             TargetDrawable target = mTargetDrawables.get(i);
@@ -521,13 +559,13 @@
             mTargetAnimations.add(Tweener.to(target, duration,
                     "ease", Ease.Cubic.easeOut,
                     "alpha", 0.0f,
-                    "scaleX", TARGET_INITIAL_POSITION_SCALE,
-                    "scaleY", TARGET_INITIAL_POSITION_SCALE,
+                    "scaleX", targetScale,
+                    "scaleY", targetScale,
                     "delay", delay,
                     "onUpdate", mUpdateListener));
         }
 
-        float ringScaleTarget = mActiveTarget != -1 ? 1.5f : 0.5f;
+        final float ringScaleTarget = targetSelected ? RING_SCALE_SELECTED : RING_SCALE_UNSELECTED;
         mTargetAnimations.add(Tweener.to(mOuterRing, duration,
                 "ease", Ease.Cubic.easeOut,
                 "alpha", 0.0f,
@@ -544,13 +582,14 @@
         mTargetAnimations.stop();
         mAnimatingTargets = animate;
         final int delay = animate ? SHOW_ANIMATION_DELAY : 0;
+        final int duration = animate ? SHOW_ANIMATION_DURATION : 0;
         final int length = mTargetDrawables.size();
         for (int i = 0; i < length; i++) {
             TargetDrawable target = mTargetDrawables.get(i);
             target.setState(TargetDrawable.STATE_INACTIVE);
-            target.setScaleX(TARGET_INITIAL_POSITION_SCALE);
-            target.setScaleY(TARGET_INITIAL_POSITION_SCALE);
-            mTargetAnimations.add(Tweener.to(target, animate ? SHOW_ANIMATION_DURATION : 0,
+            target.setScaleX(TARGET_SCALE_SELECTED);
+            target.setScaleY(TARGET_SCALE_SELECTED);
+            mTargetAnimations.add(Tweener.to(target, duration,
                     "ease", Ease.Cubic.easeOut,
                     "alpha", 1.0f,
                     "scaleX", 1.0f,
@@ -558,9 +597,7 @@
                     "delay", delay,
                     "onUpdate", mUpdateListener));
         }
-        mOuterRing.setScaleX(0.5f);
-        mOuterRing.setScaleY(0.5f);
-        mTargetAnimations.add(Tweener.to(mOuterRing, animate ? SHOW_ANIMATION_DURATION : 0,
+        mTargetAnimations.add(Tweener.to(mOuterRing, duration,
                 "ease", Ease.Cubic.easeOut,
                 "alpha", 1.0f,
                 "scaleX", 1.0f,
@@ -572,10 +609,6 @@
         mTargetAnimations.start();
     }
 
-    private void stopTargetAnimation() {
-        mTargetAnimations.stop();
-    }
-
     private void vibrate() {
         if (mVibrator != null) {
             mVibrator.vibrate(mVibrationDuration);
@@ -708,7 +741,7 @@
     public void reset(boolean animate) {
         stopChevronAnimation();
         stopHandleAnimation();
-        stopTargetAnimation();
+        mTargetAnimations.stop();
         hideChevrons();
         hideTargets(animate);
         mHandleDrawable.setX(0);
@@ -760,7 +793,7 @@
     private void handleDown(MotionEvent event) {
        if (!trySwitchToFirstTouchState(event.getX(), event.getY())) {
             mDragging = false;
-            stopTargetAnimation();
+            mTargetAnimations.cancel();
             ping();
         }
     }
@@ -815,8 +848,8 @@
                     // For more than one target, snap to the closest one less than hitRadius away.
                     float best = Float.MAX_VALUE;
                     final float hitRadius2 = mHitRadius * mHitRadius;
+                    // Find first target in range
                     for (int i = 0; i < ntargets; i++) {
-                        // Snap to the first target in range
                         TargetDrawable target = targets.get(i);
                         float dx = limitX - target.getX();
                         float dy = limitY - target.getY();
@@ -842,10 +875,15 @@
             float newX = singleTarget ? x : target.getX();
             float newY = singleTarget ? y : target.getY();
             moveHandleTo(newX, newY, false);
+            mHandleAnimations.cancel();
+            mHandleDrawable.setAlpha(0.0f);
         } else {
             switchToState(STATE_TRACKING, x, y);
+            if (mActiveTarget != -1) {
+                mHandleAnimations.cancel();
+                mHandleDrawable.setAlpha(1.0f);
+            }
             moveHandleTo(x, y, false);
-            mHandleDrawable.setAlpha(1.0f);
         }
 
         // Draw handle outside parent's bounds
@@ -857,7 +895,6 @@
                 TargetDrawable target = targets.get(mActiveTarget);
                 if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
                     target.setState(TargetDrawable.STATE_INACTIVE);
-                    mHandleDrawable.setAlpha(1.0f);
                 }
             }
             // Focus the new target
@@ -865,7 +902,6 @@
                 TargetDrawable target = targets.get(activeTarget);
                 if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
                     target.setState(TargetDrawable.STATE_FOCUSED);
-                    mHandleDrawable.setAlpha(0.0f);
                 }
                 dispatchGrabbedEvent(activeTarget);
                 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
diff --git a/core/res/res/layout/notification_template_base.xml b/core/res/res/layout/notification_template_base.xml
index 3692a4d..ed680a9 100644
--- a/core/res/res/layout/notification_template_base.xml
+++ b/core/res/res/layout/notification_template_base.xml
@@ -38,14 +38,15 @@
         android:orientation="vertical"
         android:paddingLeft="12dp"
         android:paddingRight="12dp"
-        android:paddingTop="4dp"
-        android:paddingBottom="4dp"
-        android:gravity="center_vertical"
+        android:paddingTop="2dp"
+        android:paddingBottom="2dp"
+        android:gravity="top"
         >
         <LinearLayout
             android:id="@+id/line1"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:paddingTop="6dp"
             android:orientation="horizontal"
             >
             <TextView android:id="@+id/title"
@@ -85,8 +86,15 @@
             android:ellipsize="marquee"
             android:visibility="gone"
             />
+        <ProgressBar
+            android:id="@android:id/progress"
+            android:layout_width="match_parent"
+            android:layout_height="12dp"
+            android:visibility="gone"
+            style="?android:attr/progressBarStyleHorizontal"
+            />
         <TextView android:id="@+id/overflow_title"
-            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:singleLine="true"
@@ -123,21 +131,14 @@
                 />
             <ImageView android:id="@+id/right_icon"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_height="match_parent"
                 android:layout_gravity="center"
                 android:layout_weight="0"
-                android:scaleType="center"
+                android:scaleType="centerInside"
                 android:paddingLeft="8dp"
                 android:visibility="gone"
                 android:drawableAlpha="180"
                 />
         </LinearLayout>
-        <ProgressBar
-            android:id="@android:id/progress"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone"
-            style="?android:attr/progressBarStyleHorizontal"
-            />
     </LinearLayout>
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_big_base.xml b/core/res/res/layout/notification_template_big_base.xml
index d50b792..f2c204f 100644
--- a/core/res/res/layout/notification_template_big_base.xml
+++ b/core/res/res/layout/notification_template_big_base.xml
@@ -38,9 +38,9 @@
         android:orientation="vertical"
         android:paddingLeft="12dp"
         android:paddingRight="12dp"
-        android:paddingTop="4dp"
-        android:paddingBottom="4dp"
-        android:gravity="center_vertical"
+        android:paddingTop="2dp"
+        android:paddingBottom="2dp"
+        android:gravity="top"
         >
         <LinearLayout
             android:layout_width="match_parent"
@@ -52,6 +52,7 @@
                 android:id="@+id/line1"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:paddingTop="6dp"
                 android:orientation="horizontal"
                 >
                 <TextView android:id="@+id/title"
@@ -126,10 +127,10 @@
                     />
                 <ImageView android:id="@+id/right_icon"
                     android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
+                    android:layout_height="match_parent"
                     android:layout_gravity="center"
                     android:layout_weight="0"
-                    android:scaleType="center"
+                    android:scaleType="centerInside"
                     android:paddingLeft="8dp"
                     android:visibility="gone"
                     android:drawableAlpha="180"
@@ -138,7 +139,7 @@
             <ProgressBar
                 android:id="@android:id/progress"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
+                android:layout_height="12dp"
                 android:visibility="gone"
                 style="?android:attr/progressBarStyleHorizontal"
                 />
diff --git a/core/res/res/layout/notification_template_big_picture.xml b/core/res/res/layout/notification_template_big_picture.xml
index f98970a..9b896a4 100644
--- a/core/res/res/layout/notification_template_big_picture.xml
+++ b/core/res/res/layout/notification_template_big_picture.xml
@@ -31,6 +31,13 @@
         android:layout_gravity="bottom"
         android:scaleType="centerCrop"
         />
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="6dp"
+        android:layout_marginTop="64dp"
+        android:scaleType="fitXY"
+        android:src="@drawable/title_bar_shadow"
+        />
     <include layout="@layout/notification_template_base" 
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/core/res/res/layout/notification_template_big_text.xml b/core/res/res/layout/notification_template_big_text.xml
index 210bc13..304f2b5 100644
--- a/core/res/res/layout/notification_template_big_text.xml
+++ b/core/res/res/layout/notification_template_big_text.xml
@@ -36,9 +36,9 @@
         android:orientation="vertical"
         android:paddingLeft="12dp"
         android:paddingRight="12dp"
-        android:paddingTop="4dp"
-        android:paddingBottom="4dp"
-        android:gravity="center_vertical"
+        android:paddingTop="2dp"
+        android:paddingBottom="2dp"
+        android:gravity="top"
         >
         <LinearLayout
             android:layout_width="match_parent"
@@ -50,6 +50,7 @@
                 android:id="@+id/line1"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:paddingTop="6dp"
                 android:orientation="horizontal"
                 android:layout_gravity="top"
                 android:layout_weight="0"
@@ -92,11 +93,18 @@
                 android:layout_weight="0"
                 android:visibility="gone"
                 />
+            <ProgressBar
+                android:id="@android:id/progress"
+                android:layout_width="match_parent"
+                android:layout_height="12dp"
+                android:visibility="gone"
+                android:layout_weight="0"
+                style="?android:attr/progressBarStyleHorizontal"
+                />
             <TextView android:id="@+id/big_text"
                 android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
                 android:layout_width="match_parent"
                 android:layout_height="0dp"
-                android:layout_marginTop="2dp"
                 android:layout_marginBottom="2dp"
                 android:singleLine="false"
                 android:visibility="gone"
@@ -113,7 +121,7 @@
             android:layout_weight="1"
             />
         <TextView android:id="@+id/overflow_title"
-            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:singleLine="true"
@@ -127,7 +135,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="horizontal"
-            android:layout_weight="1"
+            android:layout_weight="0"
             >
             <TextView android:id="@+id/text"
                 android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
@@ -151,22 +159,14 @@
                 />
             <ImageView android:id="@+id/right_icon"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_height="match_parent"
                 android:layout_gravity="center"
                 android:layout_weight="0"
-                android:scaleType="center"
+                android:scaleType="centerInside"
                 android:paddingLeft="8dp"
                 android:visibility="gone"
                 android:drawableAlpha="180"
                 />
         </LinearLayout>
-        <ProgressBar
-            android:id="@android:id/progress"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone"
-            android:layout_weight="0"
-            style="?android:attr/progressBarStyleHorizontal"
-            />
     </LinearLayout>
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_inbox.xml b/core/res/res/layout/notification_template_inbox.xml
index 3b00feb..121a81c 100644
--- a/core/res/res/layout/notification_template_inbox.xml
+++ b/core/res/res/layout/notification_template_inbox.xml
@@ -38,14 +38,15 @@
         android:orientation="vertical"
         android:paddingLeft="12dp"
         android:paddingRight="12dp"
-        android:paddingTop="4dp"
-        android:paddingBottom="4dp"
-        android:gravity="center_vertical"
+        android:paddingTop="2dp"
+        android:paddingBottom="2dp"
+        android:gravity="top"
         >
         <LinearLayout
             android:id="@+id/line1"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:paddingTop="6dp"
             android:orientation="horizontal"
             android:layout_weight="0"
             >
@@ -87,6 +88,14 @@
             android:visibility="gone"
             android:layout_weight="0"
             />
+        <ProgressBar
+            android:id="@android:id/progress"
+            android:layout_width="match_parent"
+            android:layout_height="12dp"
+            android:visibility="gone"
+            android:layout_weight="0"
+            style="?android:attr/progressBarStyleHorizontal"
+            />
         <TextView android:id="@+id/inbox_text0"
             android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
             android:layout_width="match_parent"
@@ -168,7 +177,7 @@
             android:layout_weight="0"
             />
         <TextView android:id="@+id/overflow_title"
-            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:singleLine="true"
@@ -206,22 +215,14 @@
                 />
             <ImageView android:id="@+id/right_icon"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_height="match_parent"
                 android:layout_gravity="center"
                 android:layout_weight="0"
-                android:scaleType="center"
+                android:scaleType="centerInside"
                 android:paddingLeft="8dp"
                 android:visibility="gone"
                 android:drawableAlpha="180"
                 />
         </LinearLayout>
-        <ProgressBar
-            android:id="@android:id/progress"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone"
-            android:layout_weight="0"
-            style="?android:attr/progressBarStyleHorizontal"
-            />
     </LinearLayout>
 </FrameLayout>
diff --git a/core/res/res/raw/accessibility_gestures.bin b/core/res/res/raw/accessibility_gestures.bin
index 96fa1ec..acd7993 100644
--- a/core/res/res/raw/accessibility_gestures.bin
+++ b/core/res/res/raw/accessibility_gestures.bin
Binary files differ
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 734151b..d549644 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -227,4 +227,11 @@
          action bar tabs from becoming too wide on a wide screen when only
          a few are present. -->
     <dimen name="action_bar_stacked_tab_max_width">180dp</dimen>
+
+	<!-- Size of notification text (see TextAppearance.StatusBar.EventContent) -->
+    <dimen name="notification_text_size">14dp</dimen>
+	<!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) -->
+    <dimen name="notification_title_text_size">18dp</dimen>
+	<!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
+    <dimen name="notification_subtext_size">12dp</dimen>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9829d89..f1f67eb 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -297,6 +297,9 @@
   <java-symbol type="dimen" name="volume_panel_top" />
   <java-symbol type="dimen" name="action_bar_stacked_max_height" />
   <java-symbol type="dimen" name="action_bar_stacked_tab_max_width" />
+  <java-symbol type="dimen" name="notification_text_size" />
+  <java-symbol type="dimen" name="notification_title_text_size" />
+  <java-symbol type="dimen" name="notification_subtext_size" />
 
   <java-symbol type="string" name="addToDictionary" />
   <java-symbol type="string" name="action_bar_home_description" />
@@ -1518,6 +1521,8 @@
   <java-symbol type="xml" name="storage_list" />
   <java-symbol type="bool" name="config_enableDreams" />
   <java-symbol type="string" name="config_defaultDreamComponent" />
+  <java-symbol type="string" name="enable_explore_by_touch_warning_title" />
+  <java-symbol type="string" name="enable_explore_by_touch_warning_message" />
 
   <java-symbol type="layout" name="resolver_grid" />
   <java-symbol type="id" name="resolver_grid" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d50e2de..5929439 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2503,6 +2503,25 @@
     <!-- SearchView accessibility description for voice button [CHAR LIMIT=NONE] -->
     <string name="searchview_description_voice">Voice search</string>
 
+    <!-- Title for a warning message about the interaction model changes after allowing an accessibility
+         service to put the device into explore by touch mode, displayed as a dialog message when
+         the user selects to enables the service. (default). [CHAR LIMIT=35] -->
+    <string name="enable_explore_by_touch_warning_title">Enable Explore by Touch?</string>
+    <!-- Summary for a warning message about the interaction model changes after allowing an accessibility
+         service to put the device into explore by touch mode, displayed as a dialog message when
+         the user selects to enables the service. (tablet). [CHAR LIMIT=NONE] -->
+    <string name="enable_explore_by_touch_warning_message" product="tablet">
+            <xliff:g id="accessibility_service_name">%1$s</xliff:g> wants to enable Explore by Touch.
+            When Explore by Touch is turned on, you can hear or see descriptions of what\'s under
+            your finger or perform gestures to interact with the tablet.</string>
+    <!-- Summary for a warning message about the interaction model changes after allowing an accessibility
+         service to put the device into explore by touch mode, displayed as a dialog message when
+         the user selects to enables the service. (default). [CHAR LIMIT=NONE] -->
+    <string name="enable_explore_by_touch_warning_message" product="default">
+            <xliff:g id="accessibility_service_name">%1$s</xliff:g> wants to enable Explore by Touch.
+            When Explore by Touch is turned on, you can hear or see descriptions of what\'s under
+            your finger or perform gestures to interact with the phone.</string>
+
     <!-- String used to display the date. This is the string to say something happened 1 month ago. -->
     <string name="oneMonthDurationPast">1 month ago</string>
     <!-- String used to display the date. This is the string to say something happened more than 1 month ago. -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index a90dab8..a54cdf1 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -240,23 +240,23 @@
     <!-- Notification content styles -->
     <style name="TextAppearance.StatusBar.EventContent">
         <item name="android:textColor">#808080</item>
-        <item name="android:textSize">14dp</item>
+        <item name="android:textSize">@dimen/notification_text_size</item>
     </style>
     <style name="TextAppearance.StatusBar.EventContent.Title">
         <item name="android:textColor">#ffffff</item>
         <item name="android:fontFamily">sans-serif-light</item>
-        <item name="android:textSize">18dp</item>
+        <item name="android:textSize">@dimen/notification_title_text_size</item>
         <item name="android:textStyle">bold</item>
     </style>
     <style name="TextAppearance.StatusBar.EventContent.Line2">
-        <!-- inherit all -->
+        <item name="android:textSize">@dimen/notification_subtext_size</item>
     </style>
     <style name="TextAppearance.StatusBar.EventContent.Info">
-        <item name="android:textSize">12sp</item>
+        <item name="android:textSize">@dimen/notification_subtext_size</item>
         <item name="android:textColor">#666666</item>
     </style>
     <style name="TextAppearance.StatusBar.EventContent.Time">
-        <item name="android:textSize">12sp</item>
+        <item name="android:textSize">@dimen/notification_subtext_size</item>
         <item name="android:textColor">#666666</item>
     </style>
 
diff --git a/packages/SystemUI/res/anim/search_launch_enter.xml b/packages/SystemUI/res/anim/search_launch_enter.xml
new file mode 100644
index 0000000..055ea5d
--- /dev/null
+++ b/packages/SystemUI/res/anim/search_launch_enter.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shareInterpolator="false" android:zAdjustment="top">
+
+    <alpha android:fromAlpha="0" android:toAlpha="1.0"
+            android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+            android:interpolator="@android:interpolator/decelerate_quad"
+            android:duration="300"/>
+
+    <translate android:fromYDelta="200" android:toYDelta="0"
+            android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+            android:interpolator="@android:interpolator/decelerate_cubic"
+            android:duration="300" />
+</set>
diff --git a/packages/SystemUI/res/anim/search_launch_exit.xml b/packages/SystemUI/res/anim/search_launch_exit.xml
new file mode 100644
index 0000000..b4ed278
--- /dev/null
+++ b/packages/SystemUI/res/anim/search_launch_exit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+       android:interpolator="@android:anim/accelerate_interpolator"
+       android:fromXDelta="0" android:toXDelta="0"
+       android:duration="300" />
diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
index 6b0bb87..28283ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
@@ -18,6 +18,8 @@
 
 import android.animation.Animator;
 import android.animation.LayoutTransition;
+import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
 import android.app.SearchManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
@@ -36,6 +38,7 @@
 
 import com.android.internal.widget.multiwaveview.MultiWaveView;
 import com.android.internal.widget.multiwaveview.MultiWaveView.OnTriggerListener;
+import com.android.server.am.ActivityManagerService;
 import com.android.systemui.R;
 import com.android.systemui.recent.StatusBarTouchProxy;
 import com.android.systemui.statusbar.BaseStatusBar;
@@ -103,26 +106,22 @@
     }
 
     private void startAssistActivity() {
-        if (mSearchManager != null) {
-            ComponentName globalSearchActivity = mSearchManager.getGlobalSearchActivity();
-            if (globalSearchActivity != null) {
-                Intent intent = new Intent(Intent.ACTION_ASSIST);
-                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                intent.setPackage(globalSearchActivity.getPackageName());
-                try {
-                    mContext.startActivity(intent);
-                } catch (ActivityNotFoundException e) {
-                    Slog.w(TAG, "Activity not found for " + intent.getAction());
-                }
-            } else {
-                Slog.w(TAG, "No global search activity");
-            }
+        Intent intent = getAssistIntent();
+        try {
+            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
+                    R.anim.search_launch_enter, R.anim.search_launch_exit);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mContext.startActivity(intent, opts.toBundle());
+        } catch (ActivityNotFoundException e) {
+            Slog.w(TAG, "Activity not found for " + intent.getAction());
         }
     }
 
     final MultiWaveView.OnTriggerListener mMultiWaveViewListener
             = new MultiWaveView.OnTriggerListener() {
 
+        private int mTarget = -1;
+
         public void onGrabbed(View v, int handle) {
         }
 
@@ -136,11 +135,18 @@
         }
 
         public void onTrigger(View v, int target) {
-            final int resId = mMultiWaveView.getResourceIdForTarget(target);
-            switch (resId) {
-                case com.android.internal.R.drawable.ic_lockscreen_search:
-                    startAssistActivity();
-                break;
+            mTarget = target;
+        }
+
+        public void onFinishFinalAnimation() {
+            if (mTarget != -1) {
+                final int resId = mMultiWaveView.getResourceIdForTarget(mTarget);
+                mTarget = -1; // a safety to make sure we never launch w/o prior call to onTrigger
+                switch (resId) {
+                    case com.android.internal.R.drawable.ic_lockscreen_search:
+                        startAssistActivity();
+                    break;
+                }
             }
             mBar.hideSearchPanel();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 69d2e73..d38611d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -515,14 +515,8 @@
         public boolean onTouch(View v, MotionEvent event) {
             switch(event.getAction()) {
                 case MotionEvent.ACTION_DOWN:
-                    Slog.d(TAG, "showing search panel");
                     showSearchPanel();
                 break;
-
-                case MotionEvent.ACTION_UP:
-                    Slog.d(TAG, "hiding search panel");
-                    hideSearchPanel();
-                break;
             }
             return false;
         }
@@ -533,8 +527,8 @@
 
         mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
         mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPanel);
+        mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener);
         updateSearchPanel();
-//        mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener);
     }
 
     // For small-screen devices (read: phones) that lack hardware navigation buttons
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index dba1606..10c5dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -188,6 +188,17 @@
 
     public Context getContext() { return mContext; }
 
+    private View.OnTouchListener mHomeSearchActionListener = new View.OnTouchListener() {
+        public boolean onTouch(View v, MotionEvent event) {
+            switch(event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    showSearchPanel();
+                break;
+            }
+            return false;
+        }
+    };
+
     @Override
     protected void createAndAddWindows() {
         addStatusBarWindow();
@@ -290,6 +301,7 @@
 
         // Search Panel
         mStatusBarView.setBar(this);
+        mHomeButton.setOnTouchListener(mHomeSearchActionListener);
         updateSearchPanel();
 
         // Input methods Panel
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java
index 22f70a5..30cb530 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/LockScreen.java
@@ -401,6 +401,10 @@
         public void cleanUp() {
             mMultiWaveView.setOnTriggerListener(null);
         }
+
+        public void onFinishFinalAnimation() {
+
+        }
     }
 
     private void requestUnlockScreen() {
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 3fbac38..3e35b20d 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -16,7 +16,6 @@
 
 package com.android.server.accessibility;
 
-import com.android.server.accessibility.TouchExplorer.GestureListener;
 import com.android.server.input.InputFilter;
 
 import android.content.Context;
@@ -40,7 +39,7 @@
 
     private final PowerManager mPm;
 
-    private final GestureListener mGestureListener;
+    private final AccessibilityManagerService mAms;
 
     /**
      * This is an interface for explorers that take a {@link MotionEvent}
@@ -73,10 +72,10 @@
 
     private int mTouchscreenSourceDeviceId;
 
-    public AccessibilityInputFilter(Context context, GestureListener gestureListener) {
+    public AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
         super(context.getMainLooper());
         mContext = context;
-        mGestureListener = gestureListener;
+        mAms = service;
         mPm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
     }
 
@@ -85,7 +84,7 @@
         if (DEBUG) {
             Slog.d(TAG, "Accessibility input filter installed.");
         }
-        mTouchExplorer = new TouchExplorer(this, mContext, mGestureListener);
+        mTouchExplorer = new TouchExplorer(this, mContext, mAms);
         super.onInstalled();
     }
 
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index f23b25e..ebc2074 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -24,12 +24,15 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
@@ -58,7 +61,9 @@
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -66,8 +71,8 @@
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
 
+import com.android.internal.R;
 import com.android.internal.content.PackageMonitor;
-import com.android.server.accessibility.TouchExplorer.GestureListener;
 import com.android.server.wm.WindowManagerService;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -90,8 +95,7 @@
  *
  * @hide
  */
-public class AccessibilityManagerService extends IAccessibilityManager.Stub
-        implements GestureListener {
+public class AccessibilityManagerService extends IAccessibilityManager.Stub {
 
     private static final boolean DEBUG = false;
 
@@ -102,6 +106,8 @@
 
     private static final int OWN_PROCESS_ID = android.os.Process.myPid();
 
+    private static final int MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG = 1;
+
     private static int sIdCounter = 0;
 
     private static int sNextWindowId;
@@ -128,6 +134,8 @@
 
     private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
 
+    private final Rect mTempRect = new Rect();
+
     private PackageManager mPackageManager;
 
     private int mHandledFeedbackTypes = 0;
@@ -146,23 +154,15 @@
 
     private final SecurityPolicy mSecurityPolicy;
 
+    private final MainHanler mMainHandler;
+
     private Service mUiAutomationService;
 
-    /**
-     * Handler for delayed event dispatch.
-     */
-    private Handler mHandler = new Handler() {
+    private Service mQueryBridge;
 
-        @Override
-        public void handleMessage(Message message) {
-            Service service = (Service) message.obj;
-            int eventType = message.arg1;
+    private boolean mTouchExplorationGestureEnded;
 
-            synchronized (mLock) {
-                notifyAccessibilityEventLocked(service, eventType);
-            }
-        }
-    };
+    private boolean mTouchExplorationGestureStarted;
 
     /**
      * Creates a new instance.
@@ -175,7 +175,7 @@
         mWindowManagerService = (WindowManagerService) ServiceManager.getService(
                 Context.WINDOW_SERVICE);
         mSecurityPolicy = new SecurityPolicy();
-
+        mMainHandler = new MainHanler();
         registerPackageChangeAndBootCompletedBroadcastReceiver();
         registerSettingsContentObservers();
     }
@@ -349,15 +349,37 @@
     }
 
     public boolean sendAccessibilityEvent(AccessibilityEvent event) {
+        // The event for gesture start should be strictly before the
+        // first hover enter event for the gesture.
+        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
+                && mTouchExplorationGestureStarted) {
+            mTouchExplorationGestureStarted = false;
+            AccessibilityEvent gestureStartEvent = AccessibilityEvent.obtain(
+                    AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
+            sendAccessibilityEvent(gestureStartEvent);
+        }
+
         synchronized (mLock) {
             if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) {
-                mSecurityPolicy.updateRetrievalAllowingWindowAndEventSourceLocked(event);
+                mSecurityPolicy.updateActiveWindowAndEventSourceLocked(event);
                 notifyAccessibilityServicesDelayedLocked(event, false);
                 notifyAccessibilityServicesDelayedLocked(event, true);
             }
         }
+
+        // The event for gesture end should be strictly after the
+        // last hover exit event for the gesture.
+        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+                && mTouchExplorationGestureEnded) {
+            mTouchExplorationGestureEnded = false;
+            AccessibilityEvent gestureEndEvent = AccessibilityEvent.obtain(
+                    AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
+            sendAccessibilityEvent(gestureEndEvent);
+        }
+
         event.recycle();
         mHandledFeedbackTypes = 0;
+
         return (OWN_PROCESS_ID != Binder.getCallingPid());
     }
 
@@ -472,8 +494,7 @@
         }
     }
 
-    @Override
-    public boolean onGesture(int gestureId) {
+    boolean onGesture(int gestureId) {
         synchronized (mLock) {
             boolean handled = notifyGestureLocked(gestureId, false);
             if (!handled) {
@@ -483,6 +504,65 @@
         }
     }
 
+    /**
+     * Gets the bounds of the accessibility focus if the provided,
+     * point coordinates are within the currently active window
+     * and accessibility focus is found within the latter.
+     *
+     * @param x X coordinate.
+     * @param y Y coordinate
+     * @param outBounds The output to which to write the focus bounds.
+     * @return The accessibility focus rectangle if the point is in the
+     *     window and the window has accessibility focus.
+     */
+    boolean getAccessibilityFocusBounds(int x, int y, Rect outBounds) {
+        // Instead of keeping track of accessibility focus events per
+        // window to be able to find the focus in the active window,
+        // we take a stateless approach and look it up. This is fine
+        // since we do this only when the user clicks/long presses.
+        Service service = getQueryBridge();
+        final int connectionId = service.mId;
+        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+        client.addConnection(connectionId, service);
+        try {
+            AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
+                    .getRootInActiveWindow(connectionId);
+            if (root == null) {
+                return false;
+            }
+            Rect bounds = mTempRect;
+            root.getBoundsInScreen(bounds);
+            if (!bounds.contains(x, y)) {
+                return false;
+            }
+            AccessibilityNodeInfo focus = root.findFocus(
+                    AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+            if (focus == null) {
+                return false;
+            }
+            focus.getBoundsInScreen(outBounds);
+            return true;
+        } finally {
+            client.removeConnection(connectionId);
+        }
+    }
+
+    private Service getQueryBridge() {
+        if (mQueryBridge == null) {
+            AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+            mQueryBridge = new Service(null, info, true);
+        }
+        return mQueryBridge;
+    }
+
+    public void touchExplorationGestureEnded() {
+        mTouchExplorationGestureEnded = true;
+    }
+
+    public void touchExplorationGestureStarted() {
+        mTouchExplorationGestureStarted = true;
+    }
+
     private boolean notifyGestureLocked(int gestureId, boolean isDefault) {
         // TODO: Now we are giving the gestures to the last enabled
         //       service that can handle them which is the last one
@@ -496,12 +576,7 @@
         for (int i = mServices.size() - 1; i >= 0; i--) {
             Service service = mServices.get(i);
             if (service.mReqeustTouchExplorationMode && service.mIsDefault == isDefault) {
-                try {
-                    service.mServiceInterface.onGesture(gestureId);
-                } catch (RemoteException re) {
-                    Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
-                            + " to " + service.mService, re);
-                }
+                service.notifyGesture(gestureId);
                 return true;
             }
         }
@@ -573,7 +648,7 @@
                 if (service.mIsDefault == isDefault) {
                     if (canDispathEventLocked(service, event, mHandledFeedbackTypes)) {
                         mHandledFeedbackTypes |= service.mFeedbackType;
-                        notifyAccessibilityServiceDelayedLocked(service, event);
+                        service.notifyAccessibilityEvent(event);
                     }
                 }
             }
@@ -586,90 +661,6 @@
     }
 
     /**
-     * Performs an {@link AccessibilityService} delayed notification. The delay is configurable
-     * and denotes the period after the last event before notifying the service.
-     *
-     * @param service The service.
-     * @param event The event.
-     */
-    private void notifyAccessibilityServiceDelayedLocked(Service service,
-            AccessibilityEvent event) {
-        synchronized (mLock) {
-            final int eventType = event.getEventType();
-            // Make a copy since during dispatch it is possible the event to
-            // be modified to remove its source if the receiving service does
-            // not have permission to access the window content.
-            AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
-            AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType);
-            service.mPendingEvents.put(eventType, newEvent);
-
-            final int what = eventType | (service.mId << 16);
-            if (oldEvent != null) {
-                mHandler.removeMessages(what);
-                oldEvent.recycle();
-            }
-
-            Message message = mHandler.obtainMessage(what, service);
-            message.arg1 = eventType;
-            mHandler.sendMessageDelayed(message, service.mNotificationTimeout);
-        }
-    }
-
-    /**
-     * Notifies an accessibility service client for a scheduled event given the event type.
-     *
-     * @param service The service client.
-     * @param eventType The type of the event to dispatch.
-     */
-    private void notifyAccessibilityEventLocked(Service service, int eventType) {
-        IAccessibilityServiceClient listener = service.mServiceInterface;
-
-        // If the service died/was disabled while the message for dispatching
-        // the accessibility event was propagating the listener may be null.
-        if (listener == null) {
-            return;
-        }
-
-        AccessibilityEvent event = service.mPendingEvents.get(eventType);
-
-        // Check for null here because there is a concurrent scenario in which this
-        // happens: 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
-        // which posts a message for dispatching an event. 2) The message is pulled
-        // from the queue by the handler on the service thread and the latter is
-        // just about to acquire the lock and call this method. 3) Now another binder
-        // thread acquires the lock calling notifyAccessibilityServiceDelayedLocked
-        // so the service thread waits for the lock; 4) The binder thread replaces
-        // the event with a more recent one (assume the same event type) and posts a
-        // dispatch request releasing the lock. 5) Now the main thread is unblocked and
-        // dispatches the event which is removed from the pending ones. 6) And ... now
-        // the service thread handles the last message posted by the last binder call
-        // but the event is already dispatched and hence looking it up in the pending
-        // ones yields null. This check is much simpler that keeping count for each
-        // event type of each service to catch such a scenario since only one message
-        // is processed at a time.
-        if (event == null) {
-            return;
-        }
-
-        service.mPendingEvents.remove(eventType);
-        try {
-            if (mSecurityPolicy.canRetrieveWindowContent(service)) {
-                event.setConnectionId(service.mId);
-            } else {
-                event.setSource(null);
-            }
-            event.setSealed(true);
-            listener.onAccessibilityEvent(event);
-            event.recycle();
-            if (DEBUG) {
-                Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
-            }
-        } catch (RemoteException re) {
-            Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
-        }
-    }
-
-    /**
      * Adds a service.
      *
      * @param service The service to add.
@@ -683,6 +674,7 @@
             mServices.add(service);
             mComponentNameToServiceMap.put(service.mComponentName, service);
             updateInputFilterLocked();
+            tryEnableTouchExploration(service);
         } catch (RemoteException e) {
             /* do nothing */
         }
@@ -700,10 +692,10 @@
             return false;
         }
         mComponentNameToServiceMap.remove(service.mComponentName);
-        mHandler.removeMessages(service.mId);
         service.unlinkToOwnDeath();
         service.dispose();
         updateInputFilterLocked();
+        tryDisableTouchExploration(service);
         return removed;
     }
 
@@ -932,6 +924,29 @@
                 Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1;
     }
 
+    private void tryEnableTouchExploration(final Service service) {
+        if (!mIsTouchExplorationEnabled && service.mRequestTouchExplorationMode) {
+            mMainHandler.obtainMessage(MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG,
+                    service).sendToTarget();
+        }
+    }
+
+    private void tryDisableTouchExploration(Service service) {
+        if (mIsTouchExplorationEnabled && service.mReqeustTouchExplorationMode) {
+            synchronized (mLock) {
+                final int serviceCount = mServices.size();
+                for (int i = 0; i < serviceCount; i++) {
+                    Service other = mServices.get(i);
+                    if (other != service && other.mRequestTouchExplorationMode) {
+                        return;
+                    }
+                }
+                Settings.Secure.putInt(mContext.getContentResolver(),
+                        Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0);
+            }
+        }
+    }
+
     private class AccessibilityConnectionWrapper implements DeathRecipient {
         private final int mWindowId;
         private final IAccessibilityInteractionConnection mConnection;
@@ -959,6 +974,42 @@
         }
     }
 
+    private class MainHanler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            final int type = msg.what;
+            switch (type) {
+                case MSG_SHOW_ENABLE_TOUCH_EXPLORATION_DIALOG: {
+                    Service service = (Service) msg.obj;
+                    String label = service.mResolveInfo.loadLabel(
+                            mContext.getPackageManager()).toString();
+                    final AlertDialog dialog = new AlertDialog.Builder(mContext)
+                        .setIcon(android.R.drawable.ic_dialog_alert)
+                        .setPositiveButton(android.R.string.ok, new OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                Settings.Secure.putInt(mContext.getContentResolver(),
+                                        Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1);
+                            }
+                        })
+                        .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                dialog.dismiss();
+                            }
+                        })
+                        .setTitle(R.string.enable_explore_by_touch_warning_title)
+                        .setMessage(mContext.getString(
+                            R.string.enable_explore_by_touch_warning_message, label))
+                        .create();
+                    dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
+                    dialog.setCanceledOnTouchOutside(true);
+                    dialog.show();
+                }
+            }
+        }
+    }
+
     /**
      * This class represents an accessibility service. It stores all per service
      * data required for the service management, provides API for starting/stopping the
@@ -969,6 +1020,11 @@
      */
     class Service extends IAccessibilityServiceConnection.Stub
             implements ServiceConnection, DeathRecipient {
+
+        // We pick the MSB to avoid collision since accessibility event types are
+        // used as message types allowing us to remove messages per event type. 
+        private static final int MSG_ON_GESTURE = 0x80000000;
+
         int mId = 0;
 
         AccessibilityServiceInfo mAccessibilityServiceInfo;
@@ -985,6 +1041,8 @@
 
         boolean mIsDefault;
 
+        boolean mRequestTouchExplorationMode;
+
         boolean mIncludeNotImportantViews;
 
         long mNotificationTimeout;
@@ -1001,12 +1059,35 @@
 
         final Rect mTempBounds = new Rect();
 
+        final ResolveInfo mResolveInfo;
+
         // the events pending events to be dispatched to this service
         final SparseArray<AccessibilityEvent> mPendingEvents =
             new SparseArray<AccessibilityEvent>();
 
+        /**
+         * Handler for delayed event dispatch.
+         */
+        public Handler mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message message) {
+                final int type = message.what;
+                switch (type) {
+                    case MSG_ON_GESTURE: {
+                        final int gestureId = message.arg1;
+                        notifyGestureInternal(gestureId);
+                    } break;
+                    default: {
+                        final int eventType = type;
+                        notifyAccessibilityEventInternal(eventType);
+                    } break;
+                }
+            }
+        };
+
         public Service(ComponentName componentName,
                 AccessibilityServiceInfo accessibilityServiceInfo, boolean isAutomation) {
+            mResolveInfo = accessibilityServiceInfo.getResolveInfo();
             mId = sIdCounter++;
             mComponentName = componentName;
             mAccessibilityServiceInfo = accessibilityServiceInfo;
@@ -1043,6 +1124,9 @@
                     (info.flags & FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
             }
 
+            mRequestTouchExplorationMode = (info.flags
+                    & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+
             synchronized (mLock) {
                 tryAddServiceLocked(this);
             }
@@ -1403,6 +1487,108 @@
             }
         }
 
+        /**
+         * Performs a notification for an {@link AccessibilityEvent}.
+         *
+         * @param event The event.
+         */
+        public void notifyAccessibilityEvent(AccessibilityEvent event) {
+            synchronized (mLock) {
+                final int eventType = event.getEventType();
+                // Make a copy since during dispatch it is possible the event to
+                // be modified to remove its source if the receiving service does
+                // not have permission to access the window content.
+                AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
+                AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
+                mPendingEvents.put(eventType, newEvent);
+
+                final int what = eventType;
+                if (oldEvent != null) {
+                    mHandler.removeMessages(what);
+                    oldEvent.recycle();
+                }
+
+                Message message = mHandler.obtainMessage(what);
+                mHandler.sendMessageDelayed(message, mNotificationTimeout);
+            }
+        }
+
+        /**
+         * Notifies an accessibility service client for a scheduled event given the event type.
+         *
+         * @param eventType The type of the event to dispatch.
+         */
+        private void notifyAccessibilityEventInternal(int eventType) {
+            IAccessibilityServiceClient listener;
+            AccessibilityEvent event;
+
+            synchronized (mLock) {
+                listener = mServiceInterface;
+
+                // If the service died/was disabled while the message for dispatching
+                // the accessibility event was propagating the listener may be null.
+                if (listener == null) {
+                    return;
+                }
+
+                event = mPendingEvents.get(eventType);
+
+                // Check for null here because there is a concurrent scenario in which this
+                // happens: 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
+                // which posts a message for dispatching an event. 2) The message is pulled
+                // from the queue by the handler on the service thread and the latter is
+                // just about to acquire the lock and call this method. 3) Now another binder
+                // thread acquires the lock calling notifyAccessibilityServiceDelayedLocked
+                // so the service thread waits for the lock; 4) The binder thread replaces
+                // the event with a more recent one (assume the same event type) and posts a
+                // dispatch request releasing the lock. 5) Now the main thread is unblocked and
+                // dispatches the event which is removed from the pending ones. 6) And ... now
+                // the service thread handles the last message posted by the last binder call
+                // but the event is already dispatched and hence looking it up in the pending
+                // ones yields null. This check is much simpler that keeping count for each
+                // event type of each service to catch such a scenario since only one message
+                // is processed at a time.
+                if (event == null) {
+                    return;
+                }
+
+                mPendingEvents.remove(eventType);
+                if (mSecurityPolicy.canRetrieveWindowContent(this)) {
+                    event.setConnectionId(mId);
+                } else {
+                    event.setSource(null);
+                }
+                event.setSealed(true);
+            }
+
+            try {
+                listener.onAccessibilityEvent(event);
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
+                }
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
+            } finally {
+                event.recycle();
+            }
+        }
+
+        public void notifyGesture(int gestureId) {
+            mHandler.obtainMessage(MSG_ON_GESTURE, gestureId, 0).sendToTarget();
+        }
+
+        private void notifyGestureInternal(int gestureId) {
+            IAccessibilityServiceClient listener = mServiceInterface;
+            if (listener != null) {
+                try {
+                    listener.onGesture(gestureId);
+                } catch (RemoteException re) {
+                    Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
+                            + " to " + mService, re);
+                }
+            }
+        }
+
         private void sendDownAndUpKeyEvents(int keyCode) {
             final long token = Binder.clearCallingIdentity();
 
@@ -1454,7 +1640,7 @@
 
         private int resolveAccessibilityWindowId(int accessibilityWindowId) {
             if (accessibilityWindowId == AccessibilityNodeInfo.ACTIVE_WINDOW_ID) {
-                return mSecurityPolicy.mRetrievalAlowingWindowId;
+                return mSecurityPolicy.mActiveWindowId;
             }
             return accessibilityWindowId;
         }
@@ -1497,24 +1683,35 @@
             | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
             | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
 
-        private static final int RETRIEVAL_ALLOWING_WINDOW_CHANGE_EVENT_TYPES =
-            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
-            | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
-            | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT;
-
-        private int mRetrievalAlowingWindowId;
+        private int mActiveWindowId;
 
         private boolean canDispatchAccessibilityEvent(AccessibilityEvent event) {
             // Send window changed event only for the retrieval allowing window.
             return (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
-                    || event.getWindowId() == mRetrievalAlowingWindowId);
+                    || event.getWindowId() == mActiveWindowId);
         }
 
-        public void updateRetrievalAllowingWindowAndEventSourceLocked(AccessibilityEvent event) {
+        public void updateActiveWindowAndEventSourceLocked(AccessibilityEvent event) {
+            // The active window is either the window that has input focus or
+            // the window that the user is currently touching. If the user is
+            // touching a window that does not have input focus as soon as the
+            // the user stops touching that window the focused window becomes
+            // the active one.
             final int windowId = event.getWindowId();
             final int eventType = event.getEventType();
-            if ((eventType & RETRIEVAL_ALLOWING_WINDOW_CHANGE_EVENT_TYPES) != 0) {
-                mRetrievalAlowingWindowId = windowId;
+            switch (eventType) {
+                case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
+                    if (getFocusedWindowId() == windowId) {
+                        mActiveWindowId = windowId;
+                    }
+                } break;
+                case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
+                case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
+                    mActiveWindowId = windowId;
+                } break;
+                case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: {
+                    mActiveWindowId = getFocusedWindowId();
+                } break;
             }
             if ((eventType & RETRIEVAL_ALLOWING_EVENT_TYPES) == 0) {
                 event.setSource(null);
@@ -1522,7 +1719,7 @@
         }
 
         public int getRetrievalAllowingWindowLocked() {
-            return mRetrievalAlowingWindowId;
+            return mActiveWindowId;
         }
 
         public boolean canGetAccessibilityNodeInfoLocked(Service service, int windowId) {
@@ -1550,7 +1747,7 @@
         }
 
         private boolean isRetrievalAllowingWindow(int windowId) {
-            return (mRetrievalAlowingWindowId == windowId);
+            return (mActiveWindowId == windowId);
         }
 
         private boolean isActionPermitted(int action) {
@@ -1567,5 +1764,22 @@
                         + " required to call " + function);
             }
         }
+
+        private int getFocusedWindowId() {
+            // We call this only on window focus change or after touch
+            // exploration gesture end and the shown windows are not that
+            // many, so the linear look up is just fine.
+            IBinder token = mWindowManagerService.getFocusedWindowClientToken();
+            if (token != null) {
+                SparseArray<IBinder> windows = mWindowIdToWindowTokenMap;
+                final int windowCount = windows.size();
+                for (int i = 0; i < windowCount; i++) {
+                    if (windows.valueAt(i) == token) {
+                        return windows.keyAt(i);
+                    }
+                }
+            }
+            return -1;
+        }
     }
 }
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index 39012e6..b0b2b8d 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -16,9 +16,6 @@
 
 package com.android.server.accessibility;
 
-import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
-import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
-
 import android.content.Context;
 import android.gesture.Gesture;
 import android.gesture.GestureLibraries;
@@ -26,17 +23,19 @@
 import android.gesture.GesturePoint;
 import android.gesture.GestureStroke;
 import android.gesture.Prediction;
+import android.graphics.Rect;
 import android.os.Handler;
+import android.os.SystemClock;
 import android.util.Slog;
 import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerPolicy;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 
-import com.android.server.input.InputFilter;
 import com.android.internal.R;
+import com.android.server.input.InputFilter;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -47,17 +46,18 @@
  * and consuming certain events. The interaction model is:
  *
  * <ol>
- *   <li>1. One finger moving around performs touch exploration.</li>
- *   <li>2. Two close fingers moving in the same direction perform a drag.</li>
- *   <li>3. Multi-finger gestures are delivered to view hierarchy.</li>
- *   <li>4. Pointers that have not moved more than a specified distance after they
+ *   <li>1. One finger moving slow around performs touch exploration.</li>
+ *   <li>2. One finger moving fast around performs gestures.</li>
+ *   <li>3. Two close fingers moving in the same direction perform a drag.</li>
+ *   <li>4. Multi-finger gestures are delivered to view hierarchy.</li>
+ *   <li>5. Pointers that have not moved more than a specified distance after they
  *          went down are considered inactive.</li>
- *   <li>5. Two fingers moving too far from each other or in different directions
- *          are considered a multi-finger gesture.</li>
- *   <li>6. Tapping on the last touch explored location within given time and
- *          distance slop performs a click.</li>
- *   <li>7. Tapping and holding for a while on the last touch explored location within
- *          given time and distance slop performs a long press.</li>
+ *   <li>6. Two fingers moving in different directions are considered a multi-finger gesture.</li>
+ *   <li>7. Double tapping clicks on the on the last touch explored location of it was in
+ *          a window that does not take focus, otherwise the click is within the accessibility
+ *          focused rectangle.</li>
+ *   <li>7. Tapping and holding for a while performs a long press in a similar fashion
+ *          as the click above.</li>
  * <ol>
  *
  * @hide
@@ -75,85 +75,116 @@
     private static final int STATE_DELEGATING = 0x00000004;
     private static final int STATE_GESTURE_DETECTING = 0x00000005;
 
-    // The time slop in milliseconds for activating an item after it has
-    // been touch explored. Tapping on an item within this slop will perform
-    // a click and tapping and holding down a long press.
-    private static final long ACTIVATION_TIME_SLOP = 2000;
-
     // The minimum of the cosine between the vectors of two moving
     // pointers so they can be considered moving in the same direction.
     private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
 
-    // The delay for sending a hover enter event.
-    private static final long DELAY_SEND_HOVER_ENTER = 200;
-
     // Constant referring to the ids bits of all pointers.
     private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
 
     // This constant captures the current implementation detail that
     // pointer IDs are between 0 and 31 inclusive (subject to change).
     // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
-    public static final int MAX_POINTER_COUNT = 32;
+    private static final int MAX_POINTER_COUNT = 32;
 
     // Invalid pointer ID.
-    public static final int INVALID_POINTER_ID = -1;
+    private static final int INVALID_POINTER_ID = -1;
+
+    // The velocity above which we detect gestures.
+    private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
+
+    // The minimal distance before we take the middle of the distance between
+    // the two dragging pointers as opposed to use the location of the primary one.
+    private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
 
     // Temporary array for storing pointer IDs.
     private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
 
-    // The distance from the last touch explored location tapping within
-    // which would perform a click and tapping and holding a long press.
-    private final int mTouchExplorationTapSlop;
+    // Timeout within which we try to detect a tap.
+    private final int mTapTimeout;
+
+    // Timeout within which we try to detect a double tap.
+    private final int mDoubleTapTimeout;
+
+    // Slop between the down and up tap to be a tap.
+    private final int mTouchSlop;
+
+    // Slop between the first and second tap to be a double tap.
+    private final int mDoubleTapSlop;
 
     // The InputFilter this tracker is associated with i.e. the filter
     // which delegates event processing to this touch explorer.
     private final InputFilter mInputFilter;
 
-    // Handle to the accessibility manager for firing accessibility events
-    // announcing touch exploration gesture start and end.
-    private final AccessibilityManager mAccessibilityManager;
-
-    // The last event that was received while performing touch exploration.
-    private MotionEvent mLastTouchExploreEvent;
-
     // The current state of the touch explorer.
     private int mCurrentState = STATE_TOUCH_EXPLORING;
 
-    // Flag whether a touch exploration gesture is in progress.
-    private boolean mTouchExploreGestureInProgress;
-
     // The ID of the pointer used for dragging.
     private int mDraggingPointerId;
 
     // Handler for performing asynchronous operations.
     private final Handler mHandler;
 
-    // Command for delayed sending of a hover event.
-    private final SendHoverDelayed mSendHoverDelayed;
+    // Command for delayed sending of a hover enter event.
+    private final SendHoverDelayed mSendHoverEnterDelayed;
+
+    // Command for delayed sending of a hover exit event.
+    private final SendHoverDelayed mSendHoverExitDelayed;
 
     // Command for delayed sending of a long press.
     private final PerformLongPressDelayed mPerformLongPressDelayed;
 
+    // Helper to detect and react to double tap in touch explore mode.
+    private final DoubleTapDetector mDoubleTapDetector;
+
+    // The scaled minimal distance before we take the middle of the distance between
+    // the two dragging pointers as opposed to use the location of the primary one.
+    private final int mScaledMinPointerDistanceToUseMiddleLocation;
+
+    // The scaled velocity above which we detect gestures.
+    private final int mScaledGestureDetectionVelocity;
+
+    // Helper to track gesture velocity.
     private VelocityTracker mVelocityTracker;
 
+    // Helper class to track received pointers.
     private final ReceivedPointerTracker mReceivedPointerTracker;
 
+    // Helper class to track injected pointers.
     private final InjectedPointerTracker mInjectedPointerTracker;
 
-    private final GestureListener mGestureListener;
+    // Handle to the accessibility manager service.
+    private final AccessibilityManagerService mAms;
 
-    /**
-     * Callback for gesture detection.
-     */
-    public interface GestureListener {
+    // Temporary rectangle to avoid instantiation.
+    private final Rect mTempRect = new Rect();
 
-        /**
-         * Called when a given gesture was performed.
-         *
-         * @param gestureId The gesture id.
-         */
-        public boolean onGesture(int gestureId);
-    }
+    // The X of the previous event.
+    private float mPreviousX;
+
+    // The Y of the previous event.
+    private float mPreviousY;
+
+    // Buffer for storing points for gesture detection.
+    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+    // The minimal delta between moves to add a gesture point.
+    private static final int TOUCH_TOLERANCE = 3;
+
+    // The minimal score for accepting a predicted gesture.
+    private static final float MIN_PREDICTION_SCORE = 2.0f;
+
+    // The library for gesture detection.
+    private GestureLibrary mGestureLibrary;
+
+    // The long pressing pointer id if coordinate remapping is needed.
+    private int mLongPressingPointerId;
+
+    // The long pressing pointer X if coordinate remapping is needed.
+    private int mLongPressingPointerDeltaX;
+
+    // The long pressing pointer Y if coordinate remapping is needed.
+    private int mLongPressingPointerDeltaY;
 
     /**
      * Creates a new instance.
@@ -162,25 +193,73 @@
      * @param context A context handle for accessing resources.
      */
     public TouchExplorer(InputFilter inputFilter, Context context,
-            GestureListener gestureListener) {
-        mGestureListener = gestureListener;
+            AccessibilityManagerService service) {
+        mAms = service;
         mReceivedPointerTracker = new ReceivedPointerTracker(context);
         mInjectedPointerTracker = new InjectedPointerTracker();
         mInputFilter = inputFilter;
-        mTouchExplorationTapSlop =
-            ViewConfiguration.get(context).getScaledTouchExploreTapSlop();
+        mTapTimeout = ViewConfiguration.getTapTimeout();
+        mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
         mHandler = new Handler(context.getMainLooper());
-        mSendHoverDelayed = new SendHoverDelayed();
         mPerformLongPressDelayed = new PerformLongPressDelayed();
-        mAccessibilityManager = AccessibilityManager.getInstance(context);
         mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
         mGestureLibrary.setOrientationStyle(4);
         mGestureLibrary.load();
+        mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true);
+        mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false);
+        mDoubleTapDetector = new DoubleTapDetector();
+        final float density = context.getResources().getDisplayMetrics().density;
+        mScaledMinPointerDistanceToUseMiddleLocation =
+            (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
+        mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
+    }
+
+    public void clear() {
+        // If we have not received an event then we are in initial
+        // state. Therefore, there is not need to clean anything.
+        MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent();
+        if (event != null) {
+            clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED);
+        }
     }
 
     public void clear(MotionEvent event, int policyFlags) {
-        sendUpForInjectedDownPointers(event, policyFlags);
-        clear();
+        switch (mCurrentState) {
+            case STATE_TOUCH_EXPLORING: {
+                // If a touch exploration gesture is in progress send events for its end.
+                sendExitEventsIfNeeded(policyFlags);
+            } break;
+            case STATE_DRAGGING: {
+                mDraggingPointerId = INVALID_POINTER_ID;
+                // Send exit to all pointers that we have delivered.
+                sendUpForInjectedDownPointers(event, policyFlags);
+            } break;
+            case STATE_DELEGATING: {
+                // Send exit to all pointers that we have delivered.
+                sendUpForInjectedDownPointers(event, policyFlags);
+            } break;
+            case STATE_GESTURE_DETECTING: {
+                // Clear the current stroke.
+                mStrokeBuffer.clear();
+            } break;
+        }
+        // Remove all pending callbacks.
+        mSendHoverEnterDelayed.remove();
+        mSendHoverExitDelayed.remove();
+        mPerformLongPressDelayed.remove();
+        // Reset the pointer trackers.
+        mReceivedPointerTracker.clear();
+        mInjectedPointerTracker.clear();
+        // Clear the double tap detector
+        mDoubleTapDetector.clear();
+        // Go to initial state.
+        // Clear the long pressing pointer remap data.
+        mLongPressingPointerId = -1;
+        mLongPressingPointerDeltaX = 0;
+        mLongPressingPointerDeltaY = 0;
+        mCurrentState = STATE_TOUCH_EXPLORING;
     }
 
     public void onMotionEvent(MotionEvent event, int policyFlags) {
@@ -218,7 +297,6 @@
      */
     private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) {
         ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
-        InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
         final int activePointerCount = receivedTracker.getActivePointerCount();
 
         if (mVelocityTracker == null) {
@@ -226,8 +304,16 @@
         }
         mVelocityTracker.addMovement(event);
 
+        mDoubleTapDetector.onMotionEvent(event, policyFlags);
+
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
+                // Pre-feed the motion events to the gesture detector since we
+                // have a distance slop before getting into gesture detection
+                // mode and not using the points within this slop significantly
+                // decreases the quality of gesture recognition.
+                handleMotionEventGestureDetecting(event, policyFlags);
+                //$FALL-THROUGH$
             case MotionEvent.ACTION_POINTER_DOWN: {
                 switch (activePointerCount) {
                     case 0: {
@@ -235,44 +321,31 @@
                                 + "touch exploring state!");
                     }
                     case 1: {
-                        mSendHoverDelayed.remove();
-                        mPerformLongPressDelayed.remove();
-                        // Send a hover for every finger down so the user gets feedback.
+                        // If we still have not notified the user for the last
+                        // touch, we figure out what to do. If were waiting
+                        // we resent the delayed callback and wait again.
+                        if (mSendHoverEnterDelayed.isPending()) {
+                            mSendHoverEnterDelayed.remove();
+                            mSendHoverExitDelayed.remove();
+                            mPerformLongPressDelayed.remove();
+                        }
+
+                        // If we have the first tap schedule a long press and break
+                        // since we do not want to schedule hover enter because
+                        // the delayed callback will kick in before the long click.
+                        // This would lead to a state transition resulting in long
+                        // pressing the item below the double taped area which is
+                        // not necessary where accessibility focus is.
+                        if (mDoubleTapDetector.firstTapDetected()) {
+                            // We got a tap now post a long press action.
+                            mPerformLongPressDelayed.post(event, policyFlags);
+                            break;
+                        }
+                        // Deliver hover enter with a delay to have a chance
+                        // to detect what the user is trying to do.
                         final int pointerId = receivedTracker.getPrimaryActivePointerId();
                         final int pointerIdBits = (1 << pointerId);
-                        final int lastAction = injectedTracker.getLastInjectedHoverAction();
-
-                        // Deliver hover enter with a delay to have a change to detect
-                        // whether the user actually starts a scrolling gesture.
-                        if (lastAction == MotionEvent.ACTION_HOVER_EXIT) {
-                            mSendHoverDelayed.post(event, MotionEvent.ACTION_HOVER_ENTER,
-                                    pointerIdBits, policyFlags, DELAY_SEND_HOVER_ENTER);
-                        } else {
-                            sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
-                                    policyFlags);
-                        }
-
-                        if (mLastTouchExploreEvent == null) {
-                            break;
-                        }
-
-                        // If more pointers down on the screen since the last touch
-                        // exploration we discard the last cached touch explore event.
-                        if (event.getPointerCount() != mLastTouchExploreEvent.getPointerCount()) {
-                            mLastTouchExploreEvent = null;
-                            break;
-                        }
-
-                        // If the down is in the time slop => schedule a long press.
-                        final long pointerDownTime =
-                            receivedTracker.getReceivedPointerDownTime(pointerId);
-                        final long lastExploreTime = mLastTouchExploreEvent.getEventTime();
-                        final long deltaTimeExplore = pointerDownTime - lastExploreTime;
-                        if (deltaTimeExplore <= ACTIVATION_TIME_SLOP) {
-                            mPerformLongPressDelayed.post(event, policyFlags,
-                                    ViewConfiguration.getLongPressTimeout());
-                            break;
-                        }
+                        mSendHoverEnterDelayed.post(event, pointerIdBits, policyFlags);
                     } break;
                     default: {
                         /* do nothing - let the code for ACTION_MOVE decide what to do */
@@ -288,119 +361,130 @@
                         /* do nothing - no active pointers so we swallow the event */
                     } break;
                     case 1: {
-                        // Detect touch exploration gesture start by having one active pointer
-                        // that moved more than a given distance.
-                        if (!mTouchExploreGestureInProgress) {
+                        // We have not started sending events since we try to
+                        // figure out what the user is doing.
+                        if (mSendHoverEnterDelayed.isPending()) {
+                            // Pre-feed the motion events to the gesture detector since we
+                            // have a distance slop before getting into gesture detection
+                            // mode and not using the points within this slop significantly
+                            // decreases the quality of gesture recognition.
+                            handleMotionEventGestureDetecting(event, policyFlags);
+
                             final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
                                 - event.getX(pointerIndex);
                             final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
                                 - event.getY(pointerIndex);
                             final double moveDelta = Math.hypot(deltaX, deltaY);
-
-                            if (moveDelta > mTouchExplorationTapSlop) {
-
+                            // The user has moved enough for us to decide.
+                            if (moveDelta > mDoubleTapSlop) {
+                                // Check whether the user is performing a gesture. We
+                                // detect gestures if the pointer is moving above a
+                                // given velocity.
                                 mVelocityTracker.computeCurrentVelocity(1000);
                                 final float maxAbsVelocity = Math.max(
                                         Math.abs(mVelocityTracker.getXVelocity(pointerId)),
                                         Math.abs(mVelocityTracker.getYVelocity(pointerId)));
-                                // TODO: Tune the velocity cut off and add a constant.
-                                if (maxAbsVelocity > 1000) {
-                                    clear(event, policyFlags);
+                                if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
+                                    // We have to perform gesture detection, so
+                                    // clear the current state and try to detect.
                                     mCurrentState = STATE_GESTURE_DETECTING;
-                                    event.setAction(MotionEvent.ACTION_DOWN);
-                                    handleMotionEventGestureDetecting(event, policyFlags);
-                                    return;
-                                }
-
-                                mTouchExploreGestureInProgress = true;
-                                sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
-                                // Make sure the scheduled down/move event is sent.
-                                mSendHoverDelayed.forceSendAndRemove();
-                                mPerformLongPressDelayed.remove();
-                                // If we have transitioned to exploring state from another one
-                                // we need to send a hover enter event here.
-                                final int lastAction = injectedTracker.getLastInjectedHoverAction();
-                                if (lastAction == MotionEvent.ACTION_HOVER_EXIT) {
-                                    sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER,
+                                    mSendHoverEnterDelayed.remove();
+                                    mSendHoverExitDelayed.remove();
+                                    mPerformLongPressDelayed.remove();
+                                } else {
+                                    // We have just decided that the user is touch,
+                                    // exploring so start sending events.
+                                    mSendHoverEnterDelayed.forceSendAndRemove();
+                                    mSendHoverExitDelayed.remove();
+                                    sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
                                             pointerIdBits, policyFlags);
                                 }
-                                sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
-                                        policyFlags);
+                                break;
                             }
                         } else {
-                            // Touch exploration gesture in progress so send a hover event.
+                            // The user is wither double tapping or performing long
+                            // press so do not send move events yet.
+                            if (mDoubleTapDetector.firstTapDetected()) {
+                                break;
+                            }
+                            sendEnterEventsIfNeeded(policyFlags);
                             sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
                                     policyFlags);
                         }
-
-                        // If the exploring pointer moved enough => cancel the long press.
-                        if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null
-                                && mPerformLongPressDelayed.isPenidng()) {
-
-                            // If the pointer moved more than the tap slop => cancel long press.
-                            final float deltaX = mLastTouchExploreEvent.getX(pointerIndex)
-                                    - event.getX(pointerIndex);
-                            final float deltaY = mLastTouchExploreEvent.getY(pointerIndex)
-                                    - event.getY(pointerIndex);
-                            final float moveDelta = (float) Math.hypot(deltaX, deltaY);
-                            if (moveDelta > mTouchExplorationTapSlop) {
-                                mLastTouchExploreEvent = null;
-                                mPerformLongPressDelayed.remove();
-                                break;
-                            }
-                        }
                     } break;
                     case 2: {
-                        mSendHoverDelayed.remove();
-                        mPerformLongPressDelayed.remove();
-                        // We want to no longer hover over the location so subsequent
-                        // touch at the same spot will generate a hover enter.
-                        ensureHoverExitSent(event, pointerIdBits, policyFlags);
+                        // More than one pointer so the user is not touch exploring
+                        // and now we have to decide whether to delegate or drag.
+                        if (mSendHoverEnterDelayed.isPending()) {
+                            // We have not started sending events so cancel
+                            // scheduled sending events.
+                            mSendHoverEnterDelayed.remove();
+                            mSendHoverExitDelayed.remove();
+                            mPerformLongPressDelayed.remove();
+                        } else {
+                            // If the user is touch exploring the second pointer may be
+                            // performing a double tap to activate an item without need
+                            // for the user to lift his exploring finger.
+                            final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
+                                    - event.getX(pointerIndex);
+                            final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
+                                    - event.getY(pointerIndex);
+                            final double moveDelta = Math.hypot(deltaX, deltaY);
+                            if (moveDelta < mDoubleTapSlop) {
+                                break;
+                            }
+                            // We are sending events so send exit and gesture
+                            // end since we transition to another state.
+                            sendExitEventsIfNeeded(policyFlags);
+                        }
+
+                        // We know that a new state transition is to happen and the
+                        // new state will not be gesture recognition, so clear the
+                        // stashed gesture strokes.
+                        mStrokeBuffer.clear();
 
                         if (isDraggingGesture(event)) {
                             // Two pointers moving in the same direction within
                             // a given distance perform a drag.
+                            mSendHoverEnterDelayed.remove();
+                            mSendHoverExitDelayed.remove();
+                            mPerformLongPressDelayed.remove();
                             mCurrentState = STATE_DRAGGING;
-                            if (mTouchExploreGestureInProgress) {
-                                sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
-                                mTouchExploreGestureInProgress = false;
-                            }
-                            mLastTouchExploreEvent = null;
                             mDraggingPointerId = pointerId;
                             sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits,
                                     policyFlags);
                         } else {
                             // Two pointers moving arbitrary are delegated to the view hierarchy.
                             mCurrentState = STATE_DELEGATING;
-                            mSendHoverDelayed.remove();
-                            if (mTouchExploreGestureInProgress) {
-                                sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
-                                mTouchExploreGestureInProgress = false;
-                            }
-                            mLastTouchExploreEvent = null;
                             sendDownForAllActiveNotInjectedPointers(event, policyFlags);
                         }
                     } break;
                     default: {
-                        mSendHoverDelayed.remove();
-                        mPerformLongPressDelayed.remove();
-                        // We want to no longer hover over the location so subsequent
-                        // touch at the same spot will generate a hover enter.
-                        ensureHoverExitSent(event, pointerIdBits, policyFlags);
+                        // More than one pointer so the user is not touch exploring
+                        // and now we have to decide whether to delegate or drag.
+                        if (mSendHoverEnterDelayed.isPending()) {
+                            // We have not started sending events so cancel
+                            // scheduled sending events.
+                            mSendHoverEnterDelayed.remove();
+                            mSendHoverExitDelayed.remove();
+                            mPerformLongPressDelayed.remove();
+                        } else {
+                            // We are sending events so send exit and gesture
+                            // end since we transition to another state.
+                            sendExitEventsIfNeeded(policyFlags);
+                        }
 
                         // More than two pointers are delegated to the view hierarchy.
                         mCurrentState = STATE_DELEGATING;
-                        mSendHoverDelayed.remove();
-                        if (mTouchExploreGestureInProgress) {
-                            sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
-                            mTouchExploreGestureInProgress = false;
-                        }
-                        mLastTouchExploreEvent = null;
                         sendDownForAllActiveNotInjectedPointers(event, policyFlags);
                     }
                 }
             } break;
             case MotionEvent.ACTION_UP:
+                // We know that we do not need the pre-fed gesture points are not
+                // needed anymore since the last pointer just went up.
+                mStrokeBuffer.clear();
+                //$FALL-THROUGH$
             case MotionEvent.ACTION_POINTER_UP: {
                 final int pointerId = receivedTracker.getLastReceivedUpPointerId();
                 final int pointerIdBits = (1 << pointerId);
@@ -413,59 +497,12 @@
 
                         mPerformLongPressDelayed.remove();
 
-                        // If touch exploring announce the end of the gesture.
-                        // Also do not click on the last explored location.
-                        if (mTouchExploreGestureInProgress) {
-                            mTouchExploreGestureInProgress = false;
-                            mSendHoverDelayed.forceSendAndRemove();
-                            ensureHoverExitSent(event, pointerIdBits, policyFlags);
-                            mLastTouchExploreEvent = MotionEvent.obtain(event);
-                            sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
-                            break;
-                        }
-
-                        // Detect whether to activate i.e. click on the last explored location.
-                        if (mLastTouchExploreEvent != null) {
-                            // If the down was not in the time slop => nothing else to do.
-                            final long eventTime =
-                                receivedTracker.getLastReceivedUpPointerDownTime();
-                            final long exploreTime = mLastTouchExploreEvent.getEventTime();
-                            final long deltaTime = eventTime - exploreTime;
-                            if (deltaTime > ACTIVATION_TIME_SLOP) {
-                                mSendHoverDelayed.forceSendAndRemove();
-                                ensureHoverExitSent(event, pointerIdBits, policyFlags);
-                                mLastTouchExploreEvent = MotionEvent.obtain(event);
-                                break;
-                            }
-
-                            // If a tap is farther than the tap slop => nothing to do.
-                            final int pointerIndex = event.findPointerIndex(pointerId);
-                            final float deltaX = mLastTouchExploreEvent.getX(pointerIndex)
-                                    - event.getX(pointerIndex);
-                            final float deltaY = mLastTouchExploreEvent.getY(pointerIndex)
-                                    - event.getY(pointerIndex);
-                            final float deltaMove = (float) Math.hypot(deltaX, deltaY);
-                            if (deltaMove > mTouchExplorationTapSlop) {
-                                mSendHoverDelayed.forceSendAndRemove();
-                                ensureHoverExitSent(event, pointerIdBits, policyFlags);
-                                mLastTouchExploreEvent = MotionEvent.obtain(event);
-                                break;
-                            }
-
-                            // This is a tap so do not send hover events since
-                            // this events will result in firing the corresponding
-                            // accessibility events confusing the user about what
-                            // is actually clicked.
-                            mSendHoverDelayed.remove();
-                            ensureHoverExitSent(event, pointerIdBits, policyFlags);
-
-                            // All preconditions are met, so click the last explored location.
-                            sendActionDownAndUp(mLastTouchExploreEvent, policyFlags);
-                            mLastTouchExploreEvent = null;
+                        // If we have not delivered the enter schedule exit.
+                        if (mSendHoverEnterDelayed.isPending()) {
+                            mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
                         } else {
-                            mSendHoverDelayed.forceSendAndRemove();
-                            ensureHoverExitSent(event, pointerIdBits, policyFlags);
-                            mLastTouchExploreEvent = MotionEvent.obtain(event);
+                            // The user is touch exploring so we send events for end.
+                            sendExitEventsIfNeeded(policyFlags);
                         }
                     } break;
                 }
@@ -475,16 +512,7 @@
                 }
             } break;
             case MotionEvent.ACTION_CANCEL: {
-                mSendHoverDelayed.remove();
-                mPerformLongPressDelayed.remove();
-                final int pointerId = receivedTracker.getPrimaryActivePointerId();
-                final int pointerIdBits = (1 << pointerId);                
-                ensureHoverExitSent(event, pointerIdBits, policyFlags);
-                clear();
-                if (mVelocityTracker != null) {
-                    mVelocityTracker.clear();
-                    mVelocityTracker = null;
-                }
+                clear(event, policyFlags);
             } break;
         }
     }
@@ -517,6 +545,28 @@
                     } break;
                     case 2: {
                         if (isDraggingGesture(event)) {
+                            // If the dragging pointer are closer that a given distance we
+                            // use the location of the primary one. Otherwise, we take the
+                            // middle between the pointers.
+                            int[] pointerIds = mTempPointerIds;
+                            mReceivedPointerTracker.populateActivePointerIds(pointerIds);
+
+                            final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
+                            final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
+
+                            final float firstPtrX = event.getX(firstPtrIndex);
+                            final float firstPtrY = event.getY(firstPtrIndex);
+                            final float secondPtrX = event.getX(secondPtrIndex);
+                            final float secondPtrY = event.getY(secondPtrIndex);
+
+                            final float deltaX = firstPtrX - secondPtrX;
+                            final float deltaY = firstPtrY - secondPtrY;
+                            final double distance = Math.hypot(deltaX, deltaY);
+
+                            if (distance > mScaledMinPointerDistanceToUseMiddleLocation) {
+                                event.setLocation(deltaX / 2, deltaY / 2);
+                            }
+
                             // If still dragging send a drag event.
                             sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
                                     policyFlags);
@@ -557,7 +607,7 @@
                 mCurrentState = STATE_TOUCH_EXPLORING;
             } break;
             case MotionEvent.ACTION_CANCEL: {
-                clear();
+                clear(event, policyFlags);
             } break;
         }
     }
@@ -574,9 +624,6 @@
                 throw new IllegalStateException("Delegating state can only be reached if "
                         + "there is at least one pointer down!");
             }
-            case MotionEvent.ACTION_UP: {
-                mCurrentState = STATE_TOUCH_EXPLORING;
-            } break;
             case MotionEvent.ACTION_MOVE: {
                 // Check  whether some other pointer became active because they have moved
                 // a given distance and if such exist send them to the view hierarchy
@@ -587,30 +634,24 @@
                     sendDownForAllActiveNotInjectedPointers(prototype, policyFlags);
                 }
             } break;
+            case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_POINTER_UP: {
+                mLongPressingPointerId = -1;
+                mLongPressingPointerDeltaX = 0;
+                mLongPressingPointerDeltaY = 0;
                 // No active pointers => go to initial state.
                 if (mReceivedPointerTracker.getActivePointerCount() == 0) {
                     mCurrentState = STATE_TOUCH_EXPLORING;
                 }
             } break;
             case MotionEvent.ACTION_CANCEL: {
-                clear();
+                clear(event, policyFlags);
             } break;
         }
         // Deliver the event striping out inactive pointers.
         sendMotionEventStripInactivePointers(event, policyFlags);
     }
 
-    private float mPreviousX;
-    private float mPreviousY;
-
-    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
-
-    private static final int TOUCH_TOLERANCE = 3;
-    private static final float MIN_PREDICTION_SCORE = 2.0f;
-
-    private GestureLibrary mGestureLibrary;
-
     private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
@@ -631,8 +672,7 @@
                     mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
                 }
             } break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_POINTER_UP: {
+            case MotionEvent.ACTION_UP: {
                 float x = event.getX();
                 float y = event.getY();
                 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
@@ -650,7 +690,7 @@
                         }
                         try {
                             final int gestureId = Integer.parseInt(bestPrediction.name);
-                            mGestureListener.onGesture(gestureId);
+                            mAms.onGesture(gestureId);
                         } catch (NumberFormatException nfe) {
                             Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
                         }
@@ -661,8 +701,7 @@
                 mCurrentState = STATE_TOUCH_EXPLORING;
             } break;
             case MotionEvent.ACTION_CANCEL: {
-                mStrokeBuffer.clear();
-                mCurrentState = STATE_TOUCH_EXPLORING;
+                clear(event, policyFlags);
             } break;
         }
     }
@@ -706,17 +745,32 @@
     }
 
     /**
-     * Ensures that hover exit has been sent.
+     * Sends the exit events if needed. Such events are hover exit and touch explore
+     * gesture end.
      *
-     * @param prototype The prototype from which to create the injected events.
-     * @param pointerIdBits The bits of the pointers to send.
      * @param policyFlags The policy flags associated with the event.
      */
-    private void ensureHoverExitSent(MotionEvent prototype, int pointerIdBits, int policyFlags) {
-        final int lastAction = mInjectedPointerTracker.getLastInjectedHoverAction();
-        if (lastAction != MotionEvent.ACTION_HOVER_EXIT) {
-            sendMotionEvent(prototype, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits,
-                    policyFlags);
+    private void sendExitEventsIfNeeded(int policyFlags) {
+        MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
+        if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
+            final int pointerIdBits = event.getPointerIdBits();
+            mAms.touchExplorationGestureEnded();
+            sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
+        }
+    }
+
+    /**
+     * Sends the enter events if needed. Such events are hover enter and touch explore
+     * gesture start.
+     *
+     * @param policyFlags The policy flags associated with the event.
+     */
+    private void sendEnterEventsIfNeeded(int policyFlags) {
+        MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
+        if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
+            final int pointerIdBits = event.getPointerIdBits();
+            mAms.touchExplorationGestureStarted();
+            sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
         }
     }
 
@@ -826,6 +880,36 @@
             event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
         }
 
+        // If the user is long pressing but the long pressing pointer
+        // was not exactly over the accessibility focused item we need
+        // to remap the location of that pointer so the user does not
+        // have to explicitly touch explore something to be able to
+        // long press it, or even worse to avoid the user long pressing
+        // on the wrong item since click and long press behave differently.
+        if (mLongPressingPointerId >= 0) {
+            final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
+            final int pointerCount = event.getPointerCount();
+            PointerProperties[] props = PointerProperties.createArray(pointerCount);
+            PointerCoords[] coords = PointerCoords.createArray(pointerCount);
+            for (int i = 0; i < pointerCount; i++) {
+                event.getPointerProperties(i, props[i]);
+                event.getPointerCoords(i, coords[i]);
+                if (i == remappedIndex) {
+                    coords[i].x -= mLongPressingPointerDeltaX;
+                    coords[i].y -= mLongPressingPointerDeltaY;
+                }
+            }
+            MotionEvent remapped = MotionEvent.obtain(event.getDownTime(),
+                    event.getEventTime(), event.getAction(), event.getPointerCount(),
+                    props, coords, event.getMetaState(), event.getButtonState(),
+                    1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
+                    event.getSource(), event.getFlags());
+            if (event != prototype) {
+                event.recycle();
+            }
+            event = remapped;
+        }
+
         if (DEBUG) {
             Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
                     + Integer.toHexString(policyFlags));
@@ -878,6 +962,172 @@
         }
     }
 
+    private class DoubleTapDetector {
+        private MotionEvent mDownEvent;
+        private MotionEvent mFirstTapEvent;
+
+        public void onMotionEvent(MotionEvent event, int policyFlags) {
+            final int action = event.getActionMasked();
+            switch (action) {
+                case MotionEvent.ACTION_DOWN:
+                case MotionEvent.ACTION_POINTER_DOWN: {
+                    if (mFirstTapEvent != null && !isSamePointerContext(mFirstTapEvent, event)) {
+                        clear();
+                    }
+                    mDownEvent = MotionEvent.obtain(event);
+                } break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_POINTER_UP: {
+                    if (mDownEvent == null) {
+                        return;
+                    }
+                    if (!isSamePointerContext(mDownEvent, event)) {
+                        clear();
+                        return;
+                    }
+                    if (isTap(mDownEvent, event)) {
+                        if (mFirstTapEvent == null || isTimedOut(mFirstTapEvent, event,
+                                mDoubleTapTimeout)) {
+                            mFirstTapEvent = MotionEvent.obtain(event);
+                            mDownEvent.recycle();
+                            mDownEvent = null;
+                            return;
+                        }
+                        if (isDoubleTap(mFirstTapEvent, event)) {
+                            onDoubleTap(event, policyFlags);
+                            mFirstTapEvent.recycle();
+                            mFirstTapEvent = null;
+                            mDownEvent.recycle();
+                            mDownEvent = null;
+                            return;
+                        }
+                        mFirstTapEvent.recycle();
+                        mFirstTapEvent = null;
+                    } else {
+                        if (mFirstTapEvent != null) {
+                            mFirstTapEvent.recycle();
+                            mFirstTapEvent = null;
+                        }
+                    }
+                    mDownEvent.recycle();
+                    mDownEvent = null;
+                } break;
+            }
+        }
+
+        public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) {
+            // This should never be called when more than two pointers are down.
+            if (secondTapUp.getPointerCount() > 2) {
+                return;
+            }
+
+            // Remove pending event deliveries.
+            mSendHoverEnterDelayed.remove();
+            mSendHoverExitDelayed.remove();
+            mPerformLongPressDelayed.remove();
+
+            // This is a tap so do not send hover events since
+            // this events will result in firing the corresponding
+            // accessibility events confusing the user about what
+            // is actually clicked.
+            sendExitEventsIfNeeded(policyFlags);
+
+            // If the last touched explored location is not within the focused
+            // window we will click at that exact spot, otherwise we find the
+            // accessibility focus and if the tap is within its bounds we click
+            // there, otherwise we pick the middle of the focus rectangle.
+            MotionEvent lastEvent = mInjectedPointerTracker.getLastInjectedHoverEvent();
+            if (lastEvent == null) {
+                return;
+            }
+
+            final int exploreLocationX = (int) lastEvent.getX(lastEvent.getActionIndex());
+            final int exploreLocationY = (int) lastEvent.getY(lastEvent.getActionIndex());
+
+            Rect bounds = mTempRect;
+            boolean useLastHoverLocation = false;
+
+            final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex());
+            final int pointerIndex = secondTapUp.findPointerIndex(pointerId);
+            if (mAms.getAccessibilityFocusBounds(exploreLocationX, exploreLocationY, bounds)) {
+                // If the user's last touch explored location is not
+                // within the accessibility focus bounds we use the center
+                // of the accessibility focused rectangle.
+                if (!bounds.contains((int) secondTapUp.getX(pointerIndex),
+                        (int) secondTapUp.getY(pointerIndex))) {
+                    useLastHoverLocation = true;
+                }
+            }
+
+            // Do the click.
+            PointerProperties[] properties = new PointerProperties[1];
+            properties[0] = new PointerProperties();
+            secondTapUp.getPointerProperties(pointerIndex, properties[0]);
+            PointerCoords[] coords = new PointerCoords[1];
+            coords[0] = new PointerCoords();
+            coords[0].x = (useLastHoverLocation) ? bounds.centerX() : exploreLocationX;
+            coords[0].y = (useLastHoverLocation) ? bounds.centerY() : exploreLocationY;
+            MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(),
+                    secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
+                    coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0,
+                    secondTapUp.getSource(), secondTapUp.getFlags());
+            sendActionDownAndUp(event, policyFlags);
+            event.recycle();
+        }
+
+        public void clear() {
+            if (mDownEvent != null) {
+                mDownEvent.recycle();
+                mDownEvent = null;
+            }
+            if (mFirstTapEvent != null) {
+                mFirstTapEvent.recycle();
+                mFirstTapEvent = null;
+            }
+        }
+
+        public boolean isTap(MotionEvent down, MotionEvent up) {
+            return eventsWithinTimeoutAndDistance(down, up, mTapTimeout, mTouchSlop);
+        }
+
+        private boolean isDoubleTap(MotionEvent firstUp, MotionEvent secondUp) {
+            return eventsWithinTimeoutAndDistance(firstUp, secondUp, mDoubleTapTimeout,
+                    mDoubleTapSlop);
+        }
+
+        private boolean eventsWithinTimeoutAndDistance(MotionEvent first, MotionEvent second,
+                int timeout, int distance) {
+            if (isTimedOut(first, second, timeout)) {
+                return false;
+            }
+            final int downPtrIndex = first.getActionIndex();
+            final int upPtrIndex = second.getActionIndex();
+            final float deltaX = second.getX(upPtrIndex) - first.getX(downPtrIndex);
+            final float deltaY = second.getY(upPtrIndex) - first.getY(downPtrIndex);
+            final double deltaMove = Math.hypot(deltaX, deltaY);
+            if (deltaMove >= distance) {
+                return false;
+            }
+            return true;
+        }
+
+        private boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) {
+            final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime();
+            return (deltaTime >= timeout);
+        }
+
+        private boolean isSamePointerContext(MotionEvent first, MotionEvent second) {
+            return (first.getPointerIdBits() == second.getPointerIdBits()
+                    && first.getPointerId(first.getActionIndex())
+                            == second.getPointerId(second.getActionIndex()));
+        }
+
+        public boolean firstTapDetected() {
+            return mFirstTapEvent != null
+                && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout;
+        }
+    }
+
     /**
      * Determines whether a two pointer gesture is a dragging one.
      *
@@ -940,30 +1190,6 @@
         return true;
     }
 
-   /**
-    * Sends an event announcing the start/end of a touch exploration gesture.
-    *
-    * @param eventType The type of the event to send.
-    */
-    private void sendAccessibilityEvent(int eventType) {
-        AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
-        mAccessibilityManager.sendAccessibilityEvent(event);
-    }
-
-    /**
-     * Clears the internal state of this explorer.
-     */
-    public void clear() {
-        mSendHoverDelayed.remove();
-        mPerformLongPressDelayed.remove();
-        mReceivedPointerTracker.clear();
-        mInjectedPointerTracker.clear();
-        mLastTouchExploreEvent = null;
-        mCurrentState = STATE_TOUCH_EXPLORING;
-        mTouchExploreGestureInProgress = false;
-        mDraggingPointerId = INVALID_POINTER_ID;
-    }
-
     /**
      * Gets the symbolic name of a state.
      *
@@ -1002,10 +1228,10 @@
         private MotionEvent mEvent;
         private int mPolicyFlags;
 
-        public void post(MotionEvent prototype, int policyFlags, long delay) {
+        public void post(MotionEvent prototype, int policyFlags) {
             mEvent = MotionEvent.obtain(prototype);
             mPolicyFlags = policyFlags;
-            mHandler.postDelayed(this, delay);
+            mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout());
         }
 
         public void remove() {
@@ -1021,16 +1247,29 @@
 
         @Override
         public void run() {
-            mCurrentState = STATE_DELEGATING;
-            // Make sure the scheduled hover exit is delivered.
-            mSendHoverDelayed.remove();
+            final int pointerIndex = mEvent.getActionIndex();
+            final int eventX = (int) mEvent.getX(pointerIndex);
+            final int eventY = (int) mEvent.getY(pointerIndex);
+            Rect bounds = mTempRect;
+            if (mAms.getAccessibilityFocusBounds(eventX, eventY, bounds)
+                    && !bounds.contains(eventX, eventY)) {
+                mLongPressingPointerId = mEvent.getPointerId(pointerIndex);
+                mLongPressingPointerDeltaX = eventX - bounds.centerX();
+                mLongPressingPointerDeltaY = eventY - bounds.centerY();
+            } else {
+                mLongPressingPointerId = -1;
+                mLongPressingPointerDeltaX = 0;
+                mLongPressingPointerDeltaY = 0;
+            }
+            // We are sending events so send exit and gesture
+            // end since we transition to another state.
             final int pointerId = mReceivedPointerTracker.getPrimaryActivePointerId();
             final int pointerIdBits = (1 << pointerId);
-            ensureHoverExitSent(mEvent, pointerIdBits, mPolicyFlags);
+            mAms.touchExplorationGestureEnded();
+            sendExitEventsIfNeeded(mPolicyFlags);
 
+            mCurrentState = STATE_DELEGATING;
             sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
-            mTouchExploreGestureInProgress = false;
-            mLastTouchExploreEvent = null;
             clear();
         }
 
@@ -1047,20 +1286,41 @@
     /**
      * Class for delayed sending of hover events.
      */
-    private final class SendHoverDelayed implements Runnable {
-        private MotionEvent mEvent;
-        private int mAction;
+    class SendHoverDelayed implements Runnable {
+        private final String LOG_TAG_SEND_HOVER_DELAYED = SendHoverDelayed.class.getName();
+
+        private final int mHoverAction;
+        private final boolean mGestureStarted;
+
+        private MotionEvent mPrototype;
         private int mPointerIdBits;
         private int mPolicyFlags;
 
-        public void post(MotionEvent prototype, int action, int pointerIdBits, int policyFlags,
-                long delay) {
+        public SendHoverDelayed(int hoverAction, boolean gestureStarted) {
+            mHoverAction = hoverAction;
+            mGestureStarted = gestureStarted;
+        }
+
+        public void post(MotionEvent prototype, int pointerIdBits, int policyFlags) {
             remove();
-            mEvent = MotionEvent.obtain(prototype);
-            mAction = action;
+            mPrototype = MotionEvent.obtain(prototype);
             mPointerIdBits = pointerIdBits;
             mPolicyFlags = policyFlags;
-            mHandler.postDelayed(this, delay);
+            mHandler.postDelayed(this, mTapTimeout);
+        }
+
+        public float getX() {
+            if (isPending()) {
+                return mPrototype.getX();
+            }
+            return 0;
+        }
+
+        public float getY() {
+            if (isPending()) {
+                return mPrototype.getY();
+            }
+            return 0;
         }
 
         public void remove() {
@@ -1068,23 +1328,22 @@
             clear();
         }
 
-        private boolean isPenidng() {
-            return (mEvent != null);
+        private boolean isPending() {
+            return (mPrototype != null);
         }
 
         private void clear() {
-            if (!isPenidng()) {
+            if (!isPending()) {
                 return;
             }
-            mEvent.recycle();
-            mEvent = null;
-            mAction = 0;
+            mPrototype.recycle();
+            mPrototype = null;
             mPointerIdBits = -1;
             mPolicyFlags = 0;
         }
 
         public void forceSendAndRemove() {
-            if (isPenidng()) {
+            if (isPending()) {
                 run();
                 remove();
             }
@@ -1092,16 +1351,17 @@
 
         public void run() {
             if (DEBUG) {
-                if (mAction == MotionEvent.ACTION_HOVER_ENTER) {
-                    Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER);
-                } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) {
-                    Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE");
-                } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) {
-                    Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT");
-                }
+                Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event: "
+                        + MotionEvent.actionToString(mHoverAction));
+                Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ?
+                        "touchExplorationGestureStarted" : "touchExplorationGestureEnded");
             }
-
-            sendMotionEvent(mEvent, mAction, mPointerIdBits, mPolicyFlags);
+            if (mGestureStarted) {
+                mAms.touchExplorationGestureStarted();
+            } else {
+                mAms.touchExplorationGestureEnded();
+            }
+            sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags);
             clear();
         }
     }
@@ -1120,8 +1380,8 @@
         // The time of the last injected down.
         private long mLastInjectedDownEventTime;
 
-        // The action of the last injected hover event.
-        private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT;
+        // The last injected hover event.
+        private MotionEvent mLastInjectedHoverEvent;
 
         /**
          * Processes an injected {@link MotionEvent} event.
@@ -1150,11 +1410,14 @@
                 case MotionEvent.ACTION_HOVER_ENTER:
                 case MotionEvent.ACTION_HOVER_MOVE:
                 case MotionEvent.ACTION_HOVER_EXIT: {
-                    mLastInjectedHoverEventAction = event.getActionMasked();
+                    if (mLastInjectedHoverEvent != null) {
+                        mLastInjectedHoverEvent.recycle();
+                    }
+                    mLastInjectedHoverEvent = MotionEvent.obtain(event);
                 } break;
             }
             if (DEBUG) {
-                Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer: " + toString());
+                Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString());
             }
         }
 
@@ -1198,10 +1461,10 @@
         }
 
         /**
-         * @return The action of the last injected hover event.
+         * @return The the last injected hover event.
          */
-        public int getLastInjectedHoverAction() {
-            return mLastInjectedHoverEventAction;
+        public MotionEvent getLastInjectedHoverEvent() {
+            return mLastInjectedHoverEvent;
         }
 
         @Override
@@ -1260,6 +1523,8 @@
         private float mLastReceivedUpPointerDownX;
         private float mLastReceivedUpPointerDownY;
 
+        private MotionEvent mLastReceivedEvent;
+
         /**
          * Creates a new instance.
          *
@@ -1294,6 +1559,11 @@
          * @param event The event to process.
          */
         public void onMotionEvent(MotionEvent event) {
+            if (mLastReceivedEvent != null) {
+                mLastReceivedEvent.recycle();
+            }
+            mLastReceivedEvent = MotionEvent.obtain(event);
+
             final int action = event.getActionMasked();
             switch (action) {
                 case MotionEvent.ACTION_DOWN: {
@@ -1318,6 +1588,13 @@
         }
 
         /**
+         * @return The last received event.
+         */
+        public MotionEvent getLastReceivedEvent() {
+            return mLastReceivedEvent;
+        }
+
+        /**
          * @return The number of received pointers that are down.
          */
         public int getReceivedPointerDownCount() {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 885ec96..fbf9256 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -6567,6 +6567,16 @@
         sendScreenStatusToClients();
     }
 
+    public IBinder getFocusedWindowClientToken() {
+        synchronized (mWindowMap) {
+            WindowState windowState = getFocusedWindowLocked();
+            if (windowState != null) {
+                return windowState.mClient.asBinder();
+            }
+            return null;
+        }
+    }
+
     private WindowState getFocusedWindow() {
         synchronized (mWindowMap) {
             return getFocusedWindowLocked();