Merge "Help out issue #6654729: CAB + screen off during playback" into jb-dev
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 539b84e..0d8a7cd 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -132,6 +132,7 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.java)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/accessibilityservice/IAccessibilityServiceClientCallback.P)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/accessibilityservice/IAccessibilityServiceClient.P)
+$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/media/video/Disco*)
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/api/16.txt b/api/16.txt
index c95de36..e4b098f 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -3679,6 +3679,7 @@
   public class MediaRouteActionProvider extends android.view.ActionProvider {
     ctor public MediaRouteActionProvider(android.content.Context);
     method public android.view.View onCreateActionView();
+    method public void setExtendedSettingsClickListener(android.view.View.OnClickListener);
     method public void setRouteTypes(int);
   }
 
@@ -3687,7 +3688,9 @@
     ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet);
     ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet, int);
     method public int getRouteTypes();
+    method public void setExtendedSettingsClickListener(android.view.View.OnClickListener);
     method public void setRouteTypes(int);
+    method public void showDialog();
   }
 
   public class NativeActivity extends android.app.Activity implements android.view.InputQueue.Callback android.view.SurfaceHolder.Callback2 android.view.ViewTreeObserver.OnGlobalLayoutListener {
@@ -11511,6 +11514,7 @@
     method public void addUserRoute(android.media.MediaRouter.UserRouteInfo);
     method public void clearUserRoutes();
     method public android.media.MediaRouter.RouteCategory createRouteCategory(java.lang.CharSequence, boolean);
+    method public android.media.MediaRouter.RouteCategory createRouteCategory(int, boolean);
     method public android.media.MediaRouter.UserRouteInfo createUserRoute(android.media.MediaRouter.RouteCategory);
     method public android.media.MediaRouter.RouteCategory getCategoryAt(int);
     method public int getCategoryCount();
@@ -11524,7 +11528,8 @@
     field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
   }
 
-  public static abstract interface MediaRouter.Callback {
+  public static abstract class MediaRouter.Callback {
+    ctor public MediaRouter.Callback();
     method public abstract void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int);
@@ -11536,6 +11541,7 @@
 
   public static class MediaRouter.RouteCategory {
     method public java.lang.CharSequence getName();
+    method public java.lang.CharSequence getName(android.content.Context);
     method public java.util.List<android.media.MediaRouter.RouteInfo> getRoutes(java.util.List<android.media.MediaRouter.RouteInfo>);
     method public int getSupportedTypes();
     method public boolean isGroupable();
@@ -11548,17 +11554,21 @@
     method public int getRouteCount();
     method public void removeRoute(android.media.MediaRouter.RouteInfo);
     method public void removeRoute(int);
+    method public void setIconDrawable(android.graphics.drawable.Drawable);
+    method public void setIconResource(int);
   }
 
   public static class MediaRouter.RouteInfo {
     method public android.media.MediaRouter.RouteCategory getCategory();
     method public android.media.MediaRouter.RouteGroup getGroup();
+    method public android.graphics.drawable.Drawable getIconDrawable();
     method public java.lang.CharSequence getName();
+    method public java.lang.CharSequence getName(android.content.Context);
     method public java.lang.CharSequence getStatus();
     method public int getSupportedTypes();
   }
 
-  public static class MediaRouter.SimpleCallback implements android.media.MediaRouter.Callback {
+  public static class MediaRouter.SimpleCallback extends android.media.MediaRouter.Callback {
     ctor public MediaRouter.SimpleCallback();
     method public void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
@@ -11570,8 +11580,14 @@
   }
 
   public static class MediaRouter.UserRouteInfo extends android.media.MediaRouter.RouteInfo {
+    method public java.lang.Object getTag();
+    method public void setIconDrawable(android.graphics.drawable.Drawable);
+    method public void setIconResource(int);
     method public void setName(java.lang.CharSequence);
+    method public void setName(int);
+    method public void setRemoteControlClient(android.media.RemoteControlClient);
     method public void setStatus(java.lang.CharSequence);
+    method public void setTag(java.lang.Object);
   }
 
   public class MediaScannerConnection implements android.content.ServiceConnection {
@@ -23186,6 +23202,7 @@
     field public static final int KEYCODE_ALT_RIGHT = 58; // 0x3a
     field public static final int KEYCODE_APOSTROPHE = 75; // 0x4b
     field public static final int KEYCODE_APP_SWITCH = 187; // 0xbb
+    field public static final int KEYCODE_ASSIST = 219; // 0xdb
     field public static final int KEYCODE_AT = 77; // 0x4d
     field public static final int KEYCODE_AVR_INPUT = 182; // 0xb6
     field public static final int KEYCODE_AVR_POWER = 181; // 0xb5
diff --git a/api/current.txt b/api/current.txt
index f5bc504..e4b098f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11514,6 +11514,7 @@
     method public void addUserRoute(android.media.MediaRouter.UserRouteInfo);
     method public void clearUserRoutes();
     method public android.media.MediaRouter.RouteCategory createRouteCategory(java.lang.CharSequence, boolean);
+    method public android.media.MediaRouter.RouteCategory createRouteCategory(int, boolean);
     method public android.media.MediaRouter.UserRouteInfo createUserRoute(android.media.MediaRouter.RouteCategory);
     method public android.media.MediaRouter.RouteCategory getCategoryAt(int);
     method public int getCategoryCount();
@@ -11527,7 +11528,8 @@
     field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
   }
 
-  public static abstract interface MediaRouter.Callback {
+  public static abstract class MediaRouter.Callback {
+    ctor public MediaRouter.Callback();
     method public abstract void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int);
@@ -11539,6 +11541,7 @@
 
   public static class MediaRouter.RouteCategory {
     method public java.lang.CharSequence getName();
+    method public java.lang.CharSequence getName(android.content.Context);
     method public java.util.List<android.media.MediaRouter.RouteInfo> getRoutes(java.util.List<android.media.MediaRouter.RouteInfo>);
     method public int getSupportedTypes();
     method public boolean isGroupable();
@@ -11560,11 +11563,12 @@
     method public android.media.MediaRouter.RouteGroup getGroup();
     method public android.graphics.drawable.Drawable getIconDrawable();
     method public java.lang.CharSequence getName();
+    method public java.lang.CharSequence getName(android.content.Context);
     method public java.lang.CharSequence getStatus();
     method public int getSupportedTypes();
   }
 
-  public static class MediaRouter.SimpleCallback implements android.media.MediaRouter.Callback {
+  public static class MediaRouter.SimpleCallback extends android.media.MediaRouter.Callback {
     ctor public MediaRouter.SimpleCallback();
     method public void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
@@ -11580,6 +11584,7 @@
     method public void setIconDrawable(android.graphics.drawable.Drawable);
     method public void setIconResource(int);
     method public void setName(java.lang.CharSequence);
+    method public void setName(int);
     method public void setRemoteControlClient(android.media.RemoteControlClient);
     method public void setStatus(java.lang.CharSequence);
     method public void setTag(java.lang.Object);
@@ -23197,6 +23202,7 @@
     field public static final int KEYCODE_ALT_RIGHT = 58; // 0x3a
     field public static final int KEYCODE_APOSTROPHE = 75; // 0x4b
     field public static final int KEYCODE_APP_SWITCH = 187; // 0xbb
+    field public static final int KEYCODE_ASSIST = 219; // 0xdb
     field public static final int KEYCODE_AT = 77; // 0x4d
     field public static final int KEYCODE_AVR_INPUT = 182; // 0xb6
     field public static final int KEYCODE_AVR_POWER = 181; // 0xb5
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index a4eebda..018b25d 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -103,7 +103,7 @@
 
         if (mToggleMode) {
             if (mRemoteActive) {
-                mRouter.selectRoute(mRouteTypes, mRouter.getSystemAudioRoute());
+                mRouter.selectRouteInt(mRouteTypes, mRouter.getSystemAudioRoute());
             } else {
                 final int N = mRouter.getRouteCount();
                 for (int i = 0; i < N; i++) {
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 691ee30..e8bd546 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.graphics.Rect;
@@ -32,6 +33,7 @@
 import android.os.ServiceManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Slog;
 import android.view.KeyEvent;
 
 import java.util.List;
@@ -837,4 +839,32 @@
         }
     }
 
+    /**
+     * Returns true if the global assist activity is available.
+     * @return True if the assistant is available.
+     *
+     * @hide
+     */
+    public final boolean isAssistantAvailable() {
+        Intent intent = getAssistIntent();
+        return intent != null
+                && mContext.getPackageManager().queryIntentActivities(intent,
+                        PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+    }
+
+    /**
+     * Gets an intent to launch the global assist activity, or null if not available.
+     * @return The assist intent.
+     *
+     * @hide
+     */
+    public final Intent getAssistIntent() {
+        ComponentName globalSearchActivity = getGlobalSearchActivity();
+        if (globalSearchActivity != null) {
+            Intent intent = new Intent(Intent.ACTION_ASSIST);
+            intent.setPackage(globalSearchActivity.getPackageName());
+            return intent;
+        }
+        return null;
+    }
 }
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 12f16fd..d2bed48 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1696,8 +1696,14 @@
         return text.getSpans(start, end, type);
     }
 
+    private char getEllipsisChar(TextUtils.TruncateAt method) {
+        return (method == TextUtils.TruncateAt.END_SMALL) ?
+                ELLIPSIS_TWO_DOTS[0] :
+                ELLIPSIS_NORMAL[0];
+    }
+
     private void ellipsize(int start, int end, int line,
-                           char[] dest, int destoff) {
+                           char[] dest, int destoff, TextUtils.TruncateAt method) {
         int ellipsisCount = getEllipsisCount(line);
 
         if (ellipsisCount == 0) {
@@ -1711,7 +1717,7 @@
             char c;
 
             if (i == ellipsisStart) {
-                c = '\u2026'; // ellipsis
+                c = getEllipsisChar(method); // ellipsis
             } else {
                 c = '\uFEFF'; // 0-width space
             }
@@ -1785,7 +1791,7 @@
             TextUtils.getChars(mText, start, end, dest, destoff);
 
             for (int i = line1; i <= line2; i++) {
-                mLayout.ellipsize(start, end, i, dest, destoff);
+                mLayout.ellipsize(start, end, i, dest, destoff, mMethod);
             }
         }
 
@@ -1890,4 +1896,6 @@
     /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
         new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
 
+    /* package */ static final char[] ELLIPSIS_NORMAL = { '\u2026' }; // this is "..."
+    /* package */ static final char[] ELLIPSIS_TWO_DOTS = { '\u2025' }; // this is ".."
 }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 299e115..6973b2e 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -745,7 +745,8 @@
         }
 
         float ellipsisWidth = paint.measureText(
-                (where == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL);
+                (where == TextUtils.TruncateAt.END_SMALL) ?
+                        ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL, 0, 1);
         int ellipsisStart = 0;
         int ellipsisCount = 0;
         int len = lineEnd - lineStart;
@@ -985,9 +986,6 @@
 
     private static final double EXTRA_ROUNDING = 0.5;
 
-    private static final String ELLIPSIS_NORMAL = "\u2026"; // this is "..."
-    private static final String ELLIPSIS_TWO_DOTS = "\u2025"; // this is ".."
-
     private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800;
     private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF;
 
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 78dc86f..4d1b836 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -79,6 +79,16 @@
     // be dequeued.
     private static final long DEFAULT_FRAME_DELAY = 10;
 
+    // The fake vsync delay in milliseconds.
+    // When the screen is off, we might not receive real vsync pulses from the hardware
+    // which would cause posted Choreographer callbacks to not run.  This is bad because
+    // messages in the Looper might be blocked behind a barrier that is scheduled to be
+    // removed by one of those Choreographer callback (see ViewRootImpl.doTraversals).
+    // Until the barrier is removed, those messages will not run.  To prevent starvation
+    // of the Looper, we synthesize fake vsync pulses at a reduced rate whenever the
+    // display hardware stops generating them.
+    private static final long FAKE_VSYNC_DELAY = 100;
+
     // The number of milliseconds between animation frames.
     private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY;
 
@@ -113,6 +123,7 @@
     private static final int MSG_DO_FRAME = 0;
     private static final int MSG_DO_SCHEDULE_VSYNC = 1;
     private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
+    private static final int MSG_FAKE_VSYNC = 3;
 
     // All frame callbacks posted by applications have this token.
     private static final Object FRAME_CALLBACK_TOKEN = new Object() {
@@ -587,6 +598,13 @@
 
     private void scheduleVsyncLocked() {
         mDisplayEventReceiver.scheduleVsync();
+
+        // Post a message to simulate a fake vsync pulse at a reduced rate in case the
+        // display hardware stops generating them.  This ensures that Choreographer
+        // callbacks can continue to run even if the screen is off.
+        Message msg = mHandler.obtainMessage(MSG_FAKE_VSYNC);
+        msg.setAsynchronous(true);
+        mHandler.sendMessageDelayed(msg, FAKE_VSYNC_DELAY);
     }
 
     private boolean isRunningOnLooperThreadLocked() {
@@ -662,6 +680,12 @@
                 case MSG_DO_SCHEDULE_CALLBACK:
                     doScheduleCallback(msg.arg1);
                     break;
+                case MSG_FAKE_VSYNC:
+                    if (DEBUG) {
+                        Log.d(TAG, "Handling fake vsync while screen is off.");
+                    }
+                    doFrame(System.nanoTime(), 0);
+                    break;
             }
         }
     }
@@ -683,6 +707,17 @@
             // the message queue.  If there are no messages in the queue with timestamps
             // earlier than the frame time, then the vsync event will be processed immediately.
             // Otherwise, messages that predate the vsync event will be handled first.
+            if (mHavePendingVsync) {
+                if (DEBUG) {
+                    Log.d(TAG, "Already have a pending vsync event.  There should only be "
+                            + "one at a time but they can double up when a fake vsync "
+                            + "is handled in place of a real one.");
+                }
+                mHandler.removeCallbacks(this);
+            } else {
+                mHavePendingVsync = true;
+            }
+
             long now = System.nanoTime();
             if (timestampNanos > now) {
                 Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
@@ -691,12 +726,7 @@
                 timestampNanos = now;
             }
 
-            if (mHavePendingVsync) {
-                Log.w(TAG, "Already have a pending vsync event.  There should only be "
-                        + "one at a time.");
-            } else {
-                mHavePendingVsync = true;
-            }
+            mHandler.removeMessages(MSG_FAKE_VSYNC);
 
             mTimestampNanos = timestampNanos;
             mFrame = frame;
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 1080229..c2a3e58 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -620,8 +620,11 @@
     public static final int KEYCODE_RO              = 217;
     /** Key code constant: Japanese kana key. */
     public static final int KEYCODE_KANA            = 218;
+    /** Key code constant: Assist key.
+     * Launches the global assist activity.  Not delivered to applications. */
+    public static final int KEYCODE_ASSIST          = 219;
 
-    private static final int LAST_KEYCODE           = KEYCODE_KANA;
+    private static final int LAST_KEYCODE           = KEYCODE_ASSIST;
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
@@ -862,6 +865,7 @@
         names.append(KEYCODE_YEN, "KEYCODE_YEN");
         names.append(KEYCODE_RO, "KEYCODE_RO");
         names.append(KEYCODE_KANA, "KEYCODE_KANA");
+        names.append(KEYCODE_ASSIST, "KEYCODE_ASSIST");
     };
 
     // Symbolic names of all metakeys in bit order from least significant to most significant.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cdc51d1..51fd346 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3926,10 +3926,9 @@
     }
     
     public void dumpGfxInfo(int[] info) {
+        info[0] = info[1] = 0;
         if (mView != null) {
             getGfxInfo(mView, info);
-        } else {
-            info[0] = info[1] = 0;
         }
     }
 
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5d33cec..dd6b537 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -530,13 +530,16 @@
 
                     for (int i = 0; i < count; i++) {
                         ViewRootImpl root = mRoots[i];
+                        String name = getWindowName(root);
+                        pw.printf("\n\t%s", name);
+
                         HardwareRenderer renderer = root.getView().mAttachInfo.mHardwareRenderer;
                         if (renderer != null) {
                             renderer.dumpGfxInfo(pw);
                         }
                     }
 
-                    pw.println("\nView hierarchy:");
+                    pw.println("\nView hierarchy:\n");
 
                     int viewsCount = 0;
                     int displayListsSize = 0;
@@ -546,15 +549,14 @@
                         ViewRootImpl root = mRoots[i];
                         root.dumpGfxInfo(info);
 
-                        String name = root.getClass().getName() + '@' +
-                                Integer.toHexString(hashCode());                        
-                        pw.printf("  %s: %d views, %.2f kB (display lists)",
+                        String name = getWindowName(root);
+                        pw.printf("  %s\n  %d views, %.2f kB of display lists",
                                 name, info[0], info[1] / 1024.0f);
                         HardwareRenderer renderer = root.getView().mAttachInfo.mHardwareRenderer;
                         if (renderer != null) {
                             pw.printf(", %d frames rendered", renderer.getFrameCount());
                         }
-                        pw.printf("\n");
+                        pw.printf("\n\n");
 
                         viewsCount += info[0];
                         displayListsSize += info[1];
@@ -570,6 +572,11 @@
         }        
     }
 
+    private static String getWindowName(ViewRootImpl root) {
+        return root.mWindowAttributes.getTitle() + "/" +
+                root.getClass().getName() + '@' + Integer.toHexString(root.hashCode());
+    }
+
     public void setStoppedState(IBinder token, boolean stopped) {
         synchronized (this) {
             if (mViews == null)
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
index cc490bd..7dfb5bb 100644
--- a/core/java/android/webkit/AccessibilityInjector.java
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -43,11 +43,6 @@
  * APIs.
  */
 class AccessibilityInjector {
-    // Default result returned from AndroidVox. Using true here means if the
-    // script fails, an accessibility service will always think that traversal
-    // has succeeded.
-    private static final String DEFAULT_ANDROIDVOX_RESULT = "true";
-
     // The WebViewClassic this injector is responsible for managing.
     private final WebViewClassic mWebViewClassic;
 
@@ -488,15 +483,19 @@
             switch (action) {
                 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
                 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
-                    final int granularity = arguments.getInt(
-                            AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
-                    mAccessibilityJSONObject.accumulate("granularity", granularity);
+                    if (arguments != null) {
+                        final int granularity = arguments.getInt(
+                                AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
+                        mAccessibilityJSONObject.accumulate("granularity", granularity);
+                    }
                     break;
                 case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
                 case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
-                    final String element = arguments.getString(
-                            AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING);
-                    mAccessibilityJSONObject.accumulate("element", element);
+                    if (arguments != null) {
+                        final String element = arguments.getString(
+                                AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING);
+                        mAccessibilityJSONObject.accumulate("element", element);
+                    }
                     break;
             }
         } catch (JSONException e) {
@@ -505,9 +504,7 @@
 
         final String jsonString = mAccessibilityJSONObject.toString();
         final String jsCode = String.format(ACCESSIBILITY_ANDROIDVOX_TEMPLATE, jsonString);
-        final String result = mCallback.performAction(mWebView, jsCode, DEFAULT_ANDROIDVOX_RESULT);
-
-        return ("true".equalsIgnoreCase(result));
+        return mCallback.performAction(mWebView, jsCode);
     }
 
     /**
@@ -518,13 +515,13 @@
                 "javascript:(function() { %s.onResult(%d, %s); })();";
 
         // Time in milliseconds to wait for a result before failing.
-        private static final long RESULT_TIMEOUT = 200;
+        private static final long RESULT_TIMEOUT = 5000;
 
         private final AtomicInteger mResultIdCounter = new AtomicInteger();
         private final Object mResultLock = new Object();
         private final String mInterfaceName;
 
-        private String mResult = null;
+        private boolean mResult = false;
         private long mResultId = -1;
 
         private CallbackHandler(String interfaceName) {
@@ -536,29 +533,27 @@
          *
          * @param webView The WebView to perform the action on.
          * @param code JavaScript code that evaluates to a result.
-         * @param defaultResult The result to return if the action times out.
          * @return The result of the action, or false if it timed out.
          */
-        private String performAction(WebView webView, String code, String defaultResult) {
+        private boolean performAction(WebView webView, String code) {
             final int resultId = mResultIdCounter.getAndIncrement();
             final String url = String.format(
                     JAVASCRIPT_ACTION_TEMPLATE, mInterfaceName, resultId, code);
             webView.loadUrl(url);
 
-            return getResultAndClear(resultId, defaultResult);
+            return getResultAndClear(resultId);
         }
 
         /**
          * Gets the result of a request to perform an accessibility action.
          *
          * @param resultId The result id to match the result with the request.
-         * @param defaultResult The default result to return on timeout.
          * @return The result of the request.
          */
-        private String getResultAndClear(int resultId, String defaultResult) {
+        private boolean getResultAndClear(int resultId) {
             synchronized (mResultLock) {
                 final boolean success = waitForResultTimedLocked(resultId);
-                final String result = success ? mResult : defaultResult;
+                final boolean result = success ? mResult : false;
                 clearResultLocked();
                 return result;
             }
@@ -569,7 +564,7 @@
          */
         private void clearResultLocked() {
             mResultId = -1;
-            mResult = null;
+            mResult = false;
         }
 
         /**
@@ -620,7 +615,7 @@
 
             synchronized (mResultLock) {
                 if (resultId > mResultId) {
-                    mResult = result;
+                    mResult = Boolean.parseBoolean(result);
                     mResultId = resultId;
                 }
                 mResultLock.notifyAll();
diff --git a/core/java/android/webkit/AccessibilityInjectorFallback.java b/core/java/android/webkit/AccessibilityInjectorFallback.java
index 4d9c26c..783b3db 100644
--- a/core/java/android/webkit/AccessibilityInjectorFallback.java
+++ b/core/java/android/webkit/AccessibilityInjectorFallback.java
@@ -272,11 +272,19 @@
     boolean performAccessibilityAction(int action, Bundle arguments) {
         switch (action) {
             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
-            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
+            case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
                 final int direction = getDirectionForAction(action);
                 final int axis = getAxisForGranularity(arguments.getInt(
                         AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT));
                 return traverseGivenAxis(direction, axis, true, null);
+            }
+            case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
+            case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: {
+                final int direction = getDirectionForAction(action);
+                // TODO: Add support for moving by object.
+                final int axis = NAVIGATION_AXIS_SENTENCE;
+                return traverseGivenAxis(direction, axis, true, null);
+            }
             default:
                 return false;
         }
@@ -291,8 +299,10 @@
      */
     private static int getDirectionForAction(int action) {
         switch (action) {
+            case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
                 return NAVIGATION_DIRECTION_FORWARD;
+            case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
                 return NAVIGATION_DIRECTION_BACKWARD;
             default:
@@ -316,8 +326,8 @@
             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE:
                 return NAVIGATION_AXIS_SENTENCE;
             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH:
-                // TODO: Figure out what nextSibling() actually means.
-                return NAVIGATION_AXIS_SIBLING;
+                // TODO: This should map to object once we implement it.
+                return NAVIGATION_AXIS_SENTENCE;
             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE:
                 return NAVIGATION_AXIS_DOCUMENT;
             default:
diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java
index 354bb5a..1288613 100644
--- a/core/java/android/webkit/WebSettingsClassic.java
+++ b/core/java/android/webkit/WebSettingsClassic.java
@@ -22,7 +22,8 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
-import android.util.DisplayMetrics;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
 import android.util.EventLog;
 
 import java.util.Locale;
@@ -122,6 +123,7 @@
     private boolean         mLoadWithOverviewMode = false;
     private boolean         mEnableSmoothTransition = false;
     private boolean         mForceUserScalable = false;
+    private boolean         mPasswordEchoEnabled = true;
 
     // AutoFill Profile data
     public static class AutoFillProfile {
@@ -295,6 +297,13 @@
             mAllowUniversalAccessFromFileURLs = true;
             mAllowFileAccessFromFileURLs = true;
         }
+        try {
+            mPasswordEchoEnabled =
+                    Settings.System.getInt(context.getContentResolver(),
+                        Settings.System.TEXT_SHOW_PASSWORD) != 0;
+        } catch (SettingNotFoundException e) {
+            mPasswordEchoEnabled = true;
+        }
     }
 
     private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US";
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index 000eee7..6c9a047 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -19,10 +19,12 @@
 import com.android.internal.R;
 
 import android.app.Activity;
+import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.MediaRouteActionProvider;
 import android.app.MediaRouteButton;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteCategory;
 import android.media.MediaRouter.RouteGroup;
@@ -34,10 +36,14 @@
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.TextView;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 
 /**
  * This class implements the route chooser dialog for {@link MediaRouter}.
@@ -49,14 +55,30 @@
     private static final String TAG = "MediaRouteChooserDialogFragment";
     public static final String FRAGMENT_TAG = "android:MediaRouteChooserDialogFragment";
 
+    private static final int[] ITEM_LAYOUTS = new int[] {
+        R.layout.media_route_list_item_top_header,
+        R.layout.media_route_list_item_section_header,
+        R.layout.media_route_list_item
+    };
+
+    private static final int[] GROUP_ITEM_LAYOUTS = new int[] {
+        R.layout.media_route_list_item_top_header,
+        R.layout.media_route_list_item_checkable,
+        R.layout.media_route_list_item_collapse_group
+    };
+
     MediaRouter mRouter;
     private int mRouteTypes;
 
+    private LayoutInflater mInflater;
     private LauncherListener mLauncherListener;
     private View.OnClickListener mExtendedSettingsListener;
     private RouteAdapter mAdapter;
+    private GroupAdapter mGroupAdapter;
     private ListView mListView;
 
+    final RouteComparator mComparator = new RouteComparator();
+
     public MediaRouteChooserDialogFragment() {
         setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog);
     }
@@ -77,10 +99,15 @@
         if (mLauncherListener != null) {
             mLauncherListener.onDetached(this);
         }
+        if (mGroupAdapter != null) {
+            mRouter.removeCallback(mGroupAdapter.mCallback);
+            mGroupAdapter = null;
+        }
         if (mAdapter != null) {
-            mRouter.removeCallback(mAdapter);
+            mRouter.removeCallback(mAdapter.mCallback);
             mAdapter = null;
         }
+        mInflater = null;
         mRouter = null;
     }
 
@@ -102,6 +129,7 @@
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
+        mInflater = inflater;
         final View layout = inflater.inflate(R.layout.media_route_chooser_layout, container, false);
         final View extendedSettingsButton = layout.findViewById(R.id.extended_settings);
 
@@ -112,34 +140,81 @@
 
         final ListView list = (ListView) layout.findViewById(R.id.list);
         list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
-        list.setAdapter(mAdapter = new RouteAdapter(inflater));
+        list.setItemsCanFocus(true);
+        list.setAdapter(mAdapter = new RouteAdapter());
         list.setItemChecked(mAdapter.getSelectedRoutePosition(), true);
         list.setOnItemClickListener(mAdapter);
 
         mListView = list;
-        mRouter.addCallback(mRouteTypes, mAdapter);
+        mRouter.addCallback(mRouteTypes, mAdapter.mCallback);
 
         return layout;
     }
 
-    private static final int[] ITEM_LAYOUTS = new int[] {
-        R.layout.media_route_list_item_top_header,
-        R.layout.media_route_list_item_section_header,
-        R.layout.media_route_list_item
-    };
+    void onExpandGroup(RouteGroup info) {
+        mGroupAdapter = new GroupAdapter(info);
+        mRouter.addCallback(mRouteTypes, mGroupAdapter.mCallback);
+        mListView.setAdapter(mGroupAdapter);
+        mListView.setOnItemClickListener(mGroupAdapter);
+        mListView.setItemsCanFocus(false);
+        mListView.clearChoices();
+        mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+        mGroupAdapter.initCheckedItems();
 
-    private class RouteAdapter extends BaseAdapter implements MediaRouter.Callback,
-            ListView.OnItemClickListener {
+        getDialog().setCanceledOnTouchOutside(false);
+    }
+
+    void onDoneGrouping() {
+        mListView.setAdapter(mAdapter);
+        mListView.setOnItemClickListener(mAdapter);
+        mListView.setItemsCanFocus(true);
+        mListView.clearChoices();
+        mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+        mListView.setItemChecked(mAdapter.getSelectedRoutePosition(), true);
+
+        mRouter.removeCallback(mGroupAdapter.mCallback);
+        mGroupAdapter = null;
+
+        getDialog().setCanceledOnTouchOutside(true);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return new RouteChooserDialog(getActivity(), getTheme());
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        if (mListView != null) {
+            if (mGroupAdapter != null) {
+                mGroupAdapter.initCheckedItems();
+            } else {
+                mListView.setItemChecked(mAdapter.getSelectedRoutePosition(), true);
+            }
+        }
+    }
+
+    private static class ViewHolder {
+        public TextView text1;
+        public TextView text2;
+        public ImageView icon;
+        public ImageButton expandGroupButton;
+        public RouteAdapter.ExpandGroupListener expandGroupListener;
+        public int position;
+    }
+
+    private class RouteAdapter extends BaseAdapter implements ListView.OnItemClickListener {
         private static final int VIEW_TOP_HEADER = 0;
         private static final int VIEW_SECTION_HEADER = 1;
         private static final int VIEW_ROUTE = 2;
 
         private int mSelectedItemPosition;
         private final ArrayList<Object> mItems = new ArrayList<Object>();
-        private final LayoutInflater mInflater;
+        final MediaRouterCallback mCallback = new MediaRouterCallback();
 
-        RouteAdapter(LayoutInflater inflater) {
-            mInflater = inflater;
+        RouteAdapter() {
             update();
         }
 
@@ -222,11 +297,29 @@
             if (convertView == null) {
                 convertView = mInflater.inflate(ITEM_LAYOUTS[viewType], parent, false);
                 holder = new ViewHolder();
+                holder.position = position;
                 holder.text1 = (TextView) convertView.findViewById(R.id.text1);
                 holder.text2 = (TextView) convertView.findViewById(R.id.text2);
+                holder.icon = (ImageView) convertView.findViewById(R.id.icon);
+                holder.expandGroupButton = (ImageButton) convertView.findViewById(
+                        R.id.expand_button);
+                if (holder.expandGroupButton != null) {
+                    holder.expandGroupListener = new ExpandGroupListener();
+                    holder.expandGroupButton.setOnClickListener(holder.expandGroupListener);
+                }
+
+                final View fview = convertView;
+                final ListView list = (ListView) parent;
+                final ViewHolder fholder = holder;
+                convertView.setOnClickListener(new View.OnClickListener() {
+                    @Override public void onClick(View v) {
+                        list.performItemClick(fview, fholder.position, 0);
+                    }
+                });
                 convertView.setTag(holder);
             } else {
                 holder = (ViewHolder) convertView.getTag();
+                holder.position = position;
             }
 
             if (viewType == VIEW_ROUTE) {
@@ -240,7 +333,7 @@
 
         void bindItemView(int position, ViewHolder holder) {
             RouteInfo info = (RouteInfo) mItems.get(position);
-            holder.text1.setText(info.getName());
+            holder.text1.setText(info.getName(getActivity()));
             final CharSequence status = info.getStatus();
             if (TextUtils.isEmpty(status)) {
                 holder.text2.setVisibility(View.GONE);
@@ -248,11 +341,29 @@
                 holder.text2.setVisibility(View.VISIBLE);
                 holder.text2.setText(status);
             }
+            Drawable icon = info.getIconDrawable();
+            if (icon != null) {
+                // Make sure we have a fresh drawable where it doesn't matter if we mutate it
+                icon = icon.getConstantState().newDrawable(getResources());
+            }
+            holder.icon.setImageDrawable(icon);
+            holder.icon.setVisibility(icon != null ? View.VISIBLE : View.GONE);
+
+            RouteCategory cat = info.getCategory();
+            boolean canGroup = false;
+            if (cat.isGroupable()) {
+                final RouteGroup group = (RouteGroup) info;
+                canGroup = group.getRouteCount() > 1 ||
+                        getItemViewType(position - 1) == VIEW_ROUTE ||
+                        (position < getCount() - 1 && getItemViewType(position + 1) == VIEW_ROUTE);
+            }
+            holder.expandGroupButton.setVisibility(canGroup ? View.VISIBLE : View.GONE);
+            holder.expandGroupListener.position = position;
         }
 
         void bindHeaderView(int position, ViewHolder holder) {
             RouteCategory cat = (RouteCategory) mItems.get(position);
-            holder.text1.setText(cat.getName());
+            holder.text1.setText(cat.getName(getActivity()));
         }
 
         public int getSelectedRoutePosition() {
@@ -260,42 +371,6 @@
         }
 
         @Override
-        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
-            update();
-        }
-
-        @Override
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
-            update();
-        }
-
-        @Override
-        public void onRouteAdded(MediaRouter router, RouteInfo info) {
-            update();
-        }
-
-        @Override
-        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
-            update();
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, RouteInfo info) {
-            notifyDataSetChanged();
-        }
-
-        @Override
-        public void onRouteGrouped(MediaRouter router, RouteInfo info,
-                RouteGroup group, int index) {
-            update();
-        }
-
-        @Override
-        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
-            update();
-        }
-
-        @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
             ListView lv = (ListView) parent;
             final Object item = getItem(lv.getCheckedItemPosition());
@@ -303,39 +378,320 @@
                 // Oops. Stale event running around? Skip it.
                 return;
             }
-            mRouter.selectRoute(mRouteTypes, (RouteInfo) item);
+            mRouter.selectRouteInt(mRouteTypes, (RouteInfo) item);
             dismiss();
         }
+
+        class ExpandGroupListener implements View.OnClickListener {
+            int position;
+
+            @Override
+            public void onClick(View v) {
+                // Assumption: this is only available for the user to click if we're presenting
+                // a groupable category, where every top-level route in the category is a group.
+                onExpandGroup((RouteGroup) getItem(position));
+            }
+        }
+
+        class MediaRouterCallback extends MediaRouter.Callback {
+            @Override
+            public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+                update();
+            }
+
+            @Override
+            public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+                update();
+            }
+
+            @Override
+            public void onRouteAdded(MediaRouter router, RouteInfo info) {
+                update();
+            }
+
+            @Override
+            public void onRouteRemoved(MediaRouter router, RouteInfo info) {
+                update();
+            }
+
+            @Override
+            public void onRouteChanged(MediaRouter router, RouteInfo info) {
+                notifyDataSetChanged();
+            }
+
+            @Override
+            public void onRouteGrouped(MediaRouter router, RouteInfo info,
+                    RouteGroup group, int index) {
+                update();
+            }
+
+            @Override
+            public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
+                update();
+            }
+        }
     }
 
-    private static class ViewHolder {
-        public TextView text1;
-        public TextView text2;
-    }
+    private class GroupAdapter extends BaseAdapter implements ListView.OnItemClickListener {
+        private static final int VIEW_HEADER = 0;
+        private static final int VIEW_ROUTE = 1;
+        private static final int VIEW_DONE = 2;
 
-    private class GroupAdapter extends BaseAdapter {
+        private RouteGroup mPrimary;
+        private RouteCategory mCategory;
+        private final ArrayList<RouteInfo> mTempList = new ArrayList<RouteInfo>();
+        private final ArrayList<RouteInfo> mFlatRoutes = new ArrayList<RouteInfo>();
+        private boolean mIgnoreUpdates;
+        
+        final MediaRouterCallback mCallback = new MediaRouterCallback();
+
+        public GroupAdapter(RouteGroup primary) {
+            mPrimary = primary;
+            mCategory = primary.getCategory();
+            update();
+        }
+
         @Override
         public int getCount() {
-            // TODO Auto-generated method stub
-            return 0;
+            return mFlatRoutes.size() + 2;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 3;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            if (position == 0) {
+                return VIEW_HEADER;
+            } else if (position == getCount() - 1) {
+                return VIEW_DONE;
+            }
+            return VIEW_ROUTE;
+        }
+
+        void update() {
+            if (mIgnoreUpdates) return;
+            mFlatRoutes.clear();
+            mCategory.getRoutes(mTempList);
+
+            // Unpack groups and flatten for presentation
+            final int topCount = mTempList.size();
+            for (int i = 0; i < topCount; i++) {
+                final RouteInfo route = mTempList.get(i);
+                final RouteGroup group = route.getGroup();
+                if (group == route) {
+                    // This is a group, unpack it.
+                    final int groupCount = group.getRouteCount();
+                    for (int j = 0; j < groupCount; j++) {
+                        final RouteInfo innerRoute = group.getRouteAt(j);
+                        mFlatRoutes.add(innerRoute);
+                    }
+                } else {
+                    mFlatRoutes.add(route);
+                }
+            }
+            mTempList.clear();
+
+            // Sort by name. This will keep the route positions relatively stable even though they
+            // will be repeatedly added and removed.
+            Collections.sort(mFlatRoutes, mComparator);
+            notifyDataSetChanged();
+        }
+
+        void initCheckedItems() {
+            if (mIgnoreUpdates) return;
+            mListView.clearChoices();
+            int count = mFlatRoutes.size();
+            for (int i = 0; i < count; i++){
+                final RouteInfo route = mFlatRoutes.get(i);
+                if (route.getGroup() == mPrimary) {
+                    mListView.setItemChecked(i + 1, true);
+                }
+            }
         }
 
         @Override
         public Object getItem(int position) {
-            // TODO Auto-generated method stub
-            return null;
+            if (position == 0) {
+                return mCategory;
+            } else if (position == getCount() - 1) {
+                return null; // Done
+            }
+            return mFlatRoutes.get(position - 1);
         }
 
         @Override
         public long getItemId(int position) {
-            // TODO Auto-generated method stub
-            return 0;
+            return position;
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return position > 0;
         }
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
-            // TODO Auto-generated method stub
-            return null;
+            final int viewType = getItemViewType(position);
+
+            ViewHolder holder;
+            if (convertView == null) {
+                convertView = mInflater.inflate(GROUP_ITEM_LAYOUTS[viewType], parent, false);
+                holder = new ViewHolder();
+                holder.position = position;
+                holder.text1 = (TextView) convertView.findViewById(R.id.text1);
+                holder.text2 = (TextView) convertView.findViewById(R.id.text2);
+                holder.icon = (ImageView) convertView.findViewById(R.id.icon);
+                convertView.setTag(holder);
+            } else {
+                holder = (ViewHolder) convertView.getTag();
+                holder.position = position;
+            }
+
+            if (viewType == VIEW_ROUTE) {
+                bindItemView(position, holder);
+            } else if (viewType == VIEW_HEADER) {
+                bindHeaderView(position, holder);
+            }
+
+            return convertView;
+        }
+
+        void bindItemView(int position, ViewHolder holder) {
+            RouteInfo info = (RouteInfo) getItem(position);
+            holder.text1.setText(info.getName(getActivity()));
+            final CharSequence status = info.getStatus();
+            if (TextUtils.isEmpty(status)) {
+                holder.text2.setVisibility(View.GONE);
+            } else {
+                holder.text2.setVisibility(View.VISIBLE);
+                holder.text2.setText(status);
+            }
+        }
+
+        void bindHeaderView(int position, ViewHolder holder) {
+            holder.text1.setText(mCategory.getName(getActivity()));
+        }
+
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            if (getItemViewType(position) == VIEW_DONE) {
+                onDoneGrouping();
+                return;
+            }
+
+            final ListView lv = (ListView) parent;
+            final RouteInfo route = mFlatRoutes.get(position - 1);
+            final boolean checked = lv.isItemChecked(position);
+
+            mIgnoreUpdates = true;
+            RouteGroup oldGroup = route.getGroup();
+            if (checked && oldGroup != mPrimary) {
+                // Assumption: in a groupable category oldGroup will never be null.
+                oldGroup.removeRoute(route);
+
+                // If the group is now empty, remove the group too.
+                if (oldGroup.getRouteCount() == 0) {
+                    if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) {
+                        // Old group was selected but is now empty. Select the group
+                        // we're manipulating since that's where the last route went.
+                        mRouter.selectRouteInt(mRouteTypes, mPrimary);
+                    }
+                    mRouter.removeRouteInt(oldGroup);
+                }
+
+                mPrimary.addRoute(route);
+            } else if (!checked) {
+                if (mPrimary.getRouteCount() > 1) {
+                    mPrimary.removeRoute(route);
+
+                    // In a groupable category this will add the route into its own new group.
+                    mRouter.addRouteInt(route);
+                } else {
+                    // We're about to remove the last route.
+                    // Don't let this happen, as it would be silly.
+                    // Turn the checkmark back on again. Silly user!
+                    lv.setItemChecked(position, true);
+                }
+            }
+            mIgnoreUpdates = false;
+            update();
+            initCheckedItems();
+        }
+
+        class MediaRouterCallback extends MediaRouter.Callback {
+            @Override
+            public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+            }
+
+            @Override
+            public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+            }
+
+            @Override
+            public void onRouteAdded(MediaRouter router, RouteInfo info) {
+                update();
+                initCheckedItems();
+            }
+
+            @Override
+            public void onRouteRemoved(MediaRouter router, RouteInfo info) {
+                if (info == mPrimary) {
+                    // Can't keep grouping, clean it up.
+                    onDoneGrouping();
+                } else {
+                    update();
+                    initCheckedItems();
+                }
+            }
+
+            @Override
+            public void onRouteChanged(MediaRouter router, RouteInfo info) {
+                update();
+            }
+
+            @Override
+            public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
+                    int index) {
+                update();
+                initCheckedItems();
+            }
+
+            @Override
+            public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
+                update();
+                initCheckedItems();
+            }
+        }
+    }
+
+    class RouteComparator implements Comparator<RouteInfo> {
+        @Override
+        public int compare(RouteInfo lhs, RouteInfo rhs) {
+            return lhs.getName(getActivity()).toString()
+                    .compareTo(rhs.getName(getActivity()).toString());
+        }
+    }
+
+    class RouteChooserDialog extends Dialog {
+        public RouteChooserDialog(Context context, int theme) {
+            super(context, theme);
+        }
+
+        @Override
+        public void onBackPressed() {
+            if (mGroupAdapter != null) {
+                onDoneGrouping();
+            } else {
+                super.onBackPressed();
+            }
         }
     }
 }
diff --git a/core/java/com/android/internal/view/CheckableLinearLayout.java b/core/java/com/android/internal/view/CheckableLinearLayout.java
new file mode 100644
index 0000000..3fb7cec
--- /dev/null
+++ b/core/java/com/android/internal/view/CheckableLinearLayout.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+
+public class CheckableLinearLayout extends LinearLayout implements Checkable {
+    private CheckBox mCheckBox;
+
+    public CheckableLinearLayout(Context context) {
+        super(context);
+        // TODO Auto-generated constructor stub
+    }
+
+    public CheckableLinearLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        // TODO Auto-generated constructor stub
+    }
+
+    public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        // TODO Auto-generated constructor stub
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mCheckBox = (CheckBox) findViewById(R.id.check);
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        mCheckBox.setChecked(checked);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return mCheckBox.isChecked();
+    }
+
+    @Override
+    public void toggle() {
+        mCheckBox.toggle();
+    }
+}
diff --git a/core/java/com/android/internal/view/ImageButtonNoParentPress.java b/core/java/com/android/internal/view/ImageButtonNoParentPress.java
new file mode 100644
index 0000000..a6cfd86
--- /dev/null
+++ b/core/java/com/android/internal/view/ImageButtonNoParentPress.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+public class ImageButtonNoParentPress extends ImageButton {
+
+    public ImageButtonNoParentPress(Context context) {
+        super(context);
+    }
+
+    public ImageButtonNoParentPress(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ImageButtonNoParentPress(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public void setPressed(boolean pressed) {
+        // Normally parents propagate pressed state to their children.
+        // We don't want that to happen here; only press if our parent isn't.
+        super.setPressed(((ViewGroup) getParent()).isPressed() ? false : pressed);
+    }
+}
diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
index 5096be6..837b7b8 100644
--- a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
@@ -115,7 +115,6 @@
     private int mMaxTargetWidth;
 
     private float mOuterRadius = 0.0f;
-    private float mHitRadius = 0.0f;
     private float mSnapMargin = 0.0f;
     private boolean mDragging;
     private int mNewTargetResources;
@@ -211,7 +210,6 @@
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GlowPadView);
         mInnerRadius = a.getDimension(R.styleable.GlowPadView_innerRadius, mInnerRadius);
         mOuterRadius = a.getDimension(R.styleable.GlowPadView_outerRadius, mOuterRadius);
-        mHitRadius = a.getDimension(R.styleable.GlowPadView_hitRadius, mHitRadius);
         mSnapMargin = a.getDimension(R.styleable.GlowPadView_snapMargin, mSnapMargin);
         mVibrationDuration = a.getInt(R.styleable.GlowPadView_vibrationDuration,
                 mVibrationDuration);
@@ -280,7 +278,6 @@
 
     private void dump() {
         Log.v(TAG, "Outer Radius = " + mOuterRadius);
-        Log.v(TAG, "HitRadius = " + mHitRadius);
         Log.v(TAG, "SnapMargin = " + mSnapMargin);
         Log.v(TAG, "FeedbackCount = " + mFeedbackCount);
         Log.v(TAG, "VibrationDuration = " + mVibrationDuration);
@@ -799,7 +796,6 @@
         final int historySize = event.getHistorySize();
         ArrayList<TargetDrawable> targets = mTargetDrawables;
         int ntargets = targets.size();
-        final boolean singleTarget = ntargets == 1;
         float x = 0.0f;
         float y = 0.0f;
         for (int k = 0; k < historySize + 1; k++) {
@@ -812,31 +808,29 @@
             final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f;
             float limitX = tx * scale;
             float limitY = ty * scale;
+            double angleRad = Math.atan2(-ty, tx);
 
             if (!mDragging) {
                 trySwitchToFirstTouchState(eventX, eventY);
             }
 
             if (mDragging) {
-                if (singleTarget) {
-                    // Snap to outer ring if there's only one target
-                    float snapRadius = mOuterRadius - mSnapMargin;
-                    if (touchRadius > snapRadius) {
-                        activeTarget = 0;
-                    }
-                } else {
-                    // 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++) {
-                        TargetDrawable target = targets.get(i);
-                        float dx = limitX - target.getX();
-                        float dy = limitY - target.getY();
-                        float dist2 = dx*dx + dy*dy;
-                        if (target.isEnabled() && dist2 < hitRadius2 && dist2 < best) {
+                // For multiple targets, snap to the one that matches
+                final float snapRadius = mOuterRadius - mSnapMargin;
+                final float snapDistance2 = snapRadius * snapRadius;
+                // Find first target in range
+                for (int i = 0; i < ntargets; i++) {
+                    TargetDrawable target = targets.get(i);
+
+                    double targetMinRad = (i - 0.5) * 2 * Math.PI / ntargets;
+                    double targetMaxRad = (i + 0.5) * 2 * Math.PI / ntargets;
+                    if (target.isEnabled()) {
+                        boolean angleMatches =
+                            (angleRad > targetMinRad && angleRad <= targetMaxRad) ||
+                            (angleRad + 2 * Math.PI > targetMinRad &&
+                             angleRad + 2 * Math.PI <= targetMaxRad);
+                        if (angleMatches && (dist2(tx, ty) > snapDistance2)) {
                             activeTarget = i;
-                            best = dist2;
                         }
                     }
                 }
@@ -851,10 +845,7 @@
 
         if (activeTarget != -1) {
             switchToState(STATE_SNAP, x,y);
-            TargetDrawable target = targets.get(activeTarget);
-            final float newX = singleTarget ? x : target.getX();
-            final float newY = singleTarget ? y : target.getY();
-            updateGlowPosition(newX, newY);
+            updateGlowPosition(x, y);
         } else {
             switchToState(STATE_TRACKING, x, y);
             updateGlowPosition(x, y);
@@ -942,10 +933,6 @@
         if (mOuterRadius == 0.0f) {
             mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f;
         }
-        if (mHitRadius == 0.0f) {
-            // Use the radius of inscribed circle of the first target.
-            mHitRadius = mTargetDrawables.get(0).getWidth() / 2.0f;
-        }
         if (mSnapMargin == 0.0f) {
             mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                     SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics());
diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
index afeac00..10804c0 100644
--- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
@@ -119,7 +119,6 @@
     private int mMaxTargetWidth;
 
     private float mOuterRadius = 0.0f;
-    private float mHitRadius = 0.0f;
     private float mSnapMargin = 0.0f;
     private boolean mDragging;
     private int mNewTargetResources;
@@ -213,7 +212,6 @@
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiWaveView);
         mOuterRadius = a.getDimension(R.styleable.MultiWaveView_outerRadius, mOuterRadius);
-        mHitRadius = a.getDimension(R.styleable.MultiWaveView_hitRadius, mHitRadius);
         mSnapMargin = a.getDimension(R.styleable.MultiWaveView_snapMargin, mSnapMargin);
         mVibrationDuration = a.getInt(R.styleable.MultiWaveView_vibrationDuration,
                 mVibrationDuration);
@@ -277,7 +275,6 @@
 
     private void dump() {
         Log.v(TAG, "Outer Radius = " + mOuterRadius);
-        Log.v(TAG, "HitRadius = " + mHitRadius);
         Log.v(TAG, "SnapMargin = " + mSnapMargin);
         Log.v(TAG, "FeedbackCount = " + mFeedbackCount);
         Log.v(TAG, "VibrationDuration = " + mVibrationDuration);
@@ -823,7 +820,6 @@
         final int historySize = event.getHistorySize();
         ArrayList<TargetDrawable> targets = mTargetDrawables;
         int ntargets = targets.size();
-        final boolean singleTarget = ntargets == 1;
         float x = 0.0f;
         float y = 0.0f;
         for (int k = 0; k < historySize + 1; k++) {
@@ -836,31 +832,29 @@
             final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f;
             float limitX = tx * scale;
             float limitY = ty * scale;
+            double angleRad = Math.atan2(-ty, tx);
 
             if (!mDragging) {
                 trySwitchToFirstTouchState(eventX, eventY);
             }
 
             if (mDragging) {
-                if (singleTarget) {
-                    // Snap to outer ring if there's only one target
-                    float snapRadius = mOuterRadius - mSnapMargin;
-                    if (touchRadius > snapRadius) {
-                        activeTarget = 0;
-                    }
-                } else {
-                    // 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++) {
-                        TargetDrawable target = targets.get(i);
-                        float dx = limitX - target.getX();
-                        float dy = limitY - target.getY();
-                        float dist2 = dx*dx + dy*dy;
-                        if (target.isEnabled() && dist2 < hitRadius2 && dist2 < best) {
+                // For multiple targets, snap to the one that matches
+                final float snapRadius = mOuterRadius - mSnapMargin;
+                final float snapDistance2 = snapRadius * snapRadius;
+                // Find first target in range
+                for (int i = 0; i < ntargets; i++) {
+                    TargetDrawable target = targets.get(i);
+
+                    double targetMinRad = (i - 0.5) * 2 * Math.PI / ntargets;
+                    double targetMaxRad = (i + 0.5) * 2 * Math.PI / ntargets;
+                    if (target.isEnabled()) {
+                        boolean angleMatches =
+                            (angleRad > targetMinRad && angleRad <= targetMaxRad) ||
+                            (angleRad + 2 * Math.PI > targetMinRad &&
+                             angleRad + 2 * Math.PI <= targetMaxRad);
+                        if (angleMatches && (dist2(tx, ty) > snapDistance2)) {
                             activeTarget = i;
-                            best = dist2;
                         }
                     }
                 }
@@ -875,10 +869,7 @@
 
         if (activeTarget != -1) {
             switchToState(STATE_SNAP, x,y);
-            TargetDrawable target = targets.get(activeTarget);
-            final float newX = singleTarget ? x : target.getX();
-            final float newY = singleTarget ? y : target.getY();
-            moveHandleTo(newX, newY, false);
+            moveHandleTo(x, y, false);
         } else {
             switchToState(STATE_TRACKING, x, y);
             moveHandleTo(x, y, false);
@@ -972,10 +963,6 @@
         if (mOuterRadius == 0.0f) {
             mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f;
         }
-        if (mHitRadius == 0.0f) {
-            // Use the radius of inscribed circle of the first target.
-            mHitRadius = mTargetDrawables.get(0).getWidth() / 2.0f;
-        }
         if (mSnapMargin == 0.0f) {
             mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                     SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics());
diff --git a/core/jni/android_bluetooth_HeadsetBase.cpp b/core/jni/android_bluetooth_HeadsetBase.cpp
index 0df211a..34447ef 100644
--- a/core/jni/android_bluetooth_HeadsetBase.cpp
+++ b/core/jni/android_bluetooth_HeadsetBase.cpp
@@ -115,7 +115,7 @@
     pfd.fd = fd;
     pfd.events = POLLIN;
     *err = errno = 0;
-    int ret = poll(&pfd, 1, timeout_ms);
+    int ret = TEMP_FAILURE_RETRY(poll(&pfd, 1, timeout_ms));
     if (ret < 0) {
         ALOGE("poll() error\n");
         *err = errno;
@@ -136,7 +136,7 @@
     while ((int)(bufit - buf) < (len - 1))
     {
         errno = 0;
-        int rc = read(fd, bufit, 1);
+        int rc = TEMP_FAILURE_RETRY(read(fd, bufit, 1));
 
         if (!rc)
             break;
@@ -427,7 +427,7 @@
             {
                 char ch;
                 errno = 0;
-                int nr = read(nat->rfcomm_sock, &ch, 1);
+                int nr = TEMP_FAILURE_RETRY(read(nat->rfcomm_sock, &ch, 1));
                 /* It should be that nr != 1 because we just opened a socket
                    and we haven't sent anything over it for the other side to
                    respond... but one can't be paranoid enough.
diff --git a/core/res/res/drawable-hdpi/ic_media_group_collapse.png b/core/res/res/drawable-hdpi/ic_media_group_collapse.png
new file mode 100644
index 0000000..89abf2c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_media_group_collapse.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_group_expand.png b/core/res/res/drawable-hdpi/ic_media_group_expand.png
new file mode 100644
index 0000000..d9470b2
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_media_group_expand.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_group_collapse.png b/core/res/res/drawable-mdpi/ic_media_group_collapse.png
new file mode 100644
index 0000000..34454ac
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_media_group_collapse.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_group_expand.png b/core/res/res/drawable-mdpi/ic_media_group_expand.png
new file mode 100644
index 0000000..8ce5a44
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_media_group_expand.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png
new file mode 100644
index 0000000..2fb7428
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_group_expand.png b/core/res/res/drawable-xhdpi/ic_media_group_expand.png
new file mode 100644
index 0000000..5755b9d
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_media_group_expand.png
Binary files differ
diff --git a/core/res/res/drawable/item_background_activated_holo_dark.xml b/core/res/res/drawable/item_background_activated_holo_dark.xml
new file mode 100644
index 0000000..9cbf6ab
--- /dev/null
+++ b/core/res/res/drawable/item_background_activated_holo_dark.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
+    <item android:state_focused="true"  android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/list_selector_disabled_holo_light" />
+    <item android:state_focused="true"  android:state_enabled="false"                              android:drawable="@drawable/list_selector_disabled_holo_light" />
+    <item android:state_focused="true"                                android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_holo_light" />
+    <item android:state_focused="false"                               android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_holo_light" />
+    <item android:state_focused="true"                                                             android:drawable="@drawable/list_focused_holo" />
+    <item android:state_activated="true" android:drawable="@android:drawable/list_activated_holo" />
+    <item                                                                                          android:drawable="@color/transparent" />
+</selector>
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml
index cd9c913..356e7cf 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml
@@ -98,7 +98,6 @@
             android:outerRadius="@dimen/glowpadview_target_placement_radius"
             android:innerRadius="@dimen/glowpadview_inner_radius"
             android:snapMargin="@dimen/glowpadview_snap_margin"
-            android:hitRadius="@dimen/glowpadview_hit_radius"
             android:feedbackCount="1"
             android:vibrationDuration="20"
             android:glowRadius="@dimen/glowpadview_glow_radius"
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml
index 32ca602..cb1cb21 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml
@@ -98,7 +98,6 @@
             android:outerRadius="@dimen/glowpadview_target_placement_radius"
             android:innerRadius="@dimen/glowpadview_inner_radius"
             android:snapMargin="@dimen/glowpadview_snap_margin"
-            android:hitRadius="@dimen/glowpadview_hit_radius"
             android:feedbackCount="1"
             android:vibrationDuration="20"
             android:glowRadius="@dimen/glowpadview_glow_radius"
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
index 4e646a6..91f65e9 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -139,7 +139,6 @@
             android:outerRadius="@dimen/glowpadview_target_placement_radius"
             android:innerRadius="@dimen/glowpadview_inner_radius"
             android:snapMargin="@dimen/glowpadview_snap_margin"
-            android:hitRadius="@dimen/glowpadview_hit_radius"
             android:feedbackCount="1"
             android:vibrationDuration="20"
             android:glowRadius="@dimen/glowpadview_glow_radius"
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
index 5a357fb..8b9d8e4 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -144,7 +144,6 @@
         android:outerRadius="@dimen/glowpadview_target_placement_radius"
         android:innerRadius="@dimen/glowpadview_inner_radius"
         android:snapMargin="@dimen/glowpadview_snap_margin"
-        android:hitRadius="@dimen/glowpadview_hit_radius"
         android:feedbackCount="1"
         android:vibrationDuration="20"
         android:glowRadius="@dimen/glowpadview_glow_radius"
diff --git a/core/res/res/layout/media_route_chooser_layout.xml b/core/res/res/layout/media_route_chooser_layout.xml
index 320d6de..731c0d0 100644
--- a/core/res/res/layout/media_route_chooser_layout.xml
+++ b/core/res/res/layout/media_route_chooser_layout.xml
@@ -45,10 +45,4 @@
     <ListView android:id="@id/list"
               android:layout_width="match_parent"
               android:layout_height="wrap_content" />
-    <Button android:id="@+id/done"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="?android:attr/borderlessButtonStyle"
-            android:text="@string/media_route_chooser_grouping_done"
-            android:visibility="gone" />
 </LinearLayout>
diff --git a/core/res/res/layout/media_route_list_item.xml b/core/res/res/layout/media_route_list_item.xml
index d457c6c..ba1aaf2 100644
--- a/core/res/res/layout/media_route_list_item.xml
+++ b/core/res/res/layout/media_route_list_item.xml
@@ -17,7 +17,7 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:layout_height="?android:attr/listPreferredItemHeight"
-              android:background="?android:attr/activatedBackgroundIndicator"
+              android:background="@drawable/item_background_activated_holo_dark"
               android:gravity="center_vertical">
 
     <ImageView android:layout_width="56dp"
@@ -37,20 +37,25 @@
         <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" />
 
         <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" />
     </LinearLayout>
 
-    <!-- TODO Make this not glow when pressed from above, and give it a divider. -->
-    <ImageButton android:layout_width="56dp"
-                 android:layout_height="56dp"
-                 android:id="@+id/group_button"
-                 android:background="?android:attr/selectableItemBackground"
-                 android:scaleType="center"
-                 android:visibility="gone" />
+    <com.android.internal.view.ImageButtonNoParentPress
+        android:layout_width="56dp"
+        android:layout_height="56dp"
+        android:id="@+id/expand_button"
+        android:background="?android:attr/selectableItemBackground"
+        android:src="@drawable/ic_media_group_expand"
+        android:scaleType="center"
+        android:visibility="gone" />
 
 </LinearLayout>
diff --git a/core/res/res/layout/media_route_list_item_checkable.xml b/core/res/res/layout/media_route_list_item_checkable.xml
new file mode 100644
index 0000000..f6ba09e
--- /dev/null
+++ b/core/res/res/layout/media_route_list_item_checkable.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.internal.view.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="?android:attr/listPreferredItemHeight"
+              android:gravity="center_vertical">
+
+    <ImageView android:layout_width="56dp"
+               android:layout_height="56dp"
+               android:scaleType="center"
+               android:id="@+id/icon"
+               android:visibility="gone" />
+
+    <LinearLayout android:layout_width="0dp"
+                  android:layout_height="match_parent"
+                  android:layout_weight="1"
+                  android:orientation="vertical"
+                  android:gravity="left|center_vertical"
+                  android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+                  android:paddingRight="?android:attr/listPreferredItemPaddingRight">
+
+        <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" />
+
+        <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" />
+    </LinearLayout>
+
+    <CheckBox
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="16dp"
+        android:id="@+id/check"
+        android:focusable="false"
+        android:clickable="false" />
+
+</com.android.internal.view.CheckableLinearLayout>
diff --git a/core/res/res/layout/media_route_list_item_collapse_group.xml b/core/res/res/layout/media_route_list_item_collapse_group.xml
new file mode 100644
index 0000000..3f4b1c0
--- /dev/null
+++ b/core/res/res/layout/media_route_list_item_collapse_group.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="?android:attr/listPreferredItemHeightSmall"
+              android:background="#19ffffff"
+              android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+              android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+              android:gravity="center_vertical">
+
+    <TextView android:layout_width="0dp"
+              android:layout_height="wrap_content"
+              android:layout_weight="1"
+              android:singleLine="true"
+              android:ellipsize="marquee"
+              android:text="@string/media_route_chooser_grouping_done"
+              android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_media_group_collapse"
+        android:scaleType="center" />
+
+</LinearLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index aaa6778..a19bc2b 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dokluidsprekers"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI-klank"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Stelsel"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 510222d..d2cb8bc 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"የትከል ድምፅ ማጉያዎች"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI ድምጽ"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"ስርዓት"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 1e8b309..aea32a8 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"مكبرات صوت للإرساء"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"صوت HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"النظام"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 23631a8..bfe0e27 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Дынамікі станцыi"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI-аўдыёвыхад"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Сістэма"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 32911f9..a972258 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Докинг станц.: Високогов."</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI аудио"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Система"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index bc8a9c1..8f8fb1d 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -361,15 +361,15 @@
     <string name="permlab_writeProfile" msgid="907793628777397643">"modificació targeta contacte"</string>
     <string name="permdesc_writeProfile" product="default" msgid="5552084294598465899">"Permet que l\'aplicació pugui canviar o afegir informació del perfil personal emmagatzemada al dispositiu, com ara el teu nom i la teva informació de contacte. Això significa que l\'aplicació et pot identificar i enviar la informació del teu perfil a altres persones."</string>
     <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"llegeix el teu tauler d\'activitat social"</string>
-    <string name="permdesc_readSocialStream" product="default" msgid="4255706027172050872">"Permet que l\'aplicació accedeixi i sincronitzi actualitzacions socials teves i dels teus amics. Vés amb compte en compartir informació: això permet que l\'aplicació llegeix comunicacions entre tu i els teus amics a les xarxes socials, independentment de la confidencialitat. Nota: És possible que aquest permís no s\'apliqui a totes les xarxes socials."</string>
+    <string name="permdesc_readSocialStream" product="default" msgid="4255706027172050872">"Permet que l\'aplicació accedeixi i sincronitzi actualitzacions socials teves i dels teus amics. Vés amb compte en compartir informació: això permet que l\'aplicació llegeixi comunicacions entre tu i els teus amics a les xarxes socials, independentment de la confidencialitat. Nota: És possible que aquest permís no s\'apliqui a totes les xarxes socials."</string>
     <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"escriu al tauler d\'activitat social"</string>
     <string name="permdesc_writeSocialStream" product="default" msgid="3086557552204114849">"Permet que l\'aplicació mostri actualitzacions socials dels teus amics. Vés amb compte en compartir informació: això permet que l\'aplicació produeixi missatges que pot semblar que provinguin d\'un amic. Nota: És possible que aquest permís no s\'apliqui a totes les xarxes socials."</string>
     <string name="permlab_readCalendar" msgid="5972727560257612398">"llegeix els esdeveniments del calendari més informació confidencial"</string>
     <string name="permdesc_readCalendar" product="tablet" msgid="4216462049057658723">"Permet que l\'aplicació llegeixi tots els esdeveniments del calendari emmagatzemats a la tauleta, inclosos els dels amics o dels companys de feina. Aquesta acció pot permetre que l\'aplicació comparteixi o desi les dades del teu calendari, sense tenir en compte la confidencialitat."</string>
     <string name="permdesc_readCalendar" product="default" msgid="7434548682470851583">"Permet que l\'aplicació llegeixi tots els esdeveniments del calendari emmagatzemats al telèfon, inclosos els dels amics o dels companys de feina. Aquesta acció pot permetre que l\'aplicació comparteixi o desi les dades del teu calendari, sense tenir en compte la confidencialitat."</string>
     <string name="permlab_writeCalendar" msgid="8438874755193825647">"afegeix o modifica els esdeveniments del calendari i envia correus electrònics als clients sense el coneixement dels propietaris"</string>
-    <string name="permdesc_writeCalendar" product="tablet" msgid="6679035520113668528">"Permet que l\'aplicació afegeixi, elimini o canviï esdeveniments que pots modificar a la tauleta, inclosos d\'amics o de companys de feina. Aquesta acció pot permetre que l\'aplicació enviï missatges que sembli que provinguin dels propietaris del calendari o que modifiqui esdeveniments sense el coneixement dels propietaris."</string>
-    <string name="permdesc_writeCalendar" product="default" msgid="2324469496327249376">"Permet que l\'aplicació afegeixi, elimini o canviï esdeveniments que pots modificar al telèfon, inclosos d\'amics o de companys de feina. Aquesta acció pot permetre que l\'aplicació enviï missatges que sembli que provinguin dels propietaris del calendari o que modifiqui esdeveniments sense el coneixement dels propietaris."</string>
+    <string name="permdesc_writeCalendar" product="tablet" msgid="6679035520113668528">"Permet que l\'aplicació afegeixi, elimini o canviï esdeveniments que pots modificar a la tauleta, inclosos els d\'amics o de companys de feina. Aquesta acció pot permetre que l\'aplicació enviï missatges que sembli que provinguin dels propietaris del calendari o que modifiqui esdeveniments sense el coneixement dels propietaris."</string>
+    <string name="permdesc_writeCalendar" product="default" msgid="2324469496327249376">"Permet que l\'aplicació afegeixi, elimini o canviï esdeveniments que pots modificar al telèfon, inclosos els d\'amics o de companys de feina. Aquesta acció pot permetre que l\'aplicació enviï missatges que sembli que provinguin dels propietaris del calendari o que modifiqui esdeveniments sense el coneixement dels propietaris."</string>
     <string name="permlab_accessMockLocation" msgid="8688334974036823330">"crear orígens d\'ubicacions fictícies per fer proves"</string>
     <string name="permdesc_accessMockLocation" msgid="5808711039482051824">"Crea fonts d\'ubicació fictícies per provar o per instal·lar un proveïdor d\'ubicació nou. Aquesta acció permet que l\'aplicació substitueixi la ubicació o l\'estat que retornen altres fonts d\'ubicació, com ara el GPS o altres proveïdors d\'ubicació."</string>
     <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"accedir a ordres del proveïdor d\'ubicació addicionals"</string>
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altaveus del connector"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Àudio HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index bb87fa6..35f0480 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Reproduktory doku"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Zvuk HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Systém"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index dc040e2..6bc5f9f 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dockstationens højttalere"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI-lyd"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 1556180..2bd623b 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dock-Lautsprecher"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI-Audio"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 1d4f378..9f50ca3 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Ηχεία βάσης σύνδεσης"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Ήχος HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Σύστημα"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 553928b..1386ad5 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dock speakers"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI audio"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 8e1582a..c58b08e 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altavoces del conector"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Audio HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index b6cb694..127abb5 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altavoces del conector"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Audio HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 6ab1665..6266353 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Doki kõlarid"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI heli"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Süsteem"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index a22dd19..fcfbc0f 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"بلندگوهای جایگاه اتصال"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"صدای HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"سیستم"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index c0fe3d5..5888acf 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -934,10 +934,8 @@
     <string name="deleteText" msgid="7070985395199629156">"poista"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Syöttötapa"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Tekstitoiminnot"</string>
-    <!-- no translation found for low_internal_storage_view_title (5576272496365684834) -->
-    <skip />
-    <!-- no translation found for low_internal_storage_view_text (6640505817617414371) -->
-    <skip />
+    <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Tallennustila loppumassa"</string>
+    <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Kaikki järjestelmätoiminnot eivät välttämättä toimi"</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
     <string name="cancel" msgid="6442560571259935130">"Peruuta"</string>
     <string name="yes" msgid="5362982303337969312">"OK"</string>
@@ -1290,4 +1288,5 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Telineen kaiuttimet"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI-ääni"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Järjestelmä"</string>
+    <string name="media_route_chooser_grouping_done" msgid="7966438307723317169">"Valmis"</string>
 </resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index f8bc9aa..99c7627 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Haut-parleurs de la station d\'accueil"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Audio HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Système"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 0551773..dbb3878 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"डॉक स्‍पीकर"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI ऑडियो"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"सिस्‍टम"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index fddc295..dbe54ea 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Zvučnici postolja"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI audio"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sustav"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index bdec648..79e3119 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dokkolóegység hangszórója"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI audió"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Rendszer"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 5b5280f..28cc099 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Pengeras suara dok"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Audio HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 54b8b39..bf7a168 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -934,10 +934,8 @@
     <string name="deleteText" msgid="7070985395199629156">"elimina"</string>
     <string name="inputMethod" msgid="1653630062304567879">"Metodo inserimento"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Azioni testo"</string>
-    <!-- no translation found for low_internal_storage_view_title (5576272496365684834) -->
-    <skip />
-    <!-- no translation found for low_internal_storage_view_text (6640505817617414371) -->
-    <skip />
+    <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Spazio di archiviazione in esaurimento"</string>
+    <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Alcune funzioni di sistema potrebbero non funzionare"</string>
     <string name="ok" msgid="5970060430562524910">"OK"</string>
     <string name="cancel" msgid="6442560571259935130">"Annulla"</string>
     <string name="yes" msgid="5362982303337969312">"OK"</string>
@@ -1290,4 +1288,5 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altoparlanti dock"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Audio HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
+    <string name="media_route_chooser_grouping_done" msgid="7966438307723317169">"Fine"</string>
 </resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 83ad2c2..880cc9d 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"רמקולים של מעגן"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"אודיו HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"מערכת"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 68166c3..eb3d96f 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ホルダーのスピーカー"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMIオーディオ"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"システム"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index db56251..d345c91 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"도크 스피커"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI 오디오"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"시스템"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 90f8c40..319aca7 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Doko garsiakalbiai"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI garsas"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 54bd957..8839657 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Doka skaļruņi"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI audio"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistēma"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 1f8a2ac..41f8521 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Pembesar suara dok"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Audio HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 6da4257..24d8497 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dokkhøyttalere"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI-lyd"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index e94a5fb..fb5e365 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dockluidsprekers"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI-audio"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Systeem"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 8c60669..8bd9b91 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Głośniki stacji dokującej"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Dźwięk przez HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 8c86fc6..1e2687d 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altif. estação ancoragem"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Áudio HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 525719f..75840cc 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Alto-falantes do dock"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Áudio HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index ad15a00..d5a4250 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -2024,4 +2024,6 @@
     <skip />
     <!-- no translation found for default_audio_route_category_name (3722811174003886946) -->
     <skip />
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 4e90357..ef732f8 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Difuz. dispozit. andocare"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Ieşire audio HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index e4b2962..8c280cc 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Динамики док-станции"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI-аудио"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Система"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 49c50bc..0ddf9ef 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Reproduktory doku"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Zvuk HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Systém"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 11a0f34..102e6f2 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Zvočniki stojala"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Zvok HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 08bbd34..7dd17fc 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Звучници базне станице"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI аудио"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Систем"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index fa4297c..c673628 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dockningsstationens högtalare"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI-ljud"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index ac53b00..ef9d19d 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Vipasa sauti vya gati"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Sauti ya HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Mfumo"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 9b19913..c4b31d5 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ลำโพงแท่นชาร์จ"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"เสียง HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"ระบบ"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index ae51a44..4ea1dbc 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Mga speaker ng dock"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI audio"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 26b5108..182e2cd 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Yuva hoparlörleri"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI ses"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 164766c..d861415 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Динаміки док-станції"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Аудіовихід HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Система"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 271e952..e80ac6a 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Loa đế"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Âm thanh HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Hệ thống"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 82c5bf1..6b1bfa6 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"基座扬声器"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI 音频"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"系统"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index a79cead..9decfa9 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"座架喇叭"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"HDMI 音訊"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"系統"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 812fc7c..48a7a8a 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1290,4 +1290,6 @@
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Izipika ze-Dock"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Umsindo we-HDMI"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Isistimu"</string>
+    <!-- no translation found for media_route_chooser_grouping_done (7966438307723317169) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 00077512..3a7253b 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5405,9 +5405,6 @@
         <!-- Outer radius of glow area. Target icons will be drawn on this circle. -->
         <attr name="outerRadius"/>
 
-        <!-- Size of target radius. Points within this distance of target center is a "hit". -->
-        <attr name="hitRadius"/>
-
         <!-- Radius of glow under finger. -->
         <attr name="glowRadius" format="dimension" />
 
@@ -5450,9 +5447,6 @@
         <!-- Outer radius of target circle. Icons will be drawn on this circle. -->
         <attr name="outerRadius" format="dimension" />
 
-        <!-- Size of target radius. Points within this distance of target center is a "hit". -->
-        <attr name="hitRadius" format="dimension" />
-
         <!-- Tactile feedback duration for actions. Set to '0' for no vibration. -->
         <attr name="vibrationDuration" format="integer"/>
 
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index ffbcb95..f30943a 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -79,11 +79,8 @@
     <!-- Default glow radius for GlowPadView -->
     <dimen name="glowpadview_glow_radius">75dip</dimen>
 
-    <!-- Default distance beyond which GlowPadView snaps to the target radius -->
-    <dimen name="glowpadview_snap_margin">20dip</dimen>
-
-    <!-- Default distance from each snap target that GlowPadView considers a "hit" -->
-    <dimen name="glowpadview_hit_radius">60dip</dimen>
+    <!-- Default distance beyond which GlowPadView snaps to the matching target -->
+    <dimen name="glowpadview_snap_margin">40dip</dimen>
 
     <!-- Default distance from each snap target that GlowPadView considers a "hit" -->
     <dimen name="glowpadview_inner_radius">15dip</dimen>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 0af8534..a6f2f49 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1166,13 +1166,15 @@
 
   <java-symbol type="attr" name="mediaRouteButtonStyle" />
   <java-symbol type="attr" name="externalRouteEnabledDrawable" />
-  <java-symbol type="layout" name="media_route_chooser_layout" />
   <java-symbol type="id" name="extended_settings" />
-  <java-symbol type="id" name="done" />
+  <java-symbol type="id" name="check" />
+  <java-symbol type="layout" name="media_route_chooser_layout" />
   <java-symbol type="layout" name="media_route_list_item_top_header" />
   <java-symbol type="layout" name="media_route_list_item_section_header" />
   <java-symbol type="layout" name="media_route_list_item" />
-  <java-symbol type="id" name="group_button" />
+  <java-symbol type="layout" name="media_route_list_item_checkable" />
+  <java-symbol type="layout" name="media_route_list_item_collapse_group" />
+  <java-symbol type="string" name="bluetooth_a2dp_audio_route_name" />
 
   <!-- From android.policy -->
   <java-symbol type="anim" name="app_starting_exit" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 967d700..da4d37a 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3571,6 +3571,9 @@
     <!-- Name of the default audio route category. [CHAR LIMIT=50] -->
     <string name="default_audio_route_category_name">System</string>
 
+    <!-- Default name of the bluetooth a2dp audio route. [CHAR LIMIT=50] -->
+    <string name="bluetooth_a2dp_audio_route_name">Bluetooth audio</string>
+
     <!-- "Done" button for MediaRouter chooser dialog when grouping routes. [CHAR LIMIT=NONE] -->
     <string name="media_route_chooser_grouping_done">Done</string>
 </resources>
diff --git a/data/videos/VideoPackage1.mk b/data/videos/VideoPackage1.mk
index 407a76e..0089657 100644
--- a/data/videos/VideoPackage1.mk
+++ b/data/videos/VideoPackage1.mk
@@ -23,6 +23,4 @@
         $(LOCAL_PATH)/AndroidInSpace.240p.mp4:$(TARGET_PATH)/AndroidInSpace.240p.mp4 \
         $(LOCAL_PATH)/AndroidInSpace.480p.lq.mp4:$(TARGET_PATH)/AndroidInSpace.480p.mp4 \
         $(LOCAL_PATH)/Sunset.240p.mp4:$(TARGET_PATH)/Sunset.240p.mp4 \
-        $(LOCAL_PATH)/Sunset.480p.lq.mp4:$(TARGET_PATH)/Sunset.480p.mp4 \
-        $(LOCAL_PATH)/Disco.240p.mp4:$(TARGET_PATH)/Disco.240p.mp4 \
-        $(LOCAL_PATH)/Disco.480p.lq.mp4:$(TARGET_PATH)/Disco.480p.mp4
+        $(LOCAL_PATH)/Sunset.480p.lq.mp4:$(TARGET_PATH)/Sunset.480p.mp4
diff --git a/data/videos/VideoPackage2.mk b/data/videos/VideoPackage2.mk
index c256a2d..b53fd9f 100644
--- a/data/videos/VideoPackage2.mk
+++ b/data/videos/VideoPackage2.mk
@@ -23,6 +23,4 @@
         $(LOCAL_PATH)/AndroidInSpace.240p.mp4:$(TARGET_PATH)/AndroidInSpace.240p.mp4 \
         $(LOCAL_PATH)/AndroidInSpace.480p.mq.mp4:$(TARGET_PATH)/AndroidInSpace.480p.mp4 \
         $(LOCAL_PATH)/Sunset.240p.mp4:$(TARGET_PATH)/Sunset.240p.mp4 \
-        $(LOCAL_PATH)/Sunset.480p.mq.mp4:$(TARGET_PATH)/Sunset.480p.mp4 \
-        $(LOCAL_PATH)/Disco.240p.mp4:$(TARGET_PATH)/Disco.240p.mp4 \
-        $(LOCAL_PATH)/Disco.480p.mq.mp4:$(TARGET_PATH)/Disco.480p.mp4
+        $(LOCAL_PATH)/Sunset.480p.mq.mp4:$(TARGET_PATH)/Sunset.480p.mp4
diff --git a/include/androidfw/KeycodeLabels.h b/include/androidfw/KeycodeLabels.h
index 1828062..538949d 100755
--- a/include/androidfw/KeycodeLabels.h
+++ b/include/androidfw/KeycodeLabels.h
@@ -243,6 +243,7 @@
     { "YEN", 216 },
     { "RO", 217 },
     { "KANA", 218 },
+    { "ASSIST", 219 },
 
     // NOTE: If you add a new keycode here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index b0657ff..b497f63 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -83,8 +83,7 @@
         // Called after sStatic is initialized
         void initDefaultRoutes() {
             mDefaultAudio = new RouteInfo(mSystemCategory);
-            mDefaultAudio.mName = mResources.getText(
-                    com.android.internal.R.string.default_audio_route_name);
+            mDefaultAudio.mNameResId = com.android.internal.R.string.default_audio_route_name;
             mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
             addRoute(mDefaultAudio);
         }
@@ -156,10 +155,15 @@
     }
 
     static void onHeadphonesPlugged(boolean headphonesPresent, String headphonesName) {
-        sStatic.mDefaultAudio.mName = headphonesPresent
-                ? headphonesName
-                : sStatic.mResources.getText(
-                        com.android.internal.R.string.default_audio_route_name);
+        if (headphonesPresent) {
+            sStatic.mDefaultAudio.mName = headphonesName;
+            sStatic.mDefaultAudio.mNameResId = 0;
+        } else {
+            sStatic.mDefaultAudio.mName = null;
+            sStatic.mDefaultAudio.mNameResId =
+                    com.android.internal.R.string.default_audio_route_name;
+        }
+
         dispatchRouteChanged(sStatic.mDefaultAudio);
     }
 
@@ -207,11 +211,25 @@
      * @param route Route to select
      */
     public void selectRoute(int types, RouteInfo route) {
+        // Applications shouldn't programmatically change anything but user routes.
+        types &= ROUTE_TYPE_USER;
+        selectRouteStatic(types, route);
+    }
+    
+    /**
+     * @hide internal use
+     */
+    public void selectRouteInt(int types, RouteInfo route) {
         selectRouteStatic(types, route);
     }
 
     static void selectRouteStatic(int types, RouteInfo route) {
         if (sStatic.mSelectedRoute == route) return;
+        if ((route.getSupportedTypes() & types) == 0) {
+            Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
+                    typesToString(route.getSupportedTypes()) + " into route types " +
+                    typesToString(types));
+        }
 
         if (sStatic.mSelectedRoute != null) {
             // TODO filter types properly
@@ -237,6 +255,13 @@
         addRoute(info);
     }
 
+    /**
+     * @hide Framework use only
+     */
+    public void addRouteInt(RouteInfo info) {
+        addRoute(info);
+    }
+
     static void addRoute(RouteInfo info) {
         final RouteCategory cat = info.getCategory();
         if (!sStatic.mCategories.contains(cat)) {
@@ -246,13 +271,10 @@
         if (cat.isGroupable() && !(info instanceof RouteGroup)) {
             // Enforce that any added route in a groupable category must be in a group.
             final RouteGroup group = new RouteGroup(info.getCategory());
+            group.addRoute(info);
             sStatic.mRoutes.add(group);
             dispatchRouteAdded(group);
 
-            final int at = group.getRouteCount();
-            group.addRoute(info);
-            dispatchRouteGrouped(info, group, at);
-
             info = group;
         } else {
             sStatic.mRoutes.add(info);
@@ -282,13 +304,22 @@
     public void clearUserRoutes() {
         for (int i = 0; i < sStatic.mRoutes.size(); i++) {
             final RouteInfo info = sStatic.mRoutes.get(i);
-            if (info instanceof UserRouteInfo) {
+            // TODO Right now, RouteGroups only ever contain user routes.
+            // The code below will need to change if this assumption does.
+            if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
                 removeRouteAt(i);
                 i--;
             }
         }
     }
 
+    /**
+     * @hide internal use only
+     */
+    public void removeRouteInt(RouteInfo info) {
+        removeRoute(info);
+    }
+
     static void removeRoute(RouteInfo info) {
         if (sStatic.mRoutes.remove(info)) {
             final RouteCategory removingCat = info.getCategory();
@@ -301,6 +332,11 @@
                     break;
                 }
             }
+            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);
+            }
             if (!found) {
                 sStatic.mCategories.remove(removingCat);
             }
@@ -321,6 +357,11 @@
                     break;
                 }
             }
+            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);
+            }
             if (!found) {
                 sStatic.mCategories.remove(removingCat);
             }
@@ -401,6 +442,17 @@
     public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) {
         return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
     }
+    
+    /**
+     * Create a new route category. Each route must belong to a category.
+     *
+     * @param nameResId Resource ID of the name of the new category
+     * @param isGroupable true if routes in this category may be grouped with one another
+     * @return the new RouteCategory
+     */
+    public RouteCategory createRouteCategory(int nameResId, boolean isGroupable) {
+        return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
+    }
 
     static void updateRoute(final RouteInfo info) {
         dispatchRouteChanged(info);
@@ -478,7 +530,7 @@
 
     static void onA2dpDeviceConnected() {
         final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
-        info.mName = "Bluetooth"; // TODO Fetch the real name of the device
+        info.mNameResId = com.android.internal.R.string.bluetooth_a2dp_audio_route_name;
         sStatic.mBluetoothA2dpRoute = info;
         addRoute(sStatic.mBluetoothA2dpRoute);
     }
@@ -493,6 +545,7 @@
      */
     public static class RouteInfo {
         CharSequence mName;
+        int mNameResId;
         private CharSequence mStatus;
         int mSupportedTypes;
         RouteGroup mGroup;
@@ -508,6 +561,24 @@
          * to users who may select this as the active route.
          */
         public CharSequence getName() {
+            return getName(sStatic.mResources);
+        }
+        
+        /**
+         * Return the properly localized/resource selected name of this route.
+         * 
+         * @param context Context used to resolve the correct configuration to load
+         * @return The user-friendly name of the media route. This is the string presented
+         * to users who may select this as the active route.
+         */
+        public CharSequence getName(Context context) {
+            return getName(context.getResources());
+        }
+        
+        CharSequence getName(Resources res) {
+            if (mNameResId != 0) {
+                return mName = res.getText(mNameResId);
+            }
             return mName;
         }
 
@@ -567,9 +638,9 @@
 
         @Override
         public String toString() {
-            String supportedTypes = typesToString(mSupportedTypes);
-            return "RouteInfo{ name=" + mName + ", status=" + mStatus +
-                    " category=" + mCategory +
+            String supportedTypes = typesToString(getSupportedTypes());
+            return getClass().getSimpleName() + "{ name=" + getName() + ", status=" + getStatus() +
+                    " category=" + getCategory() +
                     " supportedTypes=" + supportedTypes + "}";
         }
     }
@@ -596,6 +667,16 @@
             mName = name;
             routeUpdated();
         }
+        
+        /**
+         * Set the user-visible name of this route.
+         * @param resId Resource ID of the name to display to the user to describe this route
+         */
+        public void setName(int resId) {
+            mNameResId = resId;
+            mName = null;
+            routeUpdated();
+        }
 
         /**
          * Set the current user-visible status for this route.
@@ -676,9 +757,9 @@
             mGroup = this;
         }
 
-        public CharSequence getName() {
+        CharSequence getName(Resources res) {
             if (mUpdateName) updateName();
-            return super.getName();
+            return super.getName(res);
         }
 
         /**
@@ -698,6 +779,7 @@
             }
             final int at = mRoutes.size();
             mRoutes.add(route);
+            route.mGroup = this;
             mUpdateName = true;
             dispatchRouteGrouped(route, this, at);
             routeUpdated();
@@ -720,6 +802,7 @@
                             " group category=" + mCategory + ")");
             }
             mRoutes.add(insertAt, route);
+            route.mGroup = this;
             mUpdateName = true;
             dispatchRouteGrouped(route, this, insertAt);
             routeUpdated();
@@ -736,6 +819,7 @@
                         " is not a member of this group.");
             }
             mRoutes.remove(route);
+            route.mGroup = null;
             mUpdateName = true;
             dispatchRouteUngrouped(route, this);
             routeUpdated();
@@ -748,6 +832,7 @@
          */
         public void removeRoute(int index) {
             RouteInfo route = mRoutes.remove(index);
+            route.mGroup = null;
             mUpdateName = true;
             dispatchRouteUngrouped(route, this);
             routeUpdated();
@@ -799,6 +884,18 @@
             setStatusInt(status);
         }
 
+        @Override
+        void routeUpdated() {
+            int types = 0;
+            final int count = mRoutes.size();
+            for (int i = 0; i < count; i++) {
+                types |= mRoutes.get(i).mSupportedTypes;
+            }
+            mSupportedTypes = types;
+            mIcon = count == 1 ? mRoutes.get(0).getIconDrawable() : null;
+            super.routeUpdated();
+        }
+
         void updateName() {
             final StringBuilder sb = new StringBuilder();
             final int count = mRoutes.size();
@@ -810,6 +907,19 @@
             mName = sb.toString();
             mUpdateName = false;
         }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(super.toString());
+            sb.append('[');
+            final int count = mRoutes.size();
+            for (int i = 0; i < count; i++) {
+                if (i > 0) sb.append(", ");
+                sb.append(mRoutes.get(i));
+            }
+            sb.append(']');
+            return sb.toString();
+        }
     }
 
     /**
@@ -817,6 +927,7 @@
      */
     public static class RouteCategory {
         CharSequence mName;
+        int mNameResId;
         int mTypes;
         final boolean mGroupable;
 
@@ -826,10 +937,33 @@
             mGroupable = groupable;
         }
 
+        RouteCategory(int nameResId, int types, boolean groupable) {
+            mNameResId = nameResId;
+            mTypes = types;
+            mGroupable = groupable;
+        }
+
         /**
          * @return the name of this route category
          */
         public CharSequence getName() {
+            return getName(sStatic.mResources);
+        }
+        
+        /**
+         * Return the properly localized/configuration dependent name of this RouteCategory.
+         * 
+         * @param context Context to resolve name resources
+         * @return the name of this route category
+         */
+        public CharSequence getName(Context context) {
+            return getName(context.getResources());
+        }
+        
+        CharSequence getName(Resources res) {
+            if (mNameResId != 0) {
+                return res.getText(mNameResId);
+            }
             return mName;
         }
 
@@ -884,7 +1018,7 @@
 
         public String toString() {
             return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
-                    " groupable=" + mGroupable + " routes=" + sStatic.mRoutes.size() + " }";
+                    " groupable=" + mGroupable + " }";
         }
     }
 
@@ -910,7 +1044,7 @@
      * @see MediaRouter#addCallback(int, Callback)
      * @see MediaRouter#removeCallback(Callback)
      */
-    public interface Callback {
+    public static abstract class Callback {
         /**
          * Called when the supplied route becomes selected as the active route
          * for the given route type.
@@ -919,7 +1053,7 @@
          * @param type Type flag set indicating the routes that have been selected
          * @param info Route that has been selected for the given route types
          */
-        public void onRouteSelected(MediaRouter router, int type, RouteInfo info);
+        public abstract void onRouteSelected(MediaRouter router, int type, RouteInfo info);
 
         /**
          * Called when the supplied route becomes unselected as the active route
@@ -929,7 +1063,7 @@
          * @param type Type flag set indicating the routes that have been unselected
          * @param info Route that has been unselected for the given route types
          */
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
+        public abstract void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
 
         /**
          * Called when a route for the specified type was added.
@@ -937,7 +1071,7 @@
          * @param router the MediaRouter reporting the event
          * @param info Route that has become available for use
          */
-        public void onRouteAdded(MediaRouter router, RouteInfo info);
+        public abstract void onRouteAdded(MediaRouter router, RouteInfo info);
 
         /**
          * Called when a route for the specified type was removed.
@@ -945,7 +1079,7 @@
          * @param router the MediaRouter reporting the event
          * @param info Route that has been removed from availability
          */
-        public void onRouteRemoved(MediaRouter router, RouteInfo info);
+        public abstract void onRouteRemoved(MediaRouter router, RouteInfo info);
 
         /**
          * Called when an aspect of the indicated route has changed.
@@ -956,7 +1090,7 @@
          * @param router the MediaRouter reporting the event
          * @param info The route that was changed
          */
-        public void onRouteChanged(MediaRouter router, RouteInfo info);
+        public abstract void onRouteChanged(MediaRouter router, RouteInfo info);
 
         /**
          * Called when a route is added to a group.
@@ -966,7 +1100,8 @@
          * @param group The group the route was added to
          * @param index The route index within group that info was added at
          */
-        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index);
+        public abstract void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
+                int index);
 
         /**
          * Called when a route is removed from a group.
@@ -975,15 +1110,15 @@
          * @param info The route that was removed
          * @param group The group the route was removed from
          */
-        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
+        public abstract void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
     }
 
     /**
-     * Stub implementation of the {@link MediaRouter.Callback} interface.
-     * Each interface method is defined as a no-op. Override just the ones
+     * Stub implementation of {@link MediaRouter.Callback}.
+     * Each abstract method is defined as a no-op. Override just the ones
      * you need.
      */
-    public static class SimpleCallback implements Callback {
+    public static class SimpleCallback extends Callback {
 
         @Override
         public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
diff --git a/packages/SystemUI/res/layout-land/status_bar_search_panel.xml b/packages/SystemUI/res/layout-land/status_bar_search_panel.xml
index e6c0087..c8a120d 100644
--- a/packages/SystemUI/res/layout-land/status_bar_search_panel.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_search_panel.xml
@@ -54,7 +54,6 @@
                 prvandroid:outerRadius="@dimen/navbar_search_outerring_radius"
                 prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
                 prvandroid:snapMargin="@dimen/navbar_search_snap_margin"
-                prvandroid:hitRadius="@dimen/navbar_search_hit_radius"
                 prvandroid:feedbackCount="0"
                 prvandroid:vibrationDuration="@integer/config_vibration_duration"
                 prvandroid:alwaysTrackFinger="true"
diff --git a/packages/SystemUI/res/layout-port/status_bar_search_panel.xml b/packages/SystemUI/res/layout-port/status_bar_search_panel.xml
index 3828136..1e4bb57 100644
--- a/packages/SystemUI/res/layout-port/status_bar_search_panel.xml
+++ b/packages/SystemUI/res/layout-port/status_bar_search_panel.xml
@@ -54,7 +54,6 @@
                 prvandroid:outerRadius="@dimen/navbar_search_outerring_radius"
                 prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
                 prvandroid:snapMargin="@dimen/navbar_search_snap_margin"
-                prvandroid:hitRadius="@dimen/navbar_search_hit_radius"
                 prvandroid:feedbackCount="0"
                 prvandroid:vibrationDuration="@integer/config_vibration_duration"
                 prvandroid:alwaysTrackFinger="true"
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml
index c17f858..3b6c52e 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml
@@ -40,7 +40,6 @@
         prvandroid:outerRadius="@dimen/navbar_search_outerring_radius"
         prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
         prvandroid:snapMargin="@dimen/navbar_search_snap_margin"
-        prvandroid:hitRadius="@dimen/navbar_search_hit_radius"
         prvandroid:feedbackCount="0"
         prvandroid:vibrationDuration="@integer/config_vibration_duration"
         prvandroid:alwaysTrackFinger="true"
diff --git a/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml b/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml
index 100f81d..8c2360e 100644
--- a/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml
+++ b/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml
@@ -41,7 +41,6 @@
         prvandroid:outerRadius="@dimen/navbar_search_outerring_radius"
         prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
         prvandroid:snapMargin="@dimen/navbar_search_snap_margin"
-        prvandroid:hitRadius="@dimen/navbar_search_hit_radius"
         prvandroid:feedbackCount="0"
         prvandroid:vibrationDuration="@integer/config_vibration_duration"
         prvandroid:alwaysTrackFinger="true"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c88ae2a..6a96c6b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -110,11 +110,8 @@
     <!-- The width of the view containing the menu status bar icon -->
     <dimen name="navigation_menu_key_width">40dip</dimen>
 
-    <!-- Default distance beyond which snaps to the target radius -->
-    <dimen name="navbar_search_snap_margin">20dip</dimen>
-
-    <!-- Default distance from each snap target considers a "hit" -->
-    <dimen name="navbar_search_hit_radius">60dip</dimen>
+    <!-- Default distance beyond which snaps to the matching target -->
+    <dimen name="navbar_search_snap_margin">40dip</dimen>
 
     <!-- Diameter of outer shape drawable shown in navbar search-->
     <dimen name="navbar_search_outerring_diameter">340dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
index 923bcba..c082c97 100644
--- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
@@ -23,7 +23,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -35,6 +34,7 @@
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.widget.FrameLayout;
+
 import com.android.internal.widget.multiwaveview.GlowPadView;
 import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
 import com.android.systemui.R;
@@ -53,6 +53,7 @@
     private static final String ASSIST_ICON_METADATA_NAME =
             "com.android.systemui.action_assist_icon";
     private final Context mContext;
+    private final SearchManager mSearchManager;
     private BaseStatusBar mBar;
     private StatusBarTouchProxy mStatusBarTouchProxy;
 
@@ -73,38 +74,12 @@
         }
     }
 
-    private SearchManager mSearchManager;
-
-    // This code should be the same as that used in LockScreen.java
     public boolean isAssistantAvailable() {
-        Intent intent = getAssistIntent();
-        return intent == null ? false
-                : mContext.getPackageManager().queryIntentActivities(intent,
-                PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+        return mSearchManager != null && mSearchManager.isAssistantAvailable();
     }
 
     private Intent getAssistIntent() {
-        Intent intent = null;
-        SearchManager searchManager = getSearchManager();
-        if (searchManager != null) {
-            ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
-            if (globalSearchActivity != null) {
-                intent = new Intent(Intent.ACTION_ASSIST);
-                intent.setPackage(globalSearchActivity.getPackageName());
-            } else {
-                Slog.w(TAG, "No global search activity");
-            }
-        } else {
-            Slog.w(TAG, "No SearchManager");
-        }
-        return intent;
-    }
-
-    private SearchManager getSearchManager() {
-        if (mSearchManager == null) {
-            mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
-        }
-        return mSearchManager;
+        return mSearchManager != null ? mSearchManager.getAssistIntent() : null;
     }
 
     private void startAssistActivity() {
@@ -175,9 +150,8 @@
         // TODO: fetch views
         mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view);
         mGlowPadView.setOnTriggerListener(mGlowPadViewListener);
-        SearchManager searchManager = getSearchManager();
-        if (searchManager != null) {
-            ComponentName component = searchManager.getGlobalSearchActivity();
+        if (mSearchManager != null) {
+            ComponentName component = mSearchManager.getGlobalSearchActivity();
             if (component != null) {
                 if (!mGlowPadView.replaceTargetDrawablesIfPresent(component,
                         ASSIST_ICON_METADATA_NAME,
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index bec9aa2..0bdf84a 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -790,7 +790,7 @@
             if (DEBUG) Log.v(TAG, "Starting activity " + intent);
             context.startActivity(intent, opts.toBundle());
         }
-        if (!usingDrawingCache) {
+        if (usingDrawingCache) {
             holder.thumbnailViewImage.setDrawingCacheEnabled(false);
         }
     }
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 baf86f3..9c99653 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -820,6 +820,23 @@
             R.integer.config_show_search_delay);
     }
 
+    // Q: What kinds of notifications should show during setup?
+    // A: Almost none! Only things coming from the system (package is "android") that also 
+    // have special "kind" tags marking them as relevant for setup (see below).
+    private boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
+        if ("android".equals(sbn.pkg)) {
+            if (sbn.notification.kind != null) {
+                for (String aKind : sbn.notification.kind) {
+                    // IME switcher, created by InputMethodManagerService
+                    if ("android.system.imeswitcher".equals(aKind)) return true;
+                    // OTA availability & errors, created by SystemUpdateService
+                    if ("android.system.update".equals(aKind)) return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private void loadNotificationShade() {
         if (mPile == null) return;
 
@@ -831,7 +848,7 @@
         // If the device hasn't been through Setup, we only show system notifications
         for (int i=0; i<N; i++) {
             Entry ent = mNotificationData.get(N-i-1);
-            if (provisioned || "android".equals(ent.notification.pkg)) {
+            if (provisioned || showNotificationEvenIfUnprovisioned(ent.notification)) {
                 toShow.add(ent.row);
             }
         }
@@ -886,7 +903,7 @@
         for (int i=0; i<N; i++) {
             Entry ent = mNotificationData.get(N-i-1);
             if ((provisioned && ent.notification.score >= HIDE_ICONS_BELOW_SCORE)
-                    || "android".equals(ent.notification.pkg)) {
+                    || showNotificationEvenIfUnprovisioned(ent.notification)) {
                 toShow.add(ent.icon);
             }
         }
@@ -1376,12 +1393,59 @@
         if (!mTracking)
             return;
         mTracking = false;
-        mPile.setLayerType(View.LAYER_TYPE_NONE, null);
+        setPileLayers(View.LAYER_TYPE_NONE);
         mVelocityTracker.recycle();
         mVelocityTracker = null;
         mCloseView.setPressed(false);
     }
 
+    /**
+     * Enables or disables layers on the children of the notifications pile.
+     * 
+     * When layers are enabled, this method attempts to enable layers for the minimal
+     * number of children. Only children visible when the notification area is fully
+     * expanded will receive a layer. The technique used in this method might cause
+     * more children than necessary to get a layer (at most one extra child with the
+     * current UI.)
+     * 
+     * @param layerType {@link View#LAYER_TYPE_NONE} or {@link View#LAYER_TYPE_HARDWARE}
+     */
+    private void setPileLayers(int layerType) {
+        final int count = mPile.getChildCount();
+
+        switch (layerType) {
+            case View.LAYER_TYPE_NONE:
+                for (int i = 0; i < count; i++) {
+                    mPile.getChildAt(i).setLayerType(layerType, null);
+                }
+                break;
+            case View.LAYER_TYPE_HARDWARE:
+                final int[] location = new int[2]; 
+                mNotificationPanel.getLocationInWindow(location);
+
+                final int left = location[0];
+                final int top = location[1];
+                final int right = left + mNotificationPanel.getWidth();
+                final int bottom = top + getExpandedViewMaxHeight();
+
+                final Rect childBounds = new Rect();
+
+                for (int i = 0; i < count; i++) {
+                    final View view = mPile.getChildAt(i);
+                    view.getLocationInWindow(location);
+
+                    childBounds.set(location[0], location[1],
+                            location[0] + view.getWidth(), location[1] + view.getHeight());
+
+                    if (childBounds.intersects(left, top, right, bottom)) {
+                        view.setLayerType(layerType, null);
+                    }
+                }
+
+                break;
+        }
+    }
+
     void incrementAnim(long frameTimeNanos) {
         final long deltaNanos = Math.max(frameTimeNanos - mAnimLastTimeNanos, 0);
         final float t = deltaNanos * 0.000000001f;                  // ns -> s
@@ -1421,7 +1485,7 @@
         mCloseView.setPressed(true);
 
         mTracking = true;
-        mPile.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        setPileLayers(View.LAYER_TYPE_HARDWARE);
         mVelocityTracker = VelocityTracker.obtain();
         if (opening) {
             makeExpandedVisible(true);
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java
index c9388cb..4af66ce 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/LockScreen.java
@@ -32,7 +32,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Vibrator;
@@ -254,29 +253,14 @@
         }
     }
 
-    // This code should be the same as that in SearchPanelView
-    public boolean isAssistantAvailable() {
-        Intent intent = getAssistIntent();
-        return intent == null ? false
-                : mContext.getPackageManager().queryIntentActivities(intent,
-                        PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+    private boolean isAssistantAvailable() {
+        SearchManager searchManager = getSearchManager();
+        return searchManager != null && searchManager.isAssistantAvailable();
     }
 
     private Intent getAssistIntent() {
-        Intent intent = null;
         SearchManager searchManager = getSearchManager();
-        if (searchManager != null) {
-            ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
-            if (globalSearchActivity != null) {
-                intent = new Intent(Intent.ACTION_ASSIST);
-                intent.setPackage(globalSearchActivity.getPackageName());
-            } else {
-                Slog.w(TAG, "No global search activity");
-            }
-        } else {
-            Slog.w(TAG, "No SearchManager");
-        }
-        return intent;
+        return searchManager != null ? searchManager.getAssistIntent() : null;
     }
 
     private SearchManager getSearchManager() {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 9ef8d6b..0911f1f 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -19,6 +19,7 @@
 import android.app.ActivityManagerNative;
 import android.app.IUiModeManager;
 import android.app.ProgressDialog;
+import android.app.SearchManager;
 import android.app.UiModeManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -235,6 +236,7 @@
     static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
     static public final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
     static public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
+    static public final String SYSTEM_DIALOG_REASON_ASSIST = "assist";
 
     /**
      * These are the system UI flags that, when changing, can cause the layout
@@ -279,6 +281,7 @@
     LocalPowerManager mPowerManager;
     IStatusBarService mStatusBarService;
     Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
+    SearchManager mSearchManager;
 
     // Vibrator pattern for haptic feedback of a long press.
     long[] mLongPressVibePattern;
@@ -462,6 +465,7 @@
     Intent mDeskDockIntent;
     boolean mSearchKeyShortcutPending;
     boolean mConsumeSearchKeyUp;
+    boolean mAssistKeyLongPressed;
 
     // support for activating the lock screen while the screen is on
     boolean mAllowLockscreenWhenOn;
@@ -1860,6 +1864,26 @@
                 showOrHideRecentAppsDialog(RECENT_APPS_BEHAVIOR_SHOW_OR_DISMISS);
             }
             return -1;
+        } else if (keyCode == KeyEvent.KEYCODE_ASSIST) {
+            if (down) {
+                if (repeatCount == 0) {
+                    mAssistKeyLongPressed = false;
+                } else if (repeatCount == 1) {
+                    mAssistKeyLongPressed = true;
+                    if (!keyguardOn) {
+                         launchAssistLongPressAction();
+                    }
+                }
+            } else {
+                if (mAssistKeyLongPressed) {
+                    mAssistKeyLongPressed = false;
+                } else {
+                    if (!keyguardOn) {
+                        launchAssistAction();
+                    }
+                }
+            }
+            return -1;
         }
 
         // Shortcuts are invoked through Search+key, so intercept those here
@@ -2050,6 +2074,50 @@
         return false;
     }
 
+    private void launchAssistLongPressAction() {
+        performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST);
+
+        // launch the search activity
+        Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        try {
+            SearchManager searchManager = getSearchManager();
+            if (searchManager != null) {
+                searchManager.stopSearch();
+            }
+            mContext.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            Slog.w(TAG, "No activity to handle assist long press action.", e);
+        }
+    }
+
+    private void launchAssistAction() {
+        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST);
+
+        SearchManager searchManager = getSearchManager();
+        if (searchManager != null) {
+            Intent intent = searchManager.getAssistIntent();
+            if (intent != null) {
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_SINGLE_TOP
+                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                try {
+                    mContext.startActivity(intent);
+                } catch (ActivityNotFoundException e) {
+                    Slog.w(TAG, "No activity to handle assist action.", e);
+                }
+            }
+        }
+    }
+
+    private SearchManager getSearchManager() {
+        if (mSearchManager == null) {
+            mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
+        }
+        return mSearchManager;
+    }
+
     /**
      * A home key -> launch home action was detected.  Take the appropriate action
      * given the situation with the keyguard.
@@ -2900,7 +2968,7 @@
     }
 
     public boolean allowAppAnimationsLw() {
-        if (mKeyguard != null && mKeyguard.isVisibleLw()) {
+        if (mKeyguard != null && mKeyguard.isVisibleLw() && !mKeyguard.isAnimatingLw()) {
             // If keyguard is currently visible, no reason to animate
             // behind it.
             return false;
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 48219a4..2d41f43 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -586,6 +586,10 @@
         mImeSwitcherNotification.defaults = 0; // please be quiet
         mImeSwitcherNotification.sound = null;
         mImeSwitcherNotification.vibrate = null;
+
+        // Tag this notification specially so SystemUI knows it's important
+        mImeSwitcherNotification.kind = new String[] { "android.system.imeswitcher" };
+
         Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
         mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
 
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 6695cb9..f46f8e6 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -618,9 +618,11 @@
                                 PowerManager.PARTIAL_WAKE_LOCK, "Proximity Partial", false);
 
         mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
-        mScreenOnIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mScreenOnIntent.addFlags(
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
         mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
-        mScreenOffIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mScreenOffIntent.addFlags(
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
 
         Resources resources = mContext.getResources();
 
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index a8296f8..152e1889 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -312,9 +312,9 @@
         switch (eventType) {
             case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
             case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
-                if (mInjectedPointerTracker.mLastInjectedHoverEvent != null) {
-                    mInjectedPointerTracker.mLastInjectedHoverEvent.recycle();
-                    mInjectedPointerTracker.mLastInjectedHoverEvent = null;
+                if (mInjectedPointerTracker.mLastInjectedHoverEventForClick != null) {
+                    mInjectedPointerTracker.mLastInjectedHoverEventForClick.recycle();
+                    mInjectedPointerTracker.mLastInjectedHoverEventForClick = null;
                 }
                 mLastTouchedWindowId = -1;
             } break;
@@ -1077,7 +1077,8 @@
             final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex());
             final int pointerIndex = secondTapUp.findPointerIndex(pointerId);
 
-            MotionEvent lastExploreEvent = mInjectedPointerTracker.getLastInjectedHoverEvent();
+            MotionEvent lastExploreEvent =
+                mInjectedPointerTracker.getLastInjectedHoverEventForClick();
             if (lastExploreEvent == null) {
                 // No last touch explored event but there is accessibility focus in
                 // the active window. We click in the middle of the focus bounds.
@@ -1328,7 +1329,8 @@
             final int pointerId = mEvent.getPointerId(mEvent.getActionIndex());
             final int pointerIndex = mEvent.findPointerIndex(pointerId);
 
-            MotionEvent lastExploreEvent = mInjectedPointerTracker.getLastInjectedHoverEvent();
+            MotionEvent lastExploreEvent =
+                mInjectedPointerTracker.getLastInjectedHoverEventForClick();
             if (lastExploreEvent == null) {
                 // No last touch explored event but there is accessibility focus in
                 // the active window. We click in the middle of the focus bounds.
@@ -1482,6 +1484,9 @@
         // The last injected hover event.
         private MotionEvent mLastInjectedHoverEvent;
 
+        // The last injected hover event used for performing clicks.
+        private MotionEvent mLastInjectedHoverEventForClick;
+
         /**
          * Processes an injected {@link MotionEvent} event.
          *
@@ -1513,6 +1518,10 @@
                         mLastInjectedHoverEvent.recycle();
                     }
                     mLastInjectedHoverEvent = MotionEvent.obtain(event);
+                    if (mLastInjectedHoverEventForClick != null) {
+                        mLastInjectedHoverEventForClick.recycle();
+                    }
+                    mLastInjectedHoverEventForClick = MotionEvent.obtain(event);
                 } break;
             }
             if (DEBUG) {
@@ -1566,6 +1575,13 @@
             return mLastInjectedHoverEvent;
         }
 
+        /**
+         * @return The the last injected hover event.
+         */
+        public MotionEvent getLastInjectedHoverEventForClick() {
+            return mLastInjectedHoverEventForClick;
+        }
+
         @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 8a86fe7..1eef2cf 100755
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -1474,6 +1474,21 @@
             return true;
         }
 
+        // If the most recent activity was noHistory but was only stopped rather
+        // than stopped+finished because the device went to sleep, we need to make
+        // sure to finish it as we're making a new activity topmost.
+        final ActivityRecord last = mLastPausedActivity;
+        if (mService.mSleeping && last != null && !last.finishing) {
+            if ((last.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
+                    || (last.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) {
+                if (DEBUG_STATES) {
+                    Slog.d(TAG, "no-history finish of " + last + " on new resume");
+                }
+                requestFinishActivityLocked(last.appToken, Activity.RESULT_CANCELED, null,
+                "no-history");
+            }
+        }
+
         if (prev != null && prev != next) {
             if (!prev.waitingVisible && next != null && !next.nowVisible) {
                 prev.waitingVisible = true;
@@ -3279,8 +3294,16 @@
         if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
                 || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) {
             if (!r.finishing) {
-                requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
-                        "no-history");
+                if (!mService.mSleeping) {
+                    if (DEBUG_STATES) {
+                        Slog.d(TAG, "no-history finish of " + r);
+                    }
+                    requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
+                            "no-history");
+                } else {
+                    if (DEBUG_STATES) Slog.d(TAG, "Not finishing noHistory " + r
+                            + " on stop because we're just sleeping");
+                }
             }
         }
 
@@ -3526,9 +3549,10 @@
     final boolean requestFinishActivityLocked(IBinder token, int resultCode,
             Intent resultData, String reason) {
         int index = indexOfTokenLocked(token);
-        if (DEBUG_RESULTS) Slog.v(
+        if (DEBUG_RESULTS || DEBUG_STATES) Slog.v(
                 TAG, "Finishing activity @" + index + ": token=" + token
-                + ", result=" + resultCode + ", data=" + resultData);
+                + ", result=" + resultCode + ", data=" + resultData
+                + ", reason=" + reason);
         if (index < 0) {
             return false;
         }
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index 267bf82..758b6e7 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -279,7 +279,7 @@
                         }
                         mService.mFocusMayChange = true;
                     }
-                    if (win.isReadyForDisplay()) {
+                    if (win.isReadyForDisplay() && !winAnimator.isAnimating()) {
                         mForceHiding = true;
                     }
                     if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index a5e6f5a..6c8d969 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -3237,7 +3237,7 @@
             if (delayed) {
                 a = new AlphaAnimation(1, 0);
                 a.setStartOffset(0);
-                a.setDuration(delayDuration - 50);
+                a.setDuration(delayDuration - 120);
                 a.setBackgroundColor(0xFF000000);
             } else {
                 a = createExitAnimationLocked(transit, duration);