Merge "Remove Disco video from builds." into jb-dev
diff --git a/api/16.txt b/api/16.txt
index c95de36..eb09d85 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -23186,6 +23186,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..a62698e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23197,6 +23197,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/Notification.java b/core/java/android/app/Notification.java
index ec35a3f..bb497c0 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1670,6 +1670,9 @@
                 contentView.setTextViewText(R.id.text, overflowText);
                 contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE);
                 contentView.setViewVisibility(R.id.line3, View.VISIBLE);
+            } else {
+                contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
+                contentView.setViewVisibility(R.id.line3, View.GONE);
             }
 
             return contentView;
@@ -1812,6 +1815,7 @@
             // Remove the content text so line3 only shows if you have a summary
             final boolean hadThreeLines = (mBuilder.mContentText != null && mBuilder.mSubText != null);
             mBuilder.mContentText = null;
+
             RemoteViews contentView = getStandardView(R.layout.notification_template_big_text);
             
             if (hadThreeLines) {
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/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/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index 000eee7..bfcfdfa 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;
 
+    static final RouteComparator sComparator = 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);
+            mGroupAdapter = null;
+        }
         if (mAdapter != null) {
             mRouter.removeCallback(mAdapter);
             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,7 +140,8 @@
 
         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);
 
@@ -122,11 +151,59 @@
         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);
+        mListView.setAdapter(mGroupAdapter);
+        mListView.setOnItemClickListener(mGroupAdapter);
+        mListView.setItemsCanFocus(false);
+        mListView.clearChoices();
+        mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+        mGroupAdapter.initCheckedItems();
+
+        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);
+        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 MediaRouter.Callback,
             ListView.OnItemClickListener {
@@ -136,10 +213,8 @@
 
         private int mSelectedItemPosition;
         private final ArrayList<Object> mItems = new ArrayList<Object>();
-        private final LayoutInflater mInflater;
 
-        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) {
@@ -248,6 +341,24 @@
                 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) {
@@ -306,36 +417,274 @@
             mRouter.selectRoute(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));
+            }
+        }
     }
 
-    private static class ViewHolder {
-        public TextView text1;
-        public TextView text2;
-    }
+    private class GroupAdapter extends BaseAdapter implements MediaRouter.Callback,
+            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;
+
+        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, sComparator);
+            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());
+            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());
+        }
+
+        @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();
+        }
+
+        @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.selectRoute(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();
+        }
+    }
+
+    static class RouteComparator implements Comparator<RouteInfo> {
+        @Override
+        public int compare(RouteInfo lhs, RouteInfo rhs) {
+            return lhs.getName().toString().compareTo(rhs.getName().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/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/layout/notification_template_big_text.xml b/core/res/res/layout/notification_template_big_text.xml
index d377882..0b3386b 100644
--- a/core/res/res/layout/notification_template_big_text.xml
+++ b/core/res/res/layout/notification_template_big_text.xml
@@ -108,9 +108,8 @@
                 android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
                 android:layout_width="match_parent"
                 android:layout_height="0dp"
-                android:layout_marginBottom="8dp"
+                android:layout_marginBottom="10dp"
                 android:layout_marginRight="8dp"
-                android:layout_marginTop="2dp"
                 android:singleLine="false"
                 android:visibility="gone"
                 android:maxLines="8"
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/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..8488cd2 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -237,6 +237,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 +253,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 +286,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 +314,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 +339,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);
             }
@@ -478,7 +501,8 @@
 
     static void onA2dpDeviceConnected() {
         final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
-        info.mName = "Bluetooth"; // TODO Fetch the real name of the device
+        info.mName = sStatic.mResources.getString(
+                com.android.internal.R.string.bluetooth_a2dp_audio_route_name);
         sStatic.mBluetoothA2dpRoute = info;
         addRoute(sStatic.mBluetoothA2dpRoute);
     }
@@ -567,9 +591,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 + "}";
         }
     }
@@ -698,6 +722,7 @@
             }
             final int at = mRoutes.size();
             mRoutes.add(route);
+            route.mGroup = this;
             mUpdateName = true;
             dispatchRouteGrouped(route, this, at);
             routeUpdated();
@@ -720,6 +745,7 @@
                             " group category=" + mCategory + ")");
             }
             mRoutes.add(insertAt, route);
+            route.mGroup = this;
             mUpdateName = true;
             dispatchRouteGrouped(route, this, insertAt);
             routeUpdated();
@@ -736,6 +762,7 @@
                         " is not a member of this group.");
             }
             mRoutes.remove(route);
+            route.mGroup = null;
             mUpdateName = true;
             dispatchRouteUngrouped(route, this);
             routeUpdated();
@@ -748,6 +775,7 @@
          */
         public void removeRoute(int index) {
             RouteInfo route = mRoutes.remove(index);
+            route.mGroup = null;
             mUpdateName = true;
             dispatchRouteUngrouped(route, this);
             routeUpdated();
@@ -799,6 +827,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 +850,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();
+        }
     }
 
     /**
@@ -884,7 +937,7 @@
 
         public String toString() {
             return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
-                    " groupable=" + mGroupable + " routes=" + sStatic.mRoutes.size() + " }";
+                    " groupable=" + mGroupable + " }";
         }
     }
 
diff --git a/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java
index 52c9fda..78f7f3e 100644
--- a/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java
+++ b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java
@@ -29,6 +29,7 @@
 import android.filterfw.format.ImageFormat;
 import android.opengl.GLES20;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import java.lang.ArrayIndexOutOfBoundsException;
@@ -510,6 +511,20 @@
         super(name);
 
         mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+        String adjStr = SystemProperties.get("ro.media.effect.bgdropper.adj");
+        if (adjStr.length() > 0) {
+            try {
+                mAcceptStddev += Float.parseFloat(adjStr);
+                if (mLogVerbose) {
+                    Log.v(TAG, "Adjusting accept threshold by " + adjStr +
+                            ", now " + mAcceptStddev);
+                }
+            } catch (NumberFormatException e) {
+                Log.e(TAG,
+                        "Badly formatted property ro.media.effect.bgdropper.adj: " + adjStr);
+            }
+        }
     }
 
     @Override
@@ -695,7 +710,6 @@
             mBgUpdateVarianceProgram.setHostValue("bg_adapt_rate", mAdaptRateLearning);
             mBgUpdateVarianceProgram.setHostValue("fg_adapt_rate", mAdaptRateLearning);
             mFrameCount = 0;
-            mStartLearning = false;
         }
 
         // Select correct pingpong buffers
@@ -720,6 +734,11 @@
         mBgInput.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER,
                                      GLES20.GL_LINEAR_MIPMAP_NEAREST);
 
+        if (mStartLearning) {
+            copyShaderProgram.process(mVideoInput, mBgMean[inputIndex]);
+            mStartLearning = false;
+        }
+
         // Process shaders
         Frame[] distInputs = { mVideoInput, mBgMean[inputIndex], mBgVariance[inputIndex] };
         mBgDistProgram.process(distInputs, mDistance);
@@ -765,7 +784,12 @@
                 ByteBuffer mMaskAverageByteBuffer = mMaskAverage.getData();
                 byte[] mask_average = mMaskAverageByteBuffer.array();
                 int bi = (int)(mask_average[3] & 0xFF);
-                if (mLogVerbose) Log.v(TAG, String.format("Mask_average is %d", bi));
+
+                if (mLogVerbose) {
+                    Log.v(TAG,
+                            String.format("Mask_average is %d, threshold is %d",
+                                    bi, DEFAULT_LEARNING_DONE_THRESHOLD));
+                }
 
                 if (bi >= DEFAULT_LEARNING_DONE_THRESHOLD) {
                     mStartLearning = true;                                      // Restart learning
diff --git a/packages/SystemUI/res/drawable/system_bar_notification_header_bg.xml b/packages/SystemUI/res/drawable/system_bar_notification_header_bg.xml
new file mode 100644
index 0000000..85f1ea2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/system_bar_notification_header_bg.xml
@@ -0,0 +1,20 @@
+<?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">
+    <item android:state_pressed="true"  android:drawable="@*android:drawable/list_selector_pressed_holo_dark" />
+    <item                               android:drawable="@*android:drawable/list_selector_disabled_holo_dark" />
+</selector>
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/layout/system_bar_notification_panel_title.xml b/packages/SystemUI/res/layout/system_bar_notification_panel_title.xml
index afe3b49..480d979 100644
--- a/packages/SystemUI/res/layout/system_bar_notification_panel_title.xml
+++ b/packages/SystemUI/res/layout/system_bar_notification_panel_title.xml
@@ -18,7 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
     android:id="@+id/title_area"
-    android:background="@color/notification_panel_solid_background"
+    android:background="@drawable/system_bar_notification_header_bg"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:clickable="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/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index df41d25..48efbc7 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -23,11 +23,14 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.View.OnClickListener;
+import android.widget.ScrollView;
+import android.widget.FrameLayout;
 
 public class ExpandHelper implements Gefingerpoken, OnClickListener {
     public interface Callback {
@@ -38,7 +41,7 @@
     }
 
     private static final String TAG = "ExpandHelper";
-    protected static final boolean DEBUG = false;
+    protected static final boolean DEBUG = true;
     private static final long EXPAND_DURATION = 250;
     private static final long GLOW_DURATION = 150;
 
@@ -82,8 +85,11 @@
     private int mLargeSize;
     private float mMaximumStretch;
 
+    private int mGravity;
+
     private class ViewScaler {
         View mView;
+
         public ViewScaler() {}
         public void setView(View v) {
             mView = v;
@@ -119,6 +125,15 @@
         }
     }
 
+    /**
+     * Handle expansion gestures to expand and contract children of the callback.
+     *
+     * @param context application context
+     * @param callback the container that holds the items to be manipulated
+     * @param small the smallest allowable size for the manuipulated items.
+     * @param large the largest allowable size for the manuipulated items.
+     * @param scoller if non-null also manipulate the scroll position to obey the gravity.
+     */
     public ExpandHelper(Context context, Callback callback, int small, int large) {
         mSmallSize = small;
         mMaximumStretch = mSmallSize * STRETCH_INTERVAL;
@@ -126,7 +141,7 @@
         mContext = context;
         mCallback = callback;
         mScaler = new ViewScaler();
-
+        mGravity = Gravity.TOP;
         mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f);
         mScaleAnimation.setDuration(EXPAND_DURATION);
 
@@ -194,6 +209,7 @@
                 span *= USE_SPAN ? 1f : 0f;
                 float drag = detector.getFocusY() - mInitialTouchFocusY;
                 drag *= USE_DRAG ? 1f : 0f;
+                drag *= mGravity == Gravity.BOTTOM ? -1f : 1f;
                 float pull = Math.abs(drag) + Math.abs(span) + 1f;
                 float hand = drag * Math.abs(drag) / pull + span * Math.abs(span) / pull;
                 if (DEBUG) Log.d(TAG, "current span handle is: " + hand);
@@ -227,6 +243,10 @@
         mEventSource = eventSource;
     }
 
+    public void setGravity(int gravity) {
+        mGravity = gravity;
+    }
+
     public void setGlow(float glow) {
         if (!mGlowAnimationSet.isRunning() || glow == 0f) {
             if (mGlowAnimationSet.isRunning()) {
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/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 9c773a5..00d6d6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -225,11 +225,28 @@
         final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
         final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0);
 
+        setSlippery(disableHome && disableRecent && disableBack);
+
         getBackButton()   .setVisibility(disableBack       ? View.INVISIBLE : View.VISIBLE);
         getHomeButton()   .setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
         getRecentsButton().setVisibility(disableRecent     ? View.INVISIBLE : View.VISIBLE);
     }
 
+    public void setSlippery(boolean newSlippery) {
+        WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
+        if (lp != null) {
+            boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0;
+            if (!oldSlippery && newSlippery) {
+                lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
+            } else if (oldSlippery && !newSlippery) {
+                lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
+            } else {
+                return;
+            }
+            WindowManagerImpl.getDefault().updateViewLayout(this, lp);
+        }
+    }
+
     public void setMenuVisibility(final boolean show) {
         setMenuVisibility(show, 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 56de506..baf86f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -171,6 +171,7 @@
     final Rect mNotificationPanelBackgroundPadding = new Rect();
     int mNotificationPanelGravity;
     int mNotificationPanelMinHeight;
+    boolean mNotificationPanelIsFullScreenWidth;
 
     // top bar
     View mClearButton;
@@ -361,9 +362,11 @@
                 return true;
             }
         });
+        mNotificationPanelIsFullScreenWidth =
+            (mNotificationPanel.getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT);
         mNotificationPanel.setSystemUiVisibility(
                   View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER
-                | View.STATUS_BAR_DISABLE_SYSTEM_INFO);
+                | (mNotificationPanelIsFullScreenWidth ? 0 : View.STATUS_BAR_DISABLE_SYSTEM_INFO));
 
         if (!ActivityManager.isHighEndGfx(mDisplay)) {
             mStatusBarWindow.setBackground(null);
@@ -376,6 +379,8 @@
             mIntruderAlertView.setBar(this);
         }
 
+        updateShowSearchHoldoff();
+
         mStatusBarView.mService = this;
 
         mChoreographer = Choreographer.getInstance();
@@ -663,7 +668,6 @@
 
         lp.setTitle("NavigationBar");
         lp.windowAnimations = 0;
-
         return lp;
     }
 
@@ -808,8 +812,12 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         updateRecentsPanel();
-        mShowSearchHoldoff  = mContext.getResources().getInteger(
-                R.integer.config_show_search_delay);
+        updateShowSearchHoldoff();
+    }
+
+    private void updateShowSearchHoldoff() {
+        mShowSearchHoldoff = mContext.getResources().getInteger(
+            R.integer.config_show_search_delay);
     }
 
     private void loadNotificationShade() {
@@ -1174,7 +1182,8 @@
 
         mExpandedVisible = true;
         mPile.setLayoutTransitionsEnabled(true);
-        makeSlippery(mNavigationBarView, true);
+        if (mNavigationBarView != null)
+            mNavigationBarView.setSlippery(true);
 
         updateCarrierLabelVisibility(true);
 
@@ -1198,19 +1207,6 @@
         visibilityChanged(true);
     }
 
-    private static void makeSlippery(View view, boolean slippery) {
-        if (view == null) {
-            return;
-        }
-        WindowManager.LayoutParams lp = (WindowManager.LayoutParams) view.getLayoutParams();
-        if (slippery) {
-            lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
-        } else {
-            lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
-        }
-        WindowManagerImpl.getDefault().updateViewLayout(view, lp);
-    }
-
     public void animateExpand() {
         if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded);
         if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) {
@@ -1295,8 +1291,9 @@
         }
         mExpandedVisible = false;
         mPile.setLayoutTransitionsEnabled(false);
+        if (mNavigationBarView != null)
+            mNavigationBarView.setSlippery(false);
         visibilityChanged(false);
-        makeSlippery(mNavigationBarView, false);
 
         // Shrink the window to the size of the status bar only
         WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index ed1b2f5..9317561 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -22,6 +22,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.widget.FrameLayout;
+import android.widget.ScrollView;
 import android.widget.TextSwitcher;
 
 import com.android.systemui.ExpandHelper;
@@ -47,6 +48,7 @@
     protected void onAttachedToWindow () {
         super.onAttachedToWindow();
         latestItems = (NotificationRowLayout) findViewById(R.id.latestItems);
+        ScrollView scroller = (ScrollView) findViewById(R.id.scroll);
         int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_row_min_height);
         int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_row_max_height);
         mExpandHelper = new ExpandHelper(mContext, latestItems, minHeight, maxHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
index d8166f1..af0f9d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Slog;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -33,6 +34,7 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.widget.RelativeLayout;
+import android.widget.ScrollView;
 
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
@@ -114,6 +116,7 @@
         int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_row_max_height);
         mExpandHelper = new ExpandHelper(mContext, latestItems, minHeight, maxHeight);
         mExpandHelper.setEventSource(this);
+        mExpandHelper.setGravity(Gravity.BOTTOM);
     }
 
     private View.OnClickListener mClearButtonListener = new View.OnClickListener() {
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
index 18b8042..87ec16b 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
@@ -81,7 +81,7 @@
 
     private int mFailedAttempts = 0;
     private int mFailedBiometricUnlockAttempts = 0;
-    private static final int FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 5;
+    private static final int FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 3;
 
     private boolean mClockVisible;
 
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
index d7fb19a..58d14ed 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
@@ -58,7 +58,6 @@
         final IccCard.State simState = mUpdateMonitor.getSimState();
         return (simState == IccCard.State.PIN_REQUIRED
                 || simState == IccCard.State.PUK_REQUIRED
-                || simState == IccCard.State.ABSENT
                 || simState == IccCard.State.PERM_DISABLED);
     }
 
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..a5190f1 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.
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 985249d..2918dbc 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -1702,7 +1702,9 @@
 
         mProximityAlerts.remove(intent);
         if (mProximityAlerts.size() == 0) {
-            removeUpdatesLocked(mProximityReceiver);
+            if (mProximityReceiver != null) {
+                removeUpdatesLocked(mProximityReceiver);
+            }
             mProximityReceiver = null;
             mProximityListener = null;
         }
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index a36c673..152e1889 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -98,6 +98,9 @@
     // the two dragging pointers as opposed to use the location of the primary one.
     private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
 
+    // The timeout after which we are no longer trying to detect a gesture.
+    private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000;
+
     // Temporary array for storing pointer IDs.
     private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
 
@@ -138,6 +141,9 @@
     // Command for delayed sending of a long press.
     private final PerformLongPressDelayed mPerformLongPressDelayed;
 
+    // Command for exiting gesture detection mode after a timeout.
+    private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed;
+
     // Helper to detect and react to double tap in touch explore mode.
     private final DoubleTapDetector mDoubleTapDetector;
 
@@ -212,6 +218,7 @@
         mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
         mHandler = new Handler(context.getMainLooper());
         mPerformLongPressDelayed = new PerformLongPressDelayed();
+        mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed();
         mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
         mGestureLibrary.setOrientationStyle(4);
         mGestureLibrary.load();
@@ -257,6 +264,7 @@
         mSendHoverEnterDelayed.remove();
         mSendHoverExitDelayed.remove();
         mPerformLongPressDelayed.remove();
+        mExitGestureDetectionModeDelayed.remove();
         // Reset the pointer trackers.
         mReceivedPointerTracker.clear();
         mInjectedPointerTracker.clear();
@@ -304,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;
@@ -420,6 +428,7 @@
                                     mSendHoverEnterDelayed.remove();
                                     mSendHoverExitDelayed.remove();
                                     mPerformLongPressDelayed.remove();
+                                    mExitGestureDetectionModeDelayed.post();
                                 } else {
                                     // We have just decided that the user is touch,
                                     // exploring so start sending events.
@@ -727,6 +736,7 @@
                 }
 
                 mStrokeBuffer.clear();
+                mExitGestureDetectionModeDelayed.remove();
                 mCurrentState = STATE_TOUCH_EXPLORING;
             } break;
             case MotionEvent.ACTION_CANCEL: {
@@ -1067,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.
@@ -1263,6 +1274,25 @@
     }
 
     /**
+     * Class for delayed exiting from gesture detecting mode.
+     */
+    private final class ExitGestureDetectionModeDelayed implements Runnable {
+
+        public void post() {
+            mHandler.postDelayed(this, EXIT_GESTURE_DETECTION_TIMEOUT);
+        }
+
+        public void remove() {
+            mHandler.removeCallbacks(this);
+        }
+
+        @Override
+        public void run() {
+            clear();
+        }
+    }
+
+    /**
      * Class for delayed sending of long press.
      */
     private final class PerformLongPressDelayed implements Runnable {
@@ -1299,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.
@@ -1453,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.
          *
@@ -1484,6 +1518,10 @@
                         mLastInjectedHoverEvent.recycle();
                     }
                     mLastInjectedHoverEvent = MotionEvent.obtain(event);
+                    if (mLastInjectedHoverEventForClick != null) {
+                        mLastInjectedHoverEventForClick.recycle();
+                    }
+                    mLastInjectedHoverEventForClick = MotionEvent.obtain(event);
                 } break;
             }
             if (DEBUG) {
@@ -1537,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();