Merge "NumberPicker should adjust min and max when displayed values are set." into jb-mr1-dev
diff --git a/Android.mk b/Android.mk
index be98487..d38150f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -429,6 +429,7 @@
     -since ./frameworks/base/api/14.txt 14 \
     -since ./frameworks/base/api/15.txt 15 \
     -since ./frameworks/base/api/16.txt 16 \
+    -since ./frameworks/base/api/17.txt 17 \
 		-werror -hide 113 \
 		-overview $(LOCAL_PATH)/core/java/overview.html
 
@@ -539,7 +540,7 @@
 
 ## SDK version identifiers used in the published docs
   # major[.minor] version for current SDK. (full releases only)
-framework_docs_SDK_VERSION:=4.1
+framework_docs_SDK_VERSION:=4.2
   # release version (ie "Release x")  (full releases only)
 framework_docs_SDK_REL_ID:=1
 
diff --git a/api/current.txt b/api/current.txt
index e8f911b..ab45790 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11750,6 +11750,7 @@
     method public void removeUserRoute(android.media.MediaRouter.UserRouteInfo);
     method public void selectRoute(int, android.media.MediaRouter.RouteInfo);
     field public static final int ROUTE_TYPE_LIVE_AUDIO = 1; // 0x1
+    field public static final int ROUTE_TYPE_LIVE_VIDEO = 2; // 0x2
     field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
   }
 
@@ -11798,6 +11799,7 @@
     method public int getVolume();
     method public int getVolumeHandling();
     method public int getVolumeMax();
+    method public boolean isEnabled();
     method public void requestSetVolume(int);
     method public void requestUpdateVolume(int);
     method public void setTag(java.lang.Object);
@@ -25040,7 +25042,6 @@
     method public boolean isInEditMode();
     method public boolean isInTouchMode();
     method public boolean isLayoutRequested();
-    method public boolean isLayoutRtl();
     method public boolean isLongClickable();
     method public boolean isOpaque();
     method protected boolean isPaddingOffsetRequired();
@@ -25678,7 +25679,6 @@
     method public int getLayoutDirection();
     method public int getMarginEnd();
     method public int getMarginStart();
-    method protected boolean isLayoutRtl();
     method public boolean isMarginRelative();
     method public void setLayoutDirection(int);
     method public void setMarginEnd(int);
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index cfc8bbd..a9ccef0 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -221,21 +221,28 @@
     void updateRouteCount() {
         final int N = mRouter.getRouteCount();
         int count = 0;
+        boolean hasVideoRoutes = false;
         for (int i = 0; i < N; i++) {
             final RouteInfo route = mRouter.getRouteAt(i);
-            if ((route.getSupportedTypes() & mRouteTypes) != 0) {
+            final int routeTypes = route.getSupportedTypes();
+            if ((routeTypes & mRouteTypes) != 0) {
                 if (route instanceof RouteGroup) {
                     count += ((RouteGroup) route).getRouteCount();
                 } else {
                     count++;
                 }
+                if ((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0) {
+                    hasVideoRoutes = true;
+                }
             }
         }
 
         setEnabled(count != 0);
 
-        // Only allow toggling if we have more than just user routes
-        mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0;
+        // Only allow toggling if we have more than just user routes.
+        // Don't toggle if we support video routes, we may have to let the dialog scan.
+        mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 &&
+                !hasVideoRoutes;
     }
 
     @Override
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index b3ab385..5b49ba3 100755
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -7659,6 +7659,54 @@
         public static final int MODE_LARGE = 3;
 
         /**
+         * Constructs the QuickContacts intent with a view's rect.
+         * @hide
+         */
+        public static Intent composeQuickContactsIntent(Context context, View target, Uri lookupUri,
+                int mode, String[] excludeMimes) {
+            // Find location and bounds of target view, adjusting based on the
+            // assumed local density.
+            final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
+            final int[] pos = new int[2];
+            target.getLocationOnScreen(pos);
+
+            final Rect rect = new Rect();
+            rect.left = (int) (pos[0] * appScale + 0.5f);
+            rect.top = (int) (pos[1] * appScale + 0.5f);
+            rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f);
+            rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f);
+
+            return composeQuickContactsIntent(context, rect, lookupUri, mode, excludeMimes);
+        }
+
+        /**
+         * Constructs the QuickContacts intent.
+         * @hide
+         */
+        public static Intent composeQuickContactsIntent(Context context, Rect target,
+                Uri lookupUri, int mode, String[] excludeMimes) {
+            // When launching from an Activiy, we don't want to start a new task, but otherwise
+            // we *must* start a new task.  (Otherwise startActivity() would crash.)
+            Context actualContext = context;
+            while ((actualContext instanceof ContextWrapper)
+                    && !(actualContext instanceof Activity)) {
+                actualContext = ((ContextWrapper) actualContext).getBaseContext();
+            }
+            final int intentFlags = (actualContext instanceof Activity)
+                    ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
+                    : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
+
+            // Launch pivot dialog through intent for now
+            final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags);
+
+            intent.setData(lookupUri);
+            intent.setSourceBounds(target);
+            intent.putExtra(EXTRA_MODE, mode);
+            intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes);
+            return intent;
+        }
+
+        /**
          * Trigger a dialog that lists the various methods of interacting with
          * the requested {@link Contacts} entry. This may be based on available
          * {@link ContactsContract.Data} rows under that contact, and may also
@@ -7683,20 +7731,10 @@
          */
         public static void showQuickContact(Context context, View target, Uri lookupUri, int mode,
                 String[] excludeMimes) {
-            // Find location and bounds of target view, adjusting based on the
-            // assumed local density.
-            final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
-            final int[] pos = new int[2];
-            target.getLocationOnScreen(pos);
-
-            final Rect rect = new Rect();
-            rect.left = (int) (pos[0] * appScale + 0.5f);
-            rect.top = (int) (pos[1] * appScale + 0.5f);
-            rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f);
-            rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f);
-
             // Trigger with obtained rectangle
-            showQuickContact(context, rect, lookupUri, mode, excludeMimes);
+            Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
+                    excludeMimes);
+            context.startActivity(intent);
         }
 
         /**
@@ -7727,25 +7765,9 @@
          */
         public static void showQuickContact(Context context, Rect target, Uri lookupUri, int mode,
                 String[] excludeMimes) {
-            // When launching from an Activiy, we don't want to start a new task, but otherwise
-            // we *must* start a new task.  (Otherwise startActivity() would crash.)
-            Context actualContext = context;
-            while ((actualContext instanceof ContextWrapper)
-                    && !(actualContext instanceof Activity)) {
-                actualContext = ((ContextWrapper) actualContext).getBaseContext();
-            }
-            final int intentFlags = (actualContext instanceof Activity)
-                    ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
-                    : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
-
-            // Launch pivot dialog through intent for now
-            final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags);
-
-            intent.setData(lookupUri);
-            intent.setSourceBounds(target);
-            intent.putExtra(EXTRA_MODE, mode);
-            intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes);
-            context.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+            Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
+                    excludeMimes);
+            context.startActivity(intent);
         }
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 550713d..056103b0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3141,6 +3141,12 @@
         public static final String DEVICE_PROVISIONED = Global.DEVICE_PROVISIONED;
 
         /**
+         * Whether the current user has been set up via setup wizard (0 = false, 1 = true)
+         * @hide
+         */
+        public static final String USER_SETUP_COMPLETE = "user_setup_complete";
+
+        /**
          * List of input methods that are currently enabled.  This is a string
          * containing the IDs of all enabled input methods, each ID separated
          * by ':'.
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index b0a2711..4873860 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.os.SystemClock;
 import android.util.FloatMath;
+import android.util.Log;
 
 import java.util.Arrays;
 
@@ -223,10 +224,14 @@
      * @param id pointer id to clear
      * @see #addTouchHistory(MotionEvent)
      */
-    private void removeTouchHistoryForId(int id) {
+    private boolean removeTouchHistoryForId(int id) {
+        if (id >= mTouchHistoryLastAccepted.length) {
+            return false;
+        }
         mTouchHistoryLastAccepted[id] = Float.NaN;
         mTouchHistoryDirection[id] = 0;
         mTouchHistoryLastAcceptedTime[id] = 0;
+        return true;
     }
 
     /**
@@ -236,6 +241,11 @@
      * @see #addTouchHistory(MotionEvent)
      */
     private float getAdjustedTouchHistory(int id) {
+        if (id >= mTouchHistoryLastAccepted.length) {
+            Log.e(TAG, "Error retrieving adjusted touch history for id=" + id +
+                    " - incomplete event stream?");
+            return 0;
+        }
         return mTouchHistoryLastAccepted[id];
     }
 
@@ -244,6 +254,10 @@
      * @see #addTouchHistory(MotionEvent)
      */
     private void clearTouchHistory() {
+        if (mTouchHistoryLastAccepted == null) {
+            // All three arrays will be null if this is the case; nothing to do.
+            return;
+        }
         Arrays.fill(mTouchHistoryLastAccepted, Float.NaN);
         Arrays.fill(mTouchHistoryDirection, 0);
         Arrays.fill(mTouchHistoryLastAcceptedTime, 0);
@@ -333,7 +347,11 @@
         final float focusY = sumY / div;
 
         if (pointerUp) {
-            removeTouchHistoryForId(event.getPointerId(event.getActionIndex()));
+            final int id = event.getPointerId(event.getActionIndex());
+            if (!removeTouchHistoryForId(id)) {
+                Log.e(TAG, "Got ACTION_POINTER_UP for previously unknown id=" + id +
+                        " - incomplete event stream?");
+            }
         } else {
             addTouchHistory(event);
         }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 111f959..f9ff865 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5887,6 +5887,8 @@
      * layout attribute and/or the inherited value from the parent
      *
      * @return true if the layout is right-to-left.
+     *
+     * @hide
      */
     @ViewDebug.ExportedProperty(category = "layout")
     public boolean isLayoutRtl() {
@@ -11628,9 +11630,11 @@
      * Resolve and cache the layout direction. LTR is set initially. This is implicitly supposing
      * that the parent directionality can and will be resolved before its children.
      *
+     * @return true if resolution has been done, false otherwise.
+     *
      * @hide
      */
-    public void resolveLayoutDirection() {
+    public boolean resolveLayoutDirection() {
         // Clear any previous layout direction resolution
         mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK;
 
@@ -11641,15 +11645,13 @@
                 case LAYOUT_DIRECTION_INHERIT:
                     // We cannot resolve yet. LTR is by default and let the resolution happen again
                     // later to get the correct resolved value
-                    if (!canResolveLayoutDirection()) return;
+                    if (!canResolveLayoutDirection()) return false;
 
-                    ViewGroup viewGroup = ((ViewGroup) mParent);
+                    View parent = ((View) mParent);
+                    // Parent has not yet resolved, LTR is still the default
+                    if (!parent.isLayoutDirectionResolved()) return false;
 
-                    // We cannot resolve yet on the parent too. LTR is by default and let the
-                    // resolution happen again later
-                    if (!viewGroup.canResolveLayoutDirection()) return;
-
-                    if (viewGroup.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+                    if (parent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
                         mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
                     }
                     break;
@@ -11669,6 +11671,7 @@
 
         // Set to resolved
         mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+        return true;
     }
 
     /**
@@ -11679,10 +11682,10 @@
      * @hide
      */
     public boolean canResolveLayoutDirection() {
-        switch ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >>
-                PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) {
+        switch (getRawLayoutDirection()) {
             case LAYOUT_DIRECTION_INHERIT:
-                return (mParent != null) && (mParent instanceof ViewGroup);
+                return (mParent != null) && (mParent instanceof ViewGroup) &&
+                       ((ViewGroup) mParent).canResolveLayoutDirection();
             default:
                 return true;
         }
@@ -16640,9 +16643,11 @@
     /**
      * Resolve the text direction.
      *
+     * @return true if resolution has been done, false otherwise.
+     *
      * @hide
      */
-    public void resolveTextDirection() {
+    public boolean resolveTextDirection() {
         // Reset any previous text direction resolution
         mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK);
 
@@ -16651,29 +16656,35 @@
             final int textDirection = getRawTextDirection();
             switch(textDirection) {
                 case TEXT_DIRECTION_INHERIT:
-                    if (canResolveTextDirection()) {
-                        ViewGroup viewGroup = ((ViewGroup) mParent);
-
-                        // Set current resolved direction to the same value as the parent's one
-                        final int parentResolvedDirection = viewGroup.getTextDirection();
-                        switch (parentResolvedDirection) {
-                            case TEXT_DIRECTION_FIRST_STRONG:
-                            case TEXT_DIRECTION_ANY_RTL:
-                            case TEXT_DIRECTION_LTR:
-                            case TEXT_DIRECTION_RTL:
-                            case TEXT_DIRECTION_LOCALE:
-                                mPrivateFlags2 |=
-                                        (parentResolvedDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
-                                break;
-                            default:
-                                // Default resolved direction is "first strong" heuristic
-                                mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
-                        }
-                    } else {
+                    if (!canResolveTextDirection()) {
                         // We cannot do the resolution if there is no parent, so use the default one
                         mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
                         // Resolution will need to happen again later
-                        return;
+                        return false;
+                    }
+
+                    View parent = ((View) mParent);
+                    // Parent has not yet resolved, so we still return the default
+                    if (!parent.isTextDirectionResolved()) {
+                        mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+                        // Resolution will need to happen again later
+                        return false;
+                    }
+
+                    // Set current resolved direction to the same value as the parent's one
+                    final int parentResolvedDirection = parent.getTextDirection();
+                    switch (parentResolvedDirection) {
+                        case TEXT_DIRECTION_FIRST_STRONG:
+                        case TEXT_DIRECTION_ANY_RTL:
+                        case TEXT_DIRECTION_LTR:
+                        case TEXT_DIRECTION_RTL:
+                        case TEXT_DIRECTION_LOCALE:
+                            mPrivateFlags2 |=
+                                    (parentResolvedDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
+                            break;
+                        default:
+                            // Default resolved direction is "first strong" heuristic
+                            mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
                     }
                     break;
                 case TEXT_DIRECTION_FIRST_STRONG:
@@ -16695,6 +16706,7 @@
 
         // Set to resolved
         mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED;
+        return true;
     }
 
     /**
@@ -16705,7 +16717,8 @@
     private boolean canResolveTextDirection() {
         switch (getRawTextDirection()) {
             case TEXT_DIRECTION_INHERIT:
-                return (mParent != null) && (mParent instanceof ViewGroup);
+                return (mParent != null) && (mParent instanceof View) &&
+                       ((View) mParent).canResolveTextDirection();
             default:
                 return true;
         }
@@ -16835,9 +16848,11 @@
     /**
      * Resolve the text alignment.
      *
+     * @return true if resolution has been done, false otherwise.
+     *
      * @hide
      */
-    public void resolveTextAlignment() {
+    public boolean resolveTextAlignment() {
         // Reset any previous text alignment resolution
         mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK);
 
@@ -16847,32 +16862,37 @@
             switch (textAlignment) {
                 case TEXT_ALIGNMENT_INHERIT:
                     // Check if we can resolve the text alignment
-                    if (canResolveTextAlignment() && mParent instanceof View) {
-                        View view = (View) mParent;
-
-                        final int parentResolvedTextAlignment = view.getTextAlignment();
-                        switch (parentResolvedTextAlignment) {
-                            case TEXT_ALIGNMENT_GRAVITY:
-                            case TEXT_ALIGNMENT_TEXT_START:
-                            case TEXT_ALIGNMENT_TEXT_END:
-                            case TEXT_ALIGNMENT_CENTER:
-                            case TEXT_ALIGNMENT_VIEW_START:
-                            case TEXT_ALIGNMENT_VIEW_END:
-                                // Resolved text alignment is the same as the parent resolved
-                                // text alignment
-                                mPrivateFlags2 |=
-                                        (parentResolvedTextAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
-                                break;
-                            default:
-                                // Use default resolved text alignment
-                                mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
-                        }
-                    }
-                    else {
+                    if (!canResolveTextAlignment()) {
                         // We cannot do the resolution if there is no parent so use the default
                         mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
                         // Resolution will need to happen again later
-                        return;
+                        return false;
+                    }
+                    View parent = (View) mParent;
+
+                    // Parent has not yet resolved, so we still return the default
+                    if (!parent.isTextAlignmentResolved()) {
+                        mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+                        // Resolution will need to happen again later
+                        return false;
+                    }
+
+                    final int parentResolvedTextAlignment = parent.getTextAlignment();
+                    switch (parentResolvedTextAlignment) {
+                        case TEXT_ALIGNMENT_GRAVITY:
+                        case TEXT_ALIGNMENT_TEXT_START:
+                        case TEXT_ALIGNMENT_TEXT_END:
+                        case TEXT_ALIGNMENT_CENTER:
+                        case TEXT_ALIGNMENT_VIEW_START:
+                        case TEXT_ALIGNMENT_VIEW_END:
+                            // Resolved text alignment is the same as the parent resolved
+                            // text alignment
+                            mPrivateFlags2 |=
+                                    (parentResolvedTextAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
+                            break;
+                        default:
+                            // Use default resolved text alignment
+                            mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
                     }
                     break;
                 case TEXT_ALIGNMENT_GRAVITY:
@@ -16895,6 +16915,7 @@
 
         // Set the resolved
         mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+        return true;
     }
 
     /**
@@ -16905,7 +16926,8 @@
     private boolean canResolveTextAlignment() {
         switch (getRawTextAlignment()) {
             case TEXT_DIRECTION_INHERIT:
-                return (mParent != null);
+                return (mParent != null) && (mParent instanceof View) &&
+                       ((View) mParent).canResolveTextAlignment();
             default:
                 return true;
         }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 41890d6..db1c00a 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3382,6 +3382,11 @@
             ai.mKeepScreenOn = lastKeepOn;
         }
 
+        if (child.isLayoutDirectionInherited()) {
+            child.resetResolvedLayoutDirection();
+            child.resolveRtlPropertiesIfNeeded();
+        }
+
         onViewAdded(child);
 
         if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
@@ -5256,48 +5261,54 @@
      * @hide
      */
     @Override
-    public void resolveLayoutDirection() {
-        super.resolveLayoutDirection();
-
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child.isLayoutDirectionInherited()) {
-                child.resolveLayoutDirection();
+    public boolean resolveLayoutDirection() {
+        final boolean result = super.resolveLayoutDirection();
+        if (result) {
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                if (child.isLayoutDirectionInherited()) {
+                    child.resolveLayoutDirection();
+                }
             }
         }
+        return result;
     }
 
     /**
      * @hide
      */
     @Override
-    public void resolveTextDirection() {
-        super.resolveTextDirection();
-
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child.isTextDirectionInherited()) {
-                child.resolveTextDirection();
+    public boolean resolveTextDirection() {
+        final boolean result = super.resolveTextDirection();
+        if (result) {
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                if (child.isTextDirectionInherited()) {
+                    child.resolveTextDirection();
+                }
             }
         }
+        return result;
     }
 
     /**
      * @hide
      */
     @Override
-    public void resolveTextAlignment() {
-        super.resolveTextAlignment();
-
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child.isTextAlignmentInherited()) {
-                child.resolveTextAlignment();
+    public boolean resolveTextAlignment() {
+        final boolean result = super.resolveTextAlignment();
+        if (result) {
+            int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                if (child.isTextAlignmentInherited()) {
+                    child.resolveTextAlignment();
+                }
             }
         }
+        return result;
     }
 
     /**
@@ -5893,7 +5904,10 @@
             }
         }
 
-        protected boolean isLayoutRtl() {
+        /**
+         * @hide
+         */
+        public boolean isLayoutRtl() {
             return (layoutDirection == View.LAYOUT_DIRECTION_RTL);
         }
 
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index f010d7b..386f387 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -25,7 +25,7 @@
 import android.app.MediaRouteButton;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
+import android.hardware.display.DisplayManager;
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteCategory;
 import android.media.MediaRouter.RouteGroup;
@@ -70,6 +70,7 @@
     };
 
     MediaRouter mRouter;
+    DisplayManager mDisplayService;
     private int mRouteTypes;
 
     private LayoutInflater mInflater;
@@ -97,6 +98,7 @@
     public void onAttach(Activity activity) {
         super.onAttach(activity);
         mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        mDisplayService = (DisplayManager) activity.getSystemService(Context.DISPLAY_SERVICE);
     }
 
     @Override
@@ -119,6 +121,15 @@
 
     public void setRouteTypes(int types) {
         mRouteTypes = types;
+        if ((mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0 && mDisplayService == null) {
+            final Context activity = getActivity();
+            if (activity != null) {
+                mDisplayService = (DisplayManager) activity.getSystemService(
+                        Context.DISPLAY_SERVICE);
+            }
+        } else {
+            mDisplayService = null;
+        }
     }
 
     void updateVolume() {
@@ -194,6 +205,9 @@
     @Override
     public void onResume() {
         super.onResume();
+        if (mDisplayService != null) {
+            mDisplayService.scanWifiDisplays();
+        }
     }
 
     private static class ViewHolder {
@@ -253,7 +267,9 @@
                 final RouteCategory cat = mRouter.getCategoryAt(i);
                 routes = cat.getRoutes(mCatRouteList);
 
-                mItems.add(cat);
+                if (!cat.isSystem()) {
+                    mItems.add(cat);
+                }
 
                 if (cat == mCategoryEditingGroups) {
                     addGroupEditingCategoryRoutes(routes);
@@ -370,6 +386,7 @@
         public boolean isEnabled(int position) {
             switch (getItemViewType(position)) {
                 case VIEW_ROUTE:
+                    return ((RouteInfo) mItems.get(position)).isEnabled();
                 case VIEW_GROUPING_ROUTE:
                 case VIEW_GROUPING_DONE:
                     return true;
@@ -434,6 +451,7 @@
             }
 
             convertView.setActivated(position == mSelectedItemPosition);
+            convertView.setEnabled(isEnabled(position));
 
             return convertView;
         }
diff --git a/core/res/res/drawable-hdpi/magnified_region_frame.9.png b/core/res/res/drawable-hdpi/magnified_region_frame.9.png
new file mode 100644
index 0000000..29bdc42
--- /dev/null
+++ b/core/res/res/drawable-hdpi/magnified_region_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/magnified_region_frame.9.png b/core/res/res/drawable-mdpi/magnified_region_frame.9.png
new file mode 100644
index 0000000..a61cbea
--- /dev/null
+++ b/core/res/res/drawable-mdpi/magnified_region_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-nodpi/magnified_region_frame.9.png b/core/res/res/drawable-nodpi/magnified_region_frame.9.png
deleted file mode 100644
index 4cadefb..0000000
--- a/core/res/res/drawable-nodpi/magnified_region_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/magnified_region_frame.9.png b/core/res/res/drawable-xhdpi/magnified_region_frame.9.png
new file mode 100644
index 0000000..424b3d9
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/magnified_region_frame.9.png
Binary files differ
diff --git a/core/res/res/layout/media_route_list_item.xml b/core/res/res/layout/media_route_list_item.xml
index 53d813e..423d544 100644
--- a/core/res/res/layout/media_route_list_item.xml
+++ b/core/res/res/layout/media_route_list_item.xml
@@ -24,7 +24,8 @@
                android:layout_height="56dp"
                android:scaleType="center"
                android:id="@+id/icon"
-               android:visibility="gone" />
+               android:visibility="gone"
+               android:duplicateParentState="true" />
 
     <LinearLayout android:layout_width="0dp"
                   android:layout_height="match_parent"
@@ -32,21 +33,24 @@
                   android:orientation="vertical"
                   android:gravity="start|center_vertical"
                   android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-                  android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+                  android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+                  android:duplicateParentState="true">
 
         <TextView android:id="@android:id/text1"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:singleLine="true"
                   android:ellipsize="marquee"
-                  android:textAppearance="?android:attr/textAppearanceMedium" />
+                  android:textAppearance="?android:attr/textAppearanceMedium"
+                  android:duplicateParentState="true" />
 
         <TextView android:id="@android:id/text2"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
                   android:singleLine="true"
                   android:ellipsize="marquee"
-                  android:textAppearance="?android:attr/textAppearanceSmall" />
+                  android:textAppearance="?android:attr/textAppearanceSmall"
+                  android:duplicateParentState="true" />
     </LinearLayout>
 
     <ImageButton
@@ -56,6 +60,7 @@
         android:background="?android:attr/selectableItemBackground"
         android:src="@drawable/ic_media_group_expand"
         android:scaleType="center"
-        android:visibility="gone" />
+        android:visibility="gone"
+        android:duplicateParentState="true" />
 
 </LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 02aa537..1b56c29 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3768,8 +3768,8 @@
     <!-- Name of the default audio route when an audio dock is connected. [CHAR LIMIT=50] -->
     <string name="default_audio_route_name_dock_speakers">Dock speakers</string>
 
-    <!-- Name of the default audio route when HDMI is connected. [CHAR LIMIT=50] -->
-    <string name="default_audio_route_name_hdmi">HDMI audio</string>
+    <!-- Name of the default media route when HDMI is connected. [CHAR LIMIT=50] -->
+    <string name="default_media_route_name_hdmi">HDMI</string>
 
     <!-- Name of the default audio route category. [CHAR LIMIT=50] -->
     <string name="default_audio_route_category_name">System</string>
@@ -3783,6 +3783,18 @@
     <!-- Content description of a MediaRouteButton for accessibility support -->
     <string name="media_route_button_content_description">Media output</string>
 
+    <!-- Status message for remote routes attempting to scan/determine availability -->
+    <string name="media_route_status_scanning">Scanning...</string>
+
+    <!-- Status message for a remote route attempting to connect -->
+    <string name="media_route_status_connecting">Connecting...</string>
+
+    <!-- Status message for a remote route that is confirmed to be available for connection -->
+    <string name="media_route_status_available">Available</string>
+
+    <!-- Status message for remote routes that are not available for connection right now -->
+    <string name="media_route_status_not_available">Not available</string>
+
     <!-- Display manager service -->
 
     <!-- Name of the built-in display.  [CHAR LIMIT=50] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9a4136b..e16dd33 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -823,9 +823,13 @@
   <java-symbol type="string" name="default_audio_route_name" />
   <java-symbol type="string" name="default_audio_route_name_headphones" />
   <java-symbol type="string" name="default_audio_route_name_dock_speakers" />
-  <java-symbol type="string" name="default_audio_route_name_hdmi" />
+  <java-symbol type="string" name="default_media_route_name_hdmi" />
   <java-symbol type="string" name="default_audio_route_category_name" />
   <java-symbol type="string" name="safe_media_volume_warning" />
+  <java-symbol type="string" name="media_route_status_scanning" />
+  <java-symbol type="string" name="media_route_status_connecting" />
+  <java-symbol type="string" name="media_route_status_available" />
+  <java-symbol type="string" name="media_route_status_not_available" />
 
   <java-symbol type="plurals" name="abbrev_in_num_days" />
   <java-symbol type="plurals" name="abbrev_in_num_hours" />
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index a256079..36c9c70 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -22,12 +22,17 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.WifiDisplay;
+import android.hardware.display.WifiDisplayStatus;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.Display;
+import android.view.DisplayInfo;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -51,6 +56,7 @@
     static class Static {
         final Resources mResources;
         final IAudioService mAudioService;
+        final DisplayManager mDisplayService;
         final Handler mHandler;
         final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
                 new CopyOnWriteArrayList<CallbackInfo>();
@@ -60,18 +66,20 @@
 
         final RouteCategory mSystemCategory;
 
-        final AudioRoutesInfo mCurRoutesInfo = new AudioRoutesInfo();
+        final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
 
-        RouteInfo mDefaultAudio;
+        RouteInfo mDefaultAudioVideo;
         RouteInfo mBluetoothA2dpRoute;
 
         RouteInfo mSelectedRoute;
 
-        final IAudioRoutesObserver.Stub mRoutesObserver = new IAudioRoutesObserver.Stub() {
+        WifiDisplayStatus mLastKnownWifiDisplayStatus;
+
+        final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
             public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
                 mHandler.post(new Runnable() {
                     @Override public void run() {
-                        updateRoutes(newRoutes);
+                        updateAudioRoutes(newRoutes);
                     }
                 });
             }
@@ -84,34 +92,42 @@
             IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
             mAudioService = IAudioService.Stub.asInterface(b);
 
+            mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
+
             mSystemCategory = new RouteCategory(
                     com.android.internal.R.string.default_audio_route_category_name,
-                    ROUTE_TYPE_LIVE_AUDIO, false);
+                    ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
+            mSystemCategory.mIsSystem = true;
         }
 
         // Called after sStatic is initialized
         void startMonitoringRoutes(Context appContext) {
-            mDefaultAudio = new RouteInfo(mSystemCategory);
-            mDefaultAudio.mNameResId = com.android.internal.R.string.default_audio_route_name;
-            mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
-            addRoute(mDefaultAudio);
+            mDefaultAudioVideo = new RouteInfo(mSystemCategory);
+            mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
+            mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
+            addRoute(mDefaultAudioVideo);
 
             appContext.registerReceiver(new VolumeChangeReceiver(),
                     new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
 
-            AudioRoutesInfo newRoutes = null;
+            AudioRoutesInfo newAudioRoutes = null;
             try {
-                newRoutes = mAudioService.startWatchingRoutes(mRoutesObserver);
+                newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
             } catch (RemoteException e) {
             }
-            if (newRoutes != null) {
-                updateRoutes(newRoutes);
+            if (newAudioRoutes != null) {
+                updateAudioRoutes(newAudioRoutes);
             }
+
+            updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
+
+            appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),
+                    new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
         }
 
-        void updateRoutes(AudioRoutesInfo newRoutes) {
-            if (newRoutes.mMainType != mCurRoutesInfo.mMainType) {
-                mCurRoutesInfo.mMainType = newRoutes.mMainType;
+        void updateAudioRoutes(AudioRoutesInfo newRoutes) {
+            if (newRoutes.mMainType != mCurAudioRoutesInfo.mMainType) {
+                mCurAudioRoutesInfo.mMainType = newRoutes.mMainType;
                 int name;
                 if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
                         || (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
@@ -119,12 +135,12 @@
                 } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
                     name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
                 } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
-                    name = com.android.internal.R.string.default_audio_route_name_hdmi;
+                    name = com.android.internal.R.string.default_media_route_name_hdmi;
                 } else {
                     name = com.android.internal.R.string.default_audio_route_name;
                 }
-                sStatic.mDefaultAudio.mNameResId = name;
-                dispatchRouteChanged(sStatic.mDefaultAudio);
+                sStatic.mDefaultAudioVideo.mNameResId = name;
+                dispatchRouteChanged(sStatic.mDefaultAudioVideo);
             }
 
             boolean a2dpEnabled;
@@ -135,17 +151,17 @@
                 a2dpEnabled = false;
             }
 
-            if (!TextUtils.equals(newRoutes.mBluetoothName, mCurRoutesInfo.mBluetoothName)) {
-                mCurRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
-                if (mCurRoutesInfo.mBluetoothName != null) {
+            if (!TextUtils.equals(newRoutes.mBluetoothName, mCurAudioRoutesInfo.mBluetoothName)) {
+                mCurAudioRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
+                if (mCurAudioRoutesInfo.mBluetoothName != null) {
                     if (sStatic.mBluetoothA2dpRoute == null) {
                         final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
-                        info.mName = mCurRoutesInfo.mBluetoothName;
+                        info.mName = mCurAudioRoutesInfo.mBluetoothName;
                         info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
                         sStatic.mBluetoothA2dpRoute = info;
                         addRoute(sStatic.mBluetoothA2dpRoute);
                     } else {
-                        sStatic.mBluetoothA2dpRoute.mName = mCurRoutesInfo.mBluetoothName;
+                        sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.mBluetoothName;
                         dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
                     }
                 } else if (sStatic.mBluetoothA2dpRoute != null) {
@@ -155,11 +171,11 @@
             }
 
             if (mBluetoothA2dpRoute != null) {
-                if (mCurRoutesInfo.mMainType != AudioRoutesInfo.MAIN_SPEAKER &&
+                if (mCurAudioRoutesInfo.mMainType != AudioRoutesInfo.MAIN_SPEAKER &&
                         mSelectedRoute == mBluetoothA2dpRoute) {
-                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudio);
-                } else if (mCurRoutesInfo.mMainType == AudioRoutesInfo.MAIN_SPEAKER &&
-                        mSelectedRoute == mDefaultAudio && a2dpEnabled) {
+                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
+                } else if (mCurAudioRoutesInfo.mMainType == AudioRoutesInfo.MAIN_SPEAKER &&
+                        mSelectedRoute == mDefaultAudioVideo && a2dpEnabled) {
                     selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
                 }
             }
@@ -181,6 +197,20 @@
     public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
 
     /**
+     * Route type flag for live video.
+     *
+     * <p>A device that supports live video routing will allow a mirrored version
+     * of the device's primary display or a customized
+     * {@link android.app.Presentation Presentation} to be routed to supported destinations.</p>
+     *
+     * <p>Once initiated, display mirroring is transparent to the application.
+     * While remote routing is active the application may use a
+     * {@link android.app.Presentation Presentation} to replace the mirrored view
+     * on the external display with different content.</p>
+     */
+    public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
+
+    /**
      * Route type flag for application-specific usage.
      *
      * <p>Unlike other media route types, user routes are managed by the application.
@@ -219,7 +249,7 @@
      * @hide for use by framework routing UI
      */
     public RouteInfo getSystemAudioRoute() {
-        return sStatic.mDefaultAudio;
+        return sStatic.mDefaultAudioVideo;
     }
 
     /**
@@ -296,7 +326,8 @@
     }
 
     static void selectRouteStatic(int types, RouteInfo route) {
-        if (sStatic.mSelectedRoute == route) return;
+        final RouteInfo oldRoute = sStatic.mSelectedRoute;
+        if (oldRoute == route) return;
         if ((route.getSupportedTypes() & types) == 0) {
             Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
                     typesToString(route.getSupportedTypes()) + " into route types " +
@@ -306,7 +337,7 @@
 
         final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
         if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
-                (route == btRoute || route == sStatic.mDefaultAudio)) {
+                (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
             try {
                 sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
             } catch (RemoteException e) {
@@ -314,10 +345,21 @@
             }
         }
 
-        if (sStatic.mSelectedRoute != null) {
+        final WifiDisplay activeDisplay =
+                sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay();
+        final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null;
+        final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null;
+        if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) {
+            if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) {
+                sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress);
+            } else if (activeDisplay != null && !newRouteHasAddress) {
+                sStatic.mDisplayService.disconnectWifiDisplay();
+            }
+        }
+
+        if (oldRoute != null) {
             // TODO filter types properly
-            dispatchRouteUnselected(types & sStatic.mSelectedRoute.getSupportedTypes(),
-                    sStatic.mSelectedRoute);
+            dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
         }
         sStatic.mSelectedRoute = route;
         if (route != null) {
@@ -327,6 +369,22 @@
     }
 
     /**
+     * Compare the device address of a display and a route.
+     * Nulls/no device address will match another null/no address.
+     */
+    static boolean matchesDeviceAddress(WifiDisplay display, RouteInfo info) {
+        final boolean routeHasAddress = info != null && info.mDeviceAddress != null;
+        if (display == null && !routeHasAddress) {
+            return true;
+        }
+
+        if (display != null && routeHasAddress) {
+            return display.getDeviceAddress().equals(info.mDeviceAddress);
+        }
+        return false;
+    }
+
+    /**
      * Add an app-specified route for media to the MediaRouter.
      * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
      *
@@ -419,7 +477,7 @@
             if (info == sStatic.mSelectedRoute) {
                 // Removing the currently selected route? Select the default before we remove it.
                 // TODO: Be smarter about the route types here; this selects for all valid.
-                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio);
+                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudioVideo);
             }
             if (!found) {
                 sStatic.mCategories.remove(removingCat);
@@ -444,7 +502,8 @@
             if (info == sStatic.mSelectedRoute) {
                 // Removing the currently selected route? Select the default before we remove it.
                 // TODO: Be smarter about the route types here; this selects for all valid.
-                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio);
+                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_USER,
+                        sStatic.mDefaultAudioVideo);
             }
             if (!found) {
                 sStatic.mCategories.remove(removingCat);
@@ -611,20 +670,151 @@
         if (selectedRoute == null) return;
 
         if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
-                selectedRoute == sStatic.mDefaultAudio) {
+                selectedRoute == sStatic.mDefaultAudioVideo) {
             dispatchRouteVolumeChanged(selectedRoute);
         } else if (sStatic.mBluetoothA2dpRoute != null) {
             try {
                 dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
-                        sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudio);
+                        sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
             }
         } else {
-            dispatchRouteVolumeChanged(sStatic.mDefaultAudio);
+            dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
         }
     }
 
+    static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) {
+        final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus;
+
+        // TODO Naive implementation. Make this smarter later.
+        boolean needScan = false;
+        WifiDisplay[] oldDisplays = oldStatus != null ?
+                oldStatus.getRememberedDisplays() : new WifiDisplay[0];
+        WifiDisplay[] newDisplays = newStatus.getRememberedDisplays();
+        WifiDisplay[] availableDisplays = newStatus.getAvailableDisplays();
+
+        for (int i = 0; i < newDisplays.length; i++) {
+            final WifiDisplay d = newDisplays[i];
+            final WifiDisplay oldRemembered = findMatchingDisplay(d, oldDisplays);
+            if (oldRemembered == null) {
+                addRoute(makeWifiDisplayRoute(d));
+                needScan = true;
+            } else {
+                final boolean available = findMatchingDisplay(d, availableDisplays) != null;
+                final RouteInfo route = findWifiDisplayRoute(d);
+                updateWifiDisplayRoute(route, d, available, newStatus);
+            }
+        }
+        for (int i = 0; i < oldDisplays.length; i++) {
+            final WifiDisplay d = oldDisplays[i];
+            final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays);
+            if (newDisplay == null) {
+                removeRoute(findWifiDisplayRoute(d));
+            }
+        }
+
+        if (needScan) {
+            sStatic.mDisplayService.scanWifiDisplays();
+        }
+
+        sStatic.mLastKnownWifiDisplayStatus = newStatus;
+    }
+
+    static RouteInfo makeWifiDisplayRoute(WifiDisplay display) {
+        final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
+        newRoute.mDeviceAddress = display.getDeviceAddress();
+        newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
+        newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
+        newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
+        newRoute.mStatus = sStatic.mResources.getText(
+                com.android.internal.R.string.media_route_status_connecting);
+        newRoute.mEnabled = false;
+
+        newRoute.mName = makeWifiDisplayName(display);
+        return newRoute;
+    }
+
+    static String makeWifiDisplayName(WifiDisplay display) {
+        String name = display.getDeviceAlias();
+        if (TextUtils.isEmpty(name)) {
+            name = display.getDeviceName();
+        }
+        return name;
+    }
+
+    private static void updateWifiDisplayRoute(RouteInfo route, WifiDisplay display,
+            boolean available, WifiDisplayStatus wifiDisplayStatus) {
+        final boolean isScanning =
+                wifiDisplayStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING;
+
+        boolean changed = false;
+        int newStatus = RouteInfo.STATUS_NONE;
+
+        if (available) {
+            newStatus = isScanning ? RouteInfo.STATUS_SCANNING : RouteInfo.STATUS_AVAILABLE;
+        } else {
+            newStatus = RouteInfo.STATUS_NOT_AVAILABLE;
+        }
+
+        if (display.equals(wifiDisplayStatus.getActiveDisplay())) {
+            final int activeState = wifiDisplayStatus.getActiveDisplayState();
+            switch (activeState) {
+                case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
+                    newStatus = RouteInfo.STATUS_NONE;
+                    break;
+                case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
+                    newStatus = RouteInfo.STATUS_CONNECTING;
+                    break;
+                case WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED:
+                    Log.e(TAG, "Active display is not connected!");
+                    break;
+            }
+        }
+
+        final String newName = makeWifiDisplayName(display);
+        if (route.getName().equals(newName)) {
+            route.mName = newName;
+            changed = true;
+        }
+
+        changed |= route.mEnabled != available;
+        route.mEnabled = available;
+
+        changed |= route.setStatusCode(newStatus);
+
+        if (changed) {
+            dispatchRouteChanged(route);
+        }
+
+        if (!available && route == sStatic.mSelectedRoute) {
+            // Oops, no longer available. Reselect the default.
+            final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo;
+            selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute);
+        }
+    }
+
+    private static WifiDisplay findMatchingDisplay(WifiDisplay address, WifiDisplay[] displays) {
+        for (int i = 0; i < displays.length; i++) {
+            final WifiDisplay d = displays[i];
+            if (d.equals(address)) {
+                return d;
+            }
+        }
+        return null;
+    }
+
+    private static RouteInfo findWifiDisplayRoute(WifiDisplay d) {
+        final int count = sStatic.mRoutes.size();
+        for (int i = 0; i < count; i++) {
+            final RouteInfo info = sStatic.mRoutes.get(i);
+            if (d.getDeviceAddress().equals(info.mDeviceAddress)) {
+                return info;
+            }
+        }
+        return null;
+    }
+
     /**
      * Information about a media route.
      */
@@ -644,6 +834,18 @@
         int mPlaybackStream = AudioManager.STREAM_MUSIC;
         VolumeCallbackInfo mVcb;
 
+        String mDeviceAddress;
+        boolean mEnabled = true;
+
+        // A predetermined connection status that can override mStatus
+        private int mStatusCode;
+
+        static final int STATUS_NONE = 0;
+        static final int STATUS_SCANNING = 1;
+        static final int STATUS_CONNECTING = 2;
+        static final int STATUS_AVAILABLE = 3;
+        static final int STATUS_NOT_AVAILABLE = 4;
+
         private Object mTag;
 
         /**
@@ -711,6 +913,34 @@
         }
 
         /**
+         * Set this route's status by predetermined status code. If the caller
+         * should dispatch a route changed event this call will return true;
+         */
+        boolean setStatusCode(int statusCode) {
+            if (statusCode != mStatusCode) {
+                mStatusCode = statusCode;
+                int resId = 0;
+                switch (statusCode) {
+                    case STATUS_SCANNING:
+                        resId = com.android.internal.R.string.media_route_status_scanning;
+                        break;
+                    case STATUS_CONNECTING:
+                        resId = com.android.internal.R.string.media_route_status_connecting;
+                        break;
+                    case STATUS_AVAILABLE:
+                        resId = com.android.internal.R.string.media_route_status_available;
+                        break;
+                    case STATUS_NOT_AVAILABLE:
+                        resId = com.android.internal.R.string.media_route_status_not_available;
+                        break;
+                }
+                mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
+                return true;
+            }
+            return false;
+        }
+
+        /**
          * @return A media type flag set describing which types this route supports.
          */
         public int getSupportedTypes() {
@@ -866,6 +1096,13 @@
             return mVolumeHandling;
         }
 
+        /**
+         * @return true if this route is enabled and may be selected
+         */
+        public boolean isEnabled() {
+            return mEnabled;
+        }
+
         void setStatusInt(CharSequence status) {
             if (!status.equals(mStatus)) {
                 mStatus = status;
@@ -881,7 +1118,6 @@
                 sStatic.mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                      //Log.d(TAG, "dispatchRemoteVolumeUpdate dir=" + direction + " val=" + value);
                         if (mVcb != null) {
                             if (direction != 0) {
                                 mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
@@ -1400,6 +1636,7 @@
         int mNameResId;
         int mTypes;
         final boolean mGroupable;
+        boolean mIsSystem;
 
         RouteCategory(CharSequence name, int types, boolean groupable) {
             mName = name;
@@ -1486,6 +1723,14 @@
             return mGroupable;
         }
 
+        /**
+         * @return true if this is the category reserved for system routes.
+         * @hide
+         */
+        public boolean isSystem() {
+            return mIsSystem;
+        }
+
         public String toString() {
             return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
                     " groupable=" + mGroupable + " }";
@@ -1671,7 +1916,6 @@
     }
 
     static class VolumeChangeReceiver extends BroadcastReceiver {
-
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
@@ -1689,6 +1933,15 @@
                 }
             }
         }
+    }
 
+    static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
+                updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(
+                        DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));
+            }
+        }
     }
 }
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 4f0d113..e084368 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1073,7 +1073,7 @@
         if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
             if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
                 if (mTicking) {
-                    mTicker.halt();
+                    haltTicker();
                 }
 
                 mNotificationIcons.animate()
@@ -1095,7 +1095,7 @@
             }
         } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
             if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
-                mTicker.halt();
+                haltTicker();
             }
         }
     }
@@ -1359,7 +1359,7 @@
                 if (lightsOut) {
                     animateCollapsePanels();
                     if (mTicking) {
-                        mTicker.halt();
+                        haltTicker();
                     }
                 }
 
@@ -1489,8 +1489,7 @@
             mStatusBarContents.setVisibility(View.VISIBLE);
             mTickerView.setVisibility(View.GONE);
             mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
-            mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out,
-                        mTickingDoneListener));
+            // we do not animate the ticker away at this point, just get rid of it (b/6992707)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index 35b9f85..165250b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -246,9 +246,10 @@
             @Override
             public void onClick(View v) {
                 mBar.collapseAllPanels(true);
-                ContactsContract.QuickContact.showQuickContact(mContext, v,
-                        ContactsContract.Profile.CONTENT_URI,
+                Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(mContext,
+                        v, ContactsContract.Profile.CONTENT_URI,
                         ContactsContract.QuickContact.MODE_LARGE, null);
+                mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
             }
         });
         mModel.addUserTile(userTile, new QuickSettingsModel.RefreshCallback() {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index e4eeceb..c3bd988 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -169,11 +169,6 @@
     static final boolean SHOW_STARTING_ANIMATIONS = true;
     static final boolean SHOW_PROCESSES_ON_ALT_MENU = false;
 
-    // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
-    // No longer recommended for desk docks; still useful in car docks.
-    static final boolean ENABLE_CAR_DOCK_HOME_CAPTURE = true;
-    static final boolean ENABLE_DESK_DOCK_HOME_CAPTURE = false;
-
     static final int LONG_PRESS_POWER_NOTHING = 0;
     static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
     static final int LONG_PRESS_POWER_SHUT_OFF = 2;
@@ -347,7 +342,6 @@
     boolean mSystemReady;
     boolean mSystemBooted;
     boolean mHdmiPlugged;
-    int mUiMode;
     int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     int mLidOpenRotation;
     int mCarDockRotation;
@@ -892,8 +886,6 @@
         mSettingsObserver.observe();
         mShortcutManager = new ShortcutManager(context, mHandler);
         mShortcutManager.observe();
-        mUiMode = context.getResources().getInteger(
-                com.android.internal.R.integer.config_defaultUiModeType);
         mHomeIntent =  new Intent(Intent.ACTION_MAIN, null);
         mHomeIntent.addCategory(Intent.CATEGORY_HOME);
         mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -3563,13 +3555,6 @@
             if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
                 mDockMode = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
                         Intent.EXTRA_DOCK_STATE_UNDOCKED);
-            } else {
-                try {
-                    IUiModeManager uiModeService = IUiModeManager.Stub.asInterface(
-                            ServiceManager.getService(Context.UI_MODE_SERVICE));
-                    mUiMode = uiModeService.getCurrentModeType();
-                } catch (RemoteException e) {
-                }
             }
             updateRotation(true);
             updateOrientationListenerLp();
@@ -4122,63 +4107,8 @@
         }
     }
 
-    /**
-     * Return an Intent to launch the currently active dock app as home.  Returns
-     * null if the standard home should be launched, which is the case if any of the following is
-     * true:
-     * <ul>
-     *  <li>The device is not in either car mode or desk mode
-     *  <li>The device is in car mode but ENABLE_CAR_DOCK_HOME_CAPTURE is false
-     *  <li>The device is in desk mode but ENABLE_DESK_DOCK_HOME_CAPTURE is false
-     *  <li>The device is in car mode but there's no CAR_DOCK app with METADATA_DOCK_HOME
-     *  <li>The device is in desk mode but there's no DESK_DOCK app with METADATA_DOCK_HOME
-     * </ul>
-     * @return
-     */
-    Intent createHomeDockIntent() {
-        Intent intent = null;
-        
-        // What home does is based on the mode, not the dock state.  That
-        // is, when in car mode you should be taken to car home regardless
-        // of whether we are actually in a car dock.
-        if (mUiMode == Configuration.UI_MODE_TYPE_CAR) {
-            if (ENABLE_CAR_DOCK_HOME_CAPTURE) {
-                intent = mCarDockIntent;
-            }
-        } else if (mUiMode == Configuration.UI_MODE_TYPE_DESK) {
-            if (ENABLE_DESK_DOCK_HOME_CAPTURE) {
-                intent = mDeskDockIntent;
-            }
-        }
-
-        if (intent == null) {
-            return null;
-        }
-        
-        ActivityInfo ai = intent.resolveActivityInfo(
-                mContext.getPackageManager(), PackageManager.GET_META_DATA);
-        if (ai == null) {
-            return null;
-        }
-        
-        if (ai.metaData != null && ai.metaData.getBoolean(Intent.METADATA_DOCK_HOME)) {
-            intent = new Intent(intent);
-            intent.setClassName(ai.packageName, ai.name);
-            return intent;
-        }
-        
-        return null;
-    }
-    
     void startDockOrHome() {
-        Intent dock = createHomeDockIntent();
-        if (dock != null) {
-            try {
-                mContext.startActivity(dock);
-                return;
-            } catch (ActivityNotFoundException e) {
-            }
-        }
+        // We don't have dock home anymore. Home is home. If you lived here, you'd be home by now.
         mContext.startActivityAsUser(mHomeIntent, UserHandle.CURRENT);
     }
     
@@ -4205,18 +4135,6 @@
                 } else {
                     ActivityManagerNative.getDefault().stopAppSwitches();
                     sendCloseSystemWindows();
-                    Intent dock = createHomeDockIntent();
-                    if (dock != null) {
-                        int result = ActivityManagerNative.getDefault()
-                                .startActivityAsUser(null, dock,
-                                        dock.resolveTypeIfNeeded(mContext.getContentResolver()),
-                                        null, null, 0,
-                                        ActivityManager.START_FLAG_ONLY_IF_NEEDED,
-                                        null, null, null, UserHandle.USER_CURRENT);
-                        if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) {
-                            return false;
-                        }
-                    }
                 }
                 int result = ActivityManagerNative.getDefault()
                         .startActivityAsUser(null, mHomeIntent,
@@ -4403,8 +4321,7 @@
             pw.print(prefix); pw.print("mLastFocusNeedsMenu=");
                     pw.println(mLastFocusNeedsMenu);
         }
-        pw.print(prefix); pw.print("mUiMode="); pw.print(mUiMode);
-                pw.print(" mDockMode="); pw.print(mDockMode);
+        pw.print(prefix); pw.print("mDockMode="); pw.print(mDockMode);
                 pw.print(" mCarDockRotation="); pw.print(mCarDockRotation);
                 pw.print(" mDeskDockRotation="); pw.println(mDeskDockRotation);
         pw.print(prefix); pw.print("mUserRotationMode="); pw.print(mUserRotationMode);
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index c5ec33b..1d1c7fc 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -547,8 +547,9 @@
         oldView.onPause();
         newView.onResume();
 
+        final boolean needsInput = newView.needsInput();
         if (mViewMediatorCallback != null) {
-            mViewMediatorCallback.setNeedsInput(newView.needsInput());
+            mViewMediatorCallback.setNeedsInput(needsInput);
         }
 
         // Find and show this child.
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index 542cc07..3e9bef0 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -102,6 +102,10 @@
     // The timeout after which we are no longer trying to detect a gesture.
     private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000;
 
+    // The timeout to send interaction end events in case we did not
+    // receive the expected hover exit event due to a misbehaving app.
+    private static final int SEND_INTERACTION_END_EVENTS_TIMEOUT = 200;
+
     // Temporary array for storing pointer IDs.
     private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
 
@@ -135,6 +139,9 @@
     // Command for delayed sending of a hover exit event.
     private final SendHoverDelayed mSendHoverExitDelayed;
 
+    // Command for delayed sending of interaction ending events.
+    private final SendInteractionEndEventsDelayed mSendInteractionEndEventsDelayed;
+
     // Command for delayed sending of a long press.
     private final PerformLongPressDelayed mPerformLongPressDelayed;
 
@@ -233,6 +240,7 @@
         mGestureLibrary.load();
         mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true);
         mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false);
+        mSendInteractionEndEventsDelayed = new SendInteractionEndEventsDelayed();
         mDoubleTapDetector = new DoubleTapDetector();
         final float density = context.getResources().getDisplayMetrics().density;
         mScaledMinPointerDistanceToUseMiddleLocation =
@@ -278,6 +286,7 @@
         mSendHoverExitDelayed.remove();
         mPerformLongPressDelayed.remove();
         mExitGestureDetectionModeDelayed.remove();
+        mSendInteractionEndEventsDelayed.remove();
         // Reset the pointer trackers.
         mReceivedPointerTracker.clear();
         mInjectedPointerTracker.clear();
@@ -334,6 +343,7 @@
         // last hover exit event.
         if (mTouchExplorationGestureEnded
                 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
+            mSendInteractionEndEventsDelayed.remove();
             mTouchExplorationGestureEnded = false;
             sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
         }
@@ -342,6 +352,7 @@
         // last hover exit and the touch exploration gesture end events.
         if (mTouchInteractionEnded
                 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
+            mSendInteractionEndEventsDelayed.remove();
             mTouchInteractionEnded = false;
             sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
         }
@@ -416,6 +427,10 @@
                             mSendHoverExitDelayed.remove();
                         }
 
+                        if (mSendInteractionEndEventsDelayed.isPending()) {
+                            mSendInteractionEndEventsDelayed.forceSendAndRemove();
+                        }
+
                         mPerformLongPressDelayed.remove();
 
                         // If we have the first tap schedule a long press and break
@@ -873,6 +888,9 @@
             final int pointerIdBits = event.getPointerIdBits();
             mTouchExplorationGestureEnded = true;
             mTouchInteractionEnded = true;
+            if (!mSendInteractionEndEventsDelayed.isPending()) {
+                mSendInteractionEndEventsDelayed.post();
+            }
             sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
         }
     }
@@ -1484,10 +1502,16 @@
                 } else {
                     mTouchExplorationGestureEnded = true;
                     mTouchInteractionEnded = true;
+                    if (!mSendInteractionEndEventsDelayed.isPending()) {
+                        mSendInteractionEndEventsDelayed.post();
+                    }
                 }
             } else {
                 if (!mGestureStarted) {
                     mTouchInteractionEnded = true;
+                    if (!mSendInteractionEndEventsDelayed.isPending()) {
+                        mSendInteractionEndEventsDelayed.post();
+                    }
                 }
             }
             sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags);
@@ -1495,6 +1519,40 @@
         }
     }
 
+    private class SendInteractionEndEventsDelayed implements Runnable {
+
+        public void remove() {
+            mHandler.removeCallbacks(this);
+        }
+
+        public void post() {
+            mHandler.postDelayed(this, SEND_INTERACTION_END_EVENTS_TIMEOUT);
+        }
+
+        public boolean isPending() {
+            return mHandler.hasCallbacks(this);
+        }
+
+        public void forceSendAndRemove() {
+            if (isPending()) {
+                run();
+                remove();
+            }
+        }
+
+        @Override
+        public void run() {
+            if (mTouchExplorationGestureEnded) {
+                mTouchExplorationGestureEnded = false;
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
+            }
+            if (mTouchInteractionEnded) {
+                mTouchInteractionEnded = false;
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+            }
+        }
+    }
+
     @Override
     public String toString() {
         return LOG_TAG;
diff --git a/services/jni/com_android_server_power_PowerManagerService.cpp b/services/jni/com_android_server_power_PowerManagerService.cpp
index 38af38d..dcc2b58 100644
--- a/services/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/jni/com_android_server_power_PowerManagerService.cpp
@@ -183,14 +183,16 @@
             gPowerModule->setInteractive(gPowerModule, true);
         }
 
+        const sp<IBinder>& display = s->getBuiltInDisplay(0);   // TODO: support multiple displays
         {
             ALOGD_IF_SLOW(100, "Excessive delay in unblank() while turning screen on");
-            s->unblank();
+            s->unblank(display);
         }
     } else {
+        const sp<IBinder>& display = s->getBuiltInDisplay(0);   // TODO: support multiple displays
         {
             ALOGD_IF_SLOW(100, "Excessive delay in blank() while turning screen off");
-            s->blank();
+            s->blank(display);
         }
 
         if (gPowerModule) {