am 5773f724: am dd4f9e8b: Merge "Update the media router dialogs and integrate into system UI." into klp-dev

* commit '5773f72457246e437c24624cda8ec2a869165ee8':
  Update the media router dialogs and integrate into system UI.
diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java
index 6839c8e..dffa969 100644
--- a/core/java/android/app/MediaRouteActionProvider.java
+++ b/core/java/android/app/MediaRouteActionProvider.java
@@ -16,10 +16,7 @@
 
 package android.app;
 
-import com.android.internal.app.MediaRouteChooserDialogFragment;
-
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteInfo;
 import android.util.Log;
@@ -30,22 +27,38 @@
 
 import java.lang.ref.WeakReference;
 
+/**
+ * The media route action provider displays a {@link MediaRouteButton media route button}
+ * in the application's {@link ActionBar} to allow the user to select routes and
+ * to control the currently selected route.
+ * <p>
+ * The application must specify the kinds of routes that the user should be allowed
+ * to select by specifying the route types with the {@link #setRouteTypes} method.
+ * </p><p>
+ * Refer to {@link MediaRouteButton} for a description of the button that will
+ * appear in the action bar menu.  Note that instead of disabling the button
+ * when no routes are available, the action provider will instead make the
+ * menu item invisible.  In this way, the button will only be visible when it
+ * is possible for the user to discover and select a matching route.
+ * </p>
+ */
 public class MediaRouteActionProvider extends ActionProvider {
     private static final String TAG = "MediaRouteActionProvider";
 
-    private Context mContext;
-    private MediaRouter mRouter;
-    private MenuItem mMenuItem;
-    private MediaRouteButton mView;
+    private final Context mContext;
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
+
     private int mRouteTypes;
+    private MediaRouteButton mButton;
     private View.OnClickListener mExtendedSettingsListener;
-    private RouterCallback mCallback;
 
     public MediaRouteActionProvider(Context context) {
         super(context);
+
         mContext = context;
         mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
-        mCallback = new RouterCallback(this);
+        mCallback = new MediaRouterCallback(this);
 
         // Start with live audio by default.
         // TODO Update this when new route types are added; segment by API level
@@ -53,80 +66,74 @@
         setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_AUDIO);
     }
 
+    /**
+     * Sets the types of routes that will be shown in the media route chooser dialog
+     * launched by this button.
+     *
+     * @param types The route types to match.
+     */
     public void setRouteTypes(int types) {
-        if (mRouteTypes == types) return;
-        if (mRouteTypes != 0) {
-            mRouter.removeCallback(mCallback);
+        if (mRouteTypes != types) {
+            // FIXME: We currently have no way of knowing whether the action provider
+            // is still needed by the UI.  Unfortunately this means the action provider
+            // may leak callbacks until garbage collection occurs.  This may result in
+            // media route providers doing more work than necessary in the short term
+            // while trying to discover routes that are no longer of interest to the
+            // application.  To solve this problem, the action provider will need some
+            // indication from the framework that it is being destroyed.
+            if (mRouteTypes != 0) {
+                mRouter.removeCallback(mCallback);
+            }
+            mRouteTypes = types;
+            if (types != 0) {
+                mRouter.addCallback(types, mCallback,
+                        MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
+            }
+            refreshRoute();
+
+            if (mButton != null) {
+                mButton.setRouteTypes(mRouteTypes);
+            }
         }
-        mRouteTypes = types;
-        if (types != 0) {
-            mRouter.addCallback(types, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
-        }
-        if (mView != null) {
-            mView.setRouteTypes(mRouteTypes);
+    }
+
+    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+        mExtendedSettingsListener = listener;
+        if (mButton != null) {
+            mButton.setExtendedSettingsClickListener(listener);
         }
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     public View onCreateActionView() {
         throw new UnsupportedOperationException("Use onCreateActionView(MenuItem) instead.");
     }
 
     @Override
     public View onCreateActionView(MenuItem item) {
-        if (mMenuItem != null || mView != null) {
+        if (mButton != null) {
             Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " +
                     "with a menu item. Don't reuse MediaRouteActionProvider instances! " +
                     "Abandoning the old one...");
         }
-        mMenuItem = item;
-        mView = new MediaRouteButton(mContext);
-        mView.setCheatSheetEnabled(true);
-        mView.setRouteTypes(mRouteTypes);
-        mView.setExtendedSettingsClickListener(mExtendedSettingsListener);
-        mView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+
+        mButton = new MediaRouteButton(mContext);
+        mButton.setCheatSheetEnabled(true);
+        mButton.setRouteTypes(mRouteTypes);
+        mButton.setExtendedSettingsClickListener(mExtendedSettingsListener);
+        mButton.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
                 ViewGroup.LayoutParams.MATCH_PARENT));
-        return mView;
+        return mButton;
     }
 
     @Override
     public boolean onPerformDefaultAction() {
-        final FragmentManager fm = getActivity().getFragmentManager();
-        // See if one is already attached to this activity.
-        MediaRouteChooserDialogFragment dialogFragment =
-                (MediaRouteChooserDialogFragment) fm.findFragmentByTag(
-                MediaRouteChooserDialogFragment.FRAGMENT_TAG);
-        if (dialogFragment != null) {
-            Log.w(TAG, "onPerformDefaultAction(): Chooser dialog already showing!");
-            return false;
+        if (mButton != null) {
+            return mButton.showDialogInternal();
         }
-
-        dialogFragment = new MediaRouteChooserDialogFragment();
-        dialogFragment.setExtendedSettingsClickListener(mExtendedSettingsListener);
-        dialogFragment.setRouteTypes(mRouteTypes);
-        dialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG);
-        return true;
-    }
-
-    private Activity getActivity() {
-        // Gross way of unwrapping the Activity so we can get the FragmentManager
-        Context context = mContext;
-        while (context instanceof ContextWrapper && !(context instanceof Activity)) {
-            context = ((ContextWrapper) context).getBaseContext();
-        }
-        if (!(context instanceof Activity)) {
-            throw new IllegalStateException("The MediaRouteActionProvider's Context " +
-                    "is not an Activity.");
-        }
-
-        return (Activity) context;
-    }
-
-    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
-        mExtendedSettingsListener = listener;
-        if (mView != null) {
-            mView.setExtendedSettingsClickListener(listener);
-        }
+        return false;
     }
 
     @Override
@@ -136,36 +143,43 @@
 
     @Override
     public boolean isVisible() {
-        return mRouter.getRouteCount() > 1;
+        return mRouter.isRouteAvailable(mRouteTypes,
+                MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
     }
 
-    private static class RouterCallback extends MediaRouter.SimpleCallback {
-        private WeakReference<MediaRouteActionProvider> mAp;
+    private void refreshRoute() {
+        refreshVisibility();
+    }
 
-        RouterCallback(MediaRouteActionProvider ap) {
-            mAp = new WeakReference<MediaRouteActionProvider>(ap);
+    private static class MediaRouterCallback extends MediaRouter.SimpleCallback {
+        private final WeakReference<MediaRouteActionProvider> mProviderWeak;
+
+        public MediaRouterCallback(MediaRouteActionProvider provider) {
+            mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider);
         }
 
         @Override
         public void onRouteAdded(MediaRouter router, RouteInfo info) {
-            final MediaRouteActionProvider ap = mAp.get();
-            if (ap == null) {
-                router.removeCallback(this);
-                return;
-            }
-
-            ap.refreshVisibility();
+            refreshRoute(router);
         }
 
         @Override
         public void onRouteRemoved(MediaRouter router, RouteInfo info) {
-            final MediaRouteActionProvider ap = mAp.get();
-            if (ap == null) {
-                router.removeCallback(this);
-                return;
-            }
+            refreshRoute(router);
+        }
 
-            ap.refreshVisibility();
+        @Override
+        public void onRouteChanged(MediaRouter router, RouteInfo info) {
+            refreshRoute(router);
+        }
+
+        private void refreshRoute(MediaRouter router) {
+            MediaRouteActionProvider provider = mProviderWeak.get();
+            if (provider != null) {
+                provider.refreshRoute();
+            } else {
+                router.removeCallback(this);
+            }
         }
     }
 }
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index 9b1ff93..a7982f4 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -17,7 +17,7 @@
 package android.app;
 
 import com.android.internal.R;
-import com.android.internal.app.MediaRouteChooserDialogFragment;
+import com.android.internal.app.MediaRouteDialogPresenter;
 
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -30,7 +30,6 @@
 import android.media.MediaRouter.RouteInfo;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.SoundEffectConstants;
@@ -38,17 +37,15 @@
 import android.widget.Toast;
 
 public class MediaRouteButton extends View {
-    private static final String TAG = "MediaRouteButton";
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
 
-    private MediaRouter mRouter;
-    private final MediaRouteCallback mRouterCallback = new MediaRouteCallback();
     private int mRouteTypes;
 
     private boolean mAttachedToWindow;
 
     private Drawable mRemoteIndicator;
     private boolean mRemoteActive;
-    private boolean mToggleMode;
     private boolean mCheatSheetEnabled;
     private boolean mIsConnecting;
 
@@ -56,12 +53,13 @@
     private int mMinHeight;
 
     private OnClickListener mExtendedSettingsClickListener;
-    private MediaRouteChooserDialogFragment mDialogFragment;
 
+    // The checked state is used when connected to a remote route.
     private static final int[] CHECKED_STATE_SET = {
         R.attr.state_checked
     };
 
+    // The activated state is used while connecting to a remote route.
     private static final int[] ACTIVATED_STATE_SET = {
         R.attr.state_activated
     };
@@ -78,6 +76,7 @@
         super(context, attrs, defStyleAttr);
 
         mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        mCallback = new MediaRouterCallback();
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0);
@@ -98,19 +97,87 @@
         setRouteTypes(routeTypes);
     }
 
-    private void setRemoteIndicatorDrawable(Drawable d) {
-        if (mRemoteIndicator != null) {
-            mRemoteIndicator.setCallback(null);
-            unscheduleDrawable(mRemoteIndicator);
+    /**
+     * Gets the media route types for filtering the routes that the user can
+     * select using the media route chooser dialog.
+     *
+     * @return The route types.
+     */
+    public int getRouteTypes() {
+        return mRouteTypes;
+    }
+
+    /**
+     * Sets the types of routes that will be shown in the media route chooser dialog
+     * launched by this button.
+     *
+     * @param types The route types to match.
+     */
+    public void setRouteTypes(int types) {
+        if (mRouteTypes != types) {
+            if (mAttachedToWindow && mRouteTypes != 0) {
+                mRouter.removeCallback(mCallback);
+            }
+
+            mRouteTypes = types;
+
+            if (mAttachedToWindow && types != 0) {
+                mRouter.addCallback(types, mCallback,
+                        MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
+            }
+
+            refreshRoute();
         }
-        mRemoteIndicator = d;
-        if (d != null) {
-            d.setCallback(this);
-            d.setState(getDrawableState());
-            d.setVisible(getVisibility() == VISIBLE, false);
+    }
+
+    public void setExtendedSettingsClickListener(OnClickListener listener) {
+        mExtendedSettingsClickListener = listener;
+    }
+
+    /**
+     * Show the route chooser or controller dialog.
+     * <p>
+     * If the default route is selected or if the currently selected route does
+     * not match the {@link #getRouteTypes route types}, then shows the route chooser dialog.
+     * Otherwise, shows the route controller dialog to offer the user
+     * a choice to disconnect from the route or perform other control actions
+     * such as setting the route's volume.
+     * </p><p>
+     * This will attach a {@link DialogFragment} to the containing Activity.
+     * </p>
+     */
+    public void showDialog() {
+        showDialogInternal();
+    }
+
+    boolean showDialogInternal() {
+        if (!mAttachedToWindow) {
+            return false;
         }
 
-        refreshDrawableState();
+        DialogFragment f = MediaRouteDialogPresenter.showDialogFragment(getActivity(),
+                mRouteTypes, mExtendedSettingsClickListener);
+        return f != null;
+    }
+
+    private Activity getActivity() {
+        // Gross way of unwrapping the Activity so we can get the FragmentManager
+        Context context = getContext();
+        while (context instanceof ContextWrapper) {
+            if (context instanceof Activity) {
+                return (Activity)context;
+            }
+            context = ((ContextWrapper)context).getBaseContext();
+        }
+        throw new IllegalStateException("The MediaRouteButton's Context is not an Activity.");
+    }
+
+    /**
+     * Sets whether to enable showing a toast with the content descriptor of the
+     * button when the button is long pressed.
+     */
+    void setCheatSheetEnabled(boolean enable) {
+        mCheatSheetEnabled = enable;
     }
 
     @Override
@@ -120,29 +187,7 @@
         if (!handled) {
             playSoundEffect(SoundEffectConstants.CLICK);
         }
-
-        if (mToggleMode) {
-            if (mRemoteActive) {
-                mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute(), true);
-            } else {
-                final int N = mRouter.getRouteCount();
-                for (int i = 0; i < N; i++) {
-                    final RouteInfo route = mRouter.getRouteAt(i);
-                    if ((route.getSupportedTypes() & mRouteTypes) != 0 &&
-                            route != mRouter.getDefaultRoute()) {
-                        mRouter.selectRouteInt(mRouteTypes, route, true);
-                    }
-                }
-            }
-        } else {
-            showDialog();
-        }
-
-        return handled;
-    }
-
-    void setCheatSheetEnabled(boolean enable) {
-        mCheatSheetEnabled = enable;
+        return showDialogInternal() || handled;
     }
 
     @Override
@@ -183,87 +228,9 @@
         }
         cheatSheet.show();
         performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-
         return true;
     }
 
-    public void setRouteTypes(int types) {
-        if (types == mRouteTypes) {
-            // Already registered; nothing to do.
-            return;
-        }
-
-        if (mAttachedToWindow && mRouteTypes != 0) {
-            mRouter.removeCallback(mRouterCallback);
-        }
-
-        mRouteTypes = types;
-
-        if (mAttachedToWindow) {
-            updateRouteInfo();
-            mRouter.addCallback(types, mRouterCallback,
-                    MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
-        }
-    }
-
-    private void updateRouteInfo() {
-        updateRemoteIndicator();
-        updateRouteCount();
-    }
-
-    public int getRouteTypes() {
-        return mRouteTypes;
-    }
-
-    void updateRemoteIndicator() {
-        final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes);
-        final boolean isRemote = selected != mRouter.getDefaultRoute();
-        final boolean isConnecting = selected != null && selected.isConnecting();
-
-        boolean needsRefresh = false;
-        if (mRemoteActive != isRemote) {
-            mRemoteActive = isRemote;
-            needsRefresh = true;
-        }
-        if (mIsConnecting != isConnecting) {
-            mIsConnecting = isConnecting;
-            needsRefresh = true;
-        }
-
-        if (needsRefresh) {
-            refreshDrawableState();
-        }
-    }
-
-    void updateRouteCount() {
-        final int N = mRouter.getRouteCount();
-        int count = 0;
-        boolean scanRequired = false;
-        for (int i = 0; i < N; i++) {
-            final RouteInfo route = mRouter.getRouteAt(i);
-            final int routeTypes = route.getSupportedTypes();
-            if ((routeTypes & mRouteTypes) != 0) {
-                if (route instanceof RouteGroup) {
-                    count += ((RouteGroup) route).getRouteCount();
-                } else {
-                    count++;
-                }
-                if (((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO
-                        | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) != 0) {
-                    scanRequired = true;
-                }
-            }
-        }
-
-        setEnabled(count != 0);
-
-        // Only allow toggling if we have more than just user routes.
-        // Don't toggle if we support video or remote display routes, we may have to
-        // let the dialog scan.
-        mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0
-                && !scanRequired;
-    }
-
     @Override
     protected int[] onCreateDrawableState(int extraSpace) {
         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
@@ -291,6 +258,21 @@
         }
     }
 
+    private void setRemoteIndicatorDrawable(Drawable d) {
+        if (mRemoteIndicator != null) {
+            mRemoteIndicator.setCallback(null);
+            unscheduleDrawable(mRemoteIndicator);
+        }
+        mRemoteIndicator = d;
+        if (d != null) {
+            d.setCallback(this);
+            d.setState(getDrawableState());
+            d.setVisible(getVisibility() == VISIBLE, false);
+        }
+
+        refreshDrawableState();
+    }
+
     @Override
     protected boolean verifyDrawable(Drawable who) {
         return super.verifyDrawable(who) || who == mRemoteIndicator;
@@ -299,12 +281,16 @@
     @Override
     public void jumpDrawablesToCurrentState() {
         super.jumpDrawablesToCurrentState();
-        if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState();
+
+        if (mRemoteIndicator != null) {
+            mRemoteIndicator.jumpToCurrentState();
+        }
     }
 
     @Override
     public void setVisibility(int visibility) {
         super.setVisibility(visibility);
+
         if (mRemoteIndicator != null) {
             mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false);
         }
@@ -313,20 +299,22 @@
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
+
         mAttachedToWindow = true;
         if (mRouteTypes != 0) {
-            mRouter.addCallback(mRouteTypes, mRouterCallback,
+            mRouter.addCallback(mRouteTypes, mCallback,
                     MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
-            updateRouteInfo();
         }
+        refreshRoute();
     }
 
     @Override
     public void onDetachedFromWindow() {
-        if (mRouteTypes != 0) {
-            mRouter.removeCallback(mRouterCallback);
-        }
         mAttachedToWindow = false;
+        if (mRouteTypes != 0) {
+            mRouter.removeCallback(mCallback);
+        }
+
         super.onDetachedFromWindow();
     }
 
@@ -389,93 +377,71 @@
         final int drawLeft = left + (right - left - drawWidth) / 2;
         final int drawTop = top + (bottom - top - drawHeight) / 2;
 
-        mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight);
+        mRemoteIndicator.setBounds(drawLeft, drawTop,
+                drawLeft + drawWidth, drawTop + drawHeight);
         mRemoteIndicator.draw(canvas);
     }
 
-    public void setExtendedSettingsClickListener(OnClickListener listener) {
-        mExtendedSettingsClickListener = listener;
-        if (mDialogFragment != null) {
-            mDialogFragment.setExtendedSettingsClickListener(listener);
-        }
-    }
+    private void refreshRoute() {
+        if (mAttachedToWindow) {
+            final MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
+            final boolean isRemote = !route.isDefault() && route.matchesTypes(mRouteTypes);
+            final boolean isConnecting = isRemote && route.isConnecting();
 
-    /**
-     * Asynchronously show the route chooser dialog.
-     * This will attach a {@link DialogFragment} to the containing Activity.
-     */
-    public void showDialog() {
-        final FragmentManager fm = getActivity().getFragmentManager();
-        if (mDialogFragment == null) {
-            // See if one is already attached to this activity.
-            mDialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag(
-                    MediaRouteChooserDialogFragment.FRAGMENT_TAG);
-        }
-        if (mDialogFragment != null) {
-            Log.w(TAG, "showDialog(): Already showing!");
-            return;
-        }
-
-        mDialogFragment = new MediaRouteChooserDialogFragment();
-        mDialogFragment.setExtendedSettingsClickListener(mExtendedSettingsClickListener);
-        mDialogFragment.setLauncherListener(new MediaRouteChooserDialogFragment.LauncherListener() {
-            @Override
-            public void onDetached(MediaRouteChooserDialogFragment detachedFragment) {
-                mDialogFragment = null;
+            boolean needsRefresh = false;
+            if (mRemoteActive != isRemote) {
+                mRemoteActive = isRemote;
+                needsRefresh = true;
             }
-        });
-        mDialogFragment.setRouteTypes(mRouteTypes);
-        mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG);
+            if (mIsConnecting != isConnecting) {
+                mIsConnecting = isConnecting;
+                needsRefresh = true;
+            }
+
+            if (needsRefresh) {
+                refreshDrawableState();
+            }
+
+            setEnabled(mRouter.isRouteAvailable(mRouteTypes,
+                    MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
+        }
     }
 
-    private Activity getActivity() {
-        // Gross way of unwrapping the Activity so we can get the FragmentManager
-        Context context = getContext();
-        while (context instanceof ContextWrapper && !(context instanceof Activity)) {
-            context = ((ContextWrapper) context).getBaseContext();
-        }
-        if (!(context instanceof Activity)) {
-            throw new IllegalStateException("The MediaRouteButton's Context is not an Activity.");
-        }
-
-        return (Activity) context;
-    }
-
-    private class MediaRouteCallback extends MediaRouter.SimpleCallback {
-        @Override
-        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
-            updateRemoteIndicator();
-        }
-
-        @Override
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
-            updateRemoteIndicator();
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, RouteInfo info) {
-            updateRemoteIndicator();
-        }
-
+    private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
         @Override
         public void onRouteAdded(MediaRouter router, RouteInfo info) {
-            updateRouteCount();
+            refreshRoute();
         }
 
         @Override
         public void onRouteRemoved(MediaRouter router, RouteInfo info) {
-            updateRouteCount();
+            refreshRoute();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, RouteInfo info) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+            refreshRoute();
+        }
+
+        @Override
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+            refreshRoute();
         }
 
         @Override
         public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
                 int index) {
-            updateRouteCount();
+            refreshRoute();
         }
 
         @Override
         public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
-            updateRouteCount();
+            refreshRoute();
         }
     }
 }
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
new file mode 100644
index 0000000..b963c74
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2013 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.app;
+
+import com.android.internal.R;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteInfo;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * This class implements the route chooser dialog for {@link MediaRouter}.
+ * <p>
+ * This dialog allows the user to choose a route that matches a given selector.
+ * </p>
+ *
+ * @see MediaRouteButton
+ * @see MediaRouteActionProvider
+ *
+ * TODO: Move this back into the API, as in the support library media router.
+ */
+public class MediaRouteChooserDialog extends Dialog {
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
+
+    private int mRouteTypes;
+    private View.OnClickListener mExtendedSettingsClickListener;
+    private RouteAdapter mAdapter;
+    private ListView mListView;
+    private Button mExtendedSettingsButton;
+    private boolean mAttachedToWindow;
+
+    public MediaRouteChooserDialog(Context context, int theme) {
+        super(context, theme);
+
+        mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        mCallback = new MediaRouterCallback();
+    }
+
+    /**
+     * Gets the media route types for filtering the routes that the user can
+     * select using the media route chooser dialog.
+     *
+     * @return The route types.
+     */
+    public int getRouteTypes() {
+        return mRouteTypes;
+    }
+
+    /**
+     * Sets the types of routes that will be shown in the media route chooser dialog
+     * launched by this button.
+     *
+     * @param types The route types to match.
+     */
+    public void setRouteTypes(int types) {
+        if (mRouteTypes != types) {
+            mRouteTypes = types;
+
+            if (mAttachedToWindow) {
+                mRouter.removeCallback(mCallback);
+                mRouter.addCallback(types, mCallback,
+                        MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+            }
+
+            refreshRoutes();
+        }
+    }
+
+    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+        if (listener != mExtendedSettingsClickListener) {
+            mExtendedSettingsClickListener = listener;
+            updateExtendedSettingsButton();
+        }
+    }
+
+    /**
+     * Returns true if the route should be included in the list.
+     * <p>
+     * The default implementation returns true for non-default routes that
+     * match the selector.  Subclasses can override this method to filter routes
+     * differently.
+     * </p>
+     *
+     * @param route The route to consider, never null.
+     * @return True if the route should be included in the chooser dialog.
+     */
+    public boolean onFilterRoute(MediaRouter.RouteInfo route) {
+        return !route.isDefault() && route.matchesTypes(mRouteTypes);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
+
+        setContentView(R.layout.media_route_chooser_dialog);
+        setTitle(R.string.media_route_chooser_title);
+
+        // Must be called after setContentView.
+        getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
+                R.drawable.ic_media_route_off_holo_dark);
+
+        mAdapter = new RouteAdapter(getContext());
+        mListView = (ListView)findViewById(R.id.media_route_list);
+        mListView.setAdapter(mAdapter);
+        mListView.setOnItemClickListener(mAdapter);
+        mListView.setEmptyView(findViewById(android.R.id.empty));
+
+        mExtendedSettingsButton = (Button)findViewById(R.id.media_route_extended_settings_button);
+        updateExtendedSettingsButton();
+    }
+
+    private void updateExtendedSettingsButton() {
+        if (mExtendedSettingsButton != null) {
+            mExtendedSettingsButton.setOnClickListener(mExtendedSettingsClickListener);
+            mExtendedSettingsButton.setVisibility(
+                    mExtendedSettingsClickListener != null ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mAttachedToWindow = true;
+        mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+        refreshRoutes();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mAttachedToWindow = false;
+        mRouter.removeCallback(mCallback);
+
+        super.onDetachedFromWindow();
+    }
+
+    /**
+     * Refreshes the list of routes that are shown in the chooser dialog.
+     */
+    public void refreshRoutes() {
+        if (mAttachedToWindow) {
+            mAdapter.update();
+        }
+    }
+
+    private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo>
+            implements ListView.OnItemClickListener {
+        private final LayoutInflater mInflater;
+
+        public RouteAdapter(Context context) {
+            super(context, 0);
+            mInflater = LayoutInflater.from(context);
+        }
+
+        public void update() {
+            clear();
+            final int count = mRouter.getRouteCount();
+            for (int i = 0; i < count; i++) {
+                MediaRouter.RouteInfo route = mRouter.getRouteAt(i);
+                if (onFilterRoute(route)) {
+                    add(route);
+                }
+            }
+            sort(RouteComparator.sInstance);
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return getItem(position).isEnabled();
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View view = convertView;
+            if (view == null) {
+                view = mInflater.inflate(R.layout.media_route_list_item, parent, false);
+            }
+            MediaRouter.RouteInfo route = getItem(position);
+            TextView text1 = (TextView)view.findViewById(android.R.id.text1);
+            TextView text2 = (TextView)view.findViewById(android.R.id.text2);
+            text1.setText(route.getName());
+            CharSequence description = route.getDescription();
+            if (TextUtils.isEmpty(description)) {
+                text2.setVisibility(View.GONE);
+                text2.setText("");
+            } else {
+                text2.setVisibility(View.VISIBLE);
+                text2.setText(description);
+            }
+            view.setEnabled(route.isEnabled());
+            return view;
+        }
+
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            MediaRouter.RouteInfo route = getItem(position);
+            if (route.isEnabled()) {
+                route.select();
+                dismiss();
+            }
+        }
+    }
+
+    private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
+        @Override
+        public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoutes();
+        }
+
+        @Override
+        public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoutes();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+            refreshRoutes();
+        }
+
+        @Override
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+            dismiss();
+        }
+    }
+
+    private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
+        public static final RouteComparator sInstance = new RouteComparator();
+
+        @Override
+        public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
+            return lhs.getName().toString().compareTo(rhs.getName().toString());
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index 268dcf6..ae362af 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 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.
@@ -16,675 +16,86 @@
 
 package com.android.internal.app;
 
-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.hardware.display.DisplayManager;
-import android.media.MediaRouter;
-import android.media.MediaRouter.RouteCategory;
-import android.media.MediaRouter.RouteGroup;
-import android.media.MediaRouter.RouteInfo;
 import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.CheckBox;
-import android.widget.Checkable;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
+import android.view.View.OnClickListener;
 
 /**
- * This class implements the route chooser dialog for {@link MediaRouter}.
+ * Media route chooser dialog fragment.
+ * <p>
+ * Creates a {@link MediaRouteChooserDialog}.  The application may subclass
+ * this dialog fragment to customize the media route chooser dialog.
+ * </p>
  *
- * @see MediaRouteButton
- * @see MediaRouteActionProvider
+ * TODO: Move this back into the API, as in the support library media router.
  */
 public class MediaRouteChooserDialogFragment extends DialogFragment {
-    private static final String TAG = "MediaRouteChooserDialogFragment";
-    public static final String FRAGMENT_TAG = "android:MediaRouteChooserDialogFragment";
+    private final String ARGUMENT_ROUTE_TYPES = "routeTypes";
 
-    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,
-        R.layout.media_route_list_item_checkable,
-        R.layout.media_route_list_item_collapse_group
-    };
+    private View.OnClickListener mExtendedSettingsClickListener;
 
-    MediaRouter mRouter;
-    private int mRouteTypes;
-
-    private LayoutInflater mInflater;
-    private LauncherListener mLauncherListener;
-    private View.OnClickListener mExtendedSettingsListener;
-    private RouteAdapter mAdapter;
-    private ListView mListView;
-    private SeekBar mVolumeSlider;
-    private ImageView mVolumeIcon;
-
-    final RouteComparator mComparator = new RouteComparator();
-    final MediaRouterCallback mCallback = new MediaRouterCallback();
-    private boolean mIgnoreSliderVolumeChanges;
-    private boolean mIgnoreCallbackVolumeChanges;
-
+    /**
+     * Creates a media route chooser dialog fragment.
+     * <p>
+     * All subclasses of this class must also possess a default constructor.
+     * </p>
+     */
     public MediaRouteChooserDialogFragment() {
-        setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog);
+        setCancelable(true);
+        setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog);
     }
 
-    public void setLauncherListener(LauncherListener listener) {
-        mLauncherListener = listener;
-    }
-
-    @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE);
-        mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
-    }
-
-    @Override
-    public void onDetach() {
-        super.onDetach();
-        if (mLauncherListener != null) {
-            mLauncherListener.onDetached(this);
-        }
-        if (mAdapter != null) {
-            mAdapter = null;
-        }
-        mInflater = null;
-        mRouter.removeCallback(mCallback);
-        mRouter = null;
-    }
-
-    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
-        mExtendedSettingsListener = listener;
+    public int getRouteTypes() {
+        Bundle args = getArguments();
+        return args != null ? args.getInt(ARGUMENT_ROUTE_TYPES) : 0;
     }
 
     public void setRouteTypes(int types) {
-        mRouteTypes = types;
-    }
-
-    void updateVolume() {
-        if (mRouter == null) return;
-
-        final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
-        mVolumeIcon.setImageResource(selectedRoute == null ||
-                selectedRoute.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_LOCAL ?
-                R.drawable.ic_audio_vol : R.drawable.ic_media_route_on_holo_dark);
-
-        mIgnoreSliderVolumeChanges = true;
-
-        if (selectedRoute == null ||
-                selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_FIXED) {
-            // Disable the slider and show it at max volume.
-            mVolumeSlider.setMax(1);
-            mVolumeSlider.setProgress(1);
-            mVolumeSlider.setEnabled(false);
-        } else {
-            mVolumeSlider.setEnabled(true);
-            mVolumeSlider.setMax(selectedRoute.getVolumeMax());
-            mVolumeSlider.setProgress(selectedRoute.getVolume());
-        }
-
-        mIgnoreSliderVolumeChanges = false;
-    }
-
-    void changeVolume(int newValue) {
-        if (mIgnoreSliderVolumeChanges) return;
-
-        final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
-        if (selectedRoute != null &&
-                selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
-            final int maxVolume = selectedRoute.getVolumeMax();
-            newValue = Math.max(0, Math.min(newValue, maxVolume));
-            selectedRoute.requestSetVolume(newValue);
-        }
-    }
-
-    @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);
-
-        mVolumeIcon = (ImageView) layout.findViewById(R.id.volume_icon);
-        mVolumeSlider = (SeekBar) layout.findViewById(R.id.volume_slider);
-        updateVolume();
-        mVolumeSlider.setOnSeekBarChangeListener(new VolumeSliderChangeListener());
-
-        if (mExtendedSettingsListener != null) {
-            final View extendedSettingsButton = layout.findViewById(R.id.extended_settings);
-            extendedSettingsButton.setVisibility(View.VISIBLE);
-            extendedSettingsButton.setOnClickListener(mExtendedSettingsListener);
-        }
-
-        final ListView list = (ListView) layout.findViewById(R.id.list);
-        list.setItemsCanFocus(true);
-        list.setAdapter(mAdapter = new RouteAdapter());
-        list.setOnItemClickListener(mAdapter);
-
-        mListView = list;
-
-        mAdapter.scrollToSelectedItem();
-
-        return layout;
-    }
-
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        return new RouteChooserDialog(getActivity(), getTheme());
-    }
-
-    private static class ViewHolder {
-        public TextView text1;
-        public TextView text2;
-        public ImageView icon;
-        public ImageButton expandGroupButton;
-        public RouteAdapter.ExpandGroupListener expandGroupListener;
-        public int position;
-        public CheckBox check;
-    }
-
-    private class RouteAdapter extends BaseAdapter implements ListView.OnItemClickListener {
-        private static final int VIEW_TOP_HEADER = 0;
-        private static final int VIEW_SECTION_HEADER = 1;
-        private static final int VIEW_ROUTE = 2;
-        private static final int VIEW_GROUPING_ROUTE = 3;
-        private static final int VIEW_GROUPING_DONE = 4;
-
-        private int mSelectedItemPosition = -1;
-        private final ArrayList<Object> mItems = new ArrayList<Object>();
-
-        private RouteCategory mCategoryEditingGroups;
-        private RouteGroup mEditingGroup;
-
-        // Temporary lists for manipulation
-        private final ArrayList<RouteInfo> mCatRouteList = new ArrayList<RouteInfo>();
-        private final ArrayList<RouteInfo> mSortRouteList = new ArrayList<RouteInfo>();
-
-        private boolean mIgnoreUpdates;
-
-        RouteAdapter() {
-            update();
-        }
-
-        void update() {
-            /*
-             * This is kind of wacky, but our data sets are going to be
-             * fairly small on average. Ideally we should be able to do some of this stuff
-             * in-place instead.
-             *
-             * Basic idea: each entry in mItems represents an item in the list for quick access.
-             * Entries can be a RouteCategory (section header), a RouteInfo with a category of
-             * mCategoryEditingGroups (a flattened RouteInfo pulled out of its group, allowing
-             * the user to change the group),
-             */
-            if (mIgnoreUpdates) return;
-
-            mItems.clear();
-
-            final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
-            mSelectedItemPosition = -1;
-
-            List<RouteInfo> routes;
-            final int catCount = mRouter.getCategoryCount();
-            for (int i = 0; i < catCount; i++) {
-                final RouteCategory cat = mRouter.getCategoryAt(i);
-                routes = cat.getRoutes(mCatRouteList);
-
-                if (!cat.isSystem()) {
-                    mItems.add(cat);
-                }
-
-                if (cat == mCategoryEditingGroups) {
-                    addGroupEditingCategoryRoutes(routes);
-                } else {
-                    addSelectableRoutes(selectedRoute, routes);
-                }
-
-                routes.clear();
+        if (types != getRouteTypes()) {
+            Bundle args = getArguments();
+            if (args == null) {
+                args = new Bundle();
             }
+            args.putInt(ARGUMENT_ROUTE_TYPES, types);
+            setArguments(args);
 
-            notifyDataSetChanged();
-            if (mListView != null && mSelectedItemPosition >= 0) {
-                mListView.setItemChecked(mSelectedItemPosition, true);
-            }
-        }
-
-        void scrollToEditingGroup() {
-            if (mCategoryEditingGroups == null || mListView == null) return;
-
-            int pos = 0;
-            int bound = 0;
-            final int itemCount = mItems.size();
-            for (int i = 0; i < itemCount; i++) {
-                final Object item = mItems.get(i);
-                if (item != null && item == mCategoryEditingGroups) {
-                    bound = i;
-                }
-                if (item == null) {
-                    pos = i;
-                    break; // this is always below the category header; we can stop here.
-                }
-            }
-
-            mListView.smoothScrollToPosition(pos, bound);
-        }
-
-        void scrollToSelectedItem() {
-            if (mListView == null || mSelectedItemPosition < 0) return;
-
-            mListView.smoothScrollToPosition(mSelectedItemPosition);
-        }
-
-        void addSelectableRoutes(RouteInfo selectedRoute, List<RouteInfo> from) {
-            final int routeCount = from.size();
-            for (int j = 0; j < routeCount; j++) {
-                final RouteInfo info = from.get(j);
-                if (info == selectedRoute) {
-                    mSelectedItemPosition = mItems.size();
-                }
-                mItems.add(info);
-            }
-        }
-
-        void addGroupEditingCategoryRoutes(List<RouteInfo> from) {
-            // Unpack groups and flatten for presentation
-            // mSortRouteList will always be empty here.
-            final int topCount = from.size();
-            for (int i = 0; i < topCount; i++) {
-                final RouteInfo route = from.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);
-                        mSortRouteList.add(innerRoute);
-                    }
-                } else {
-                    mSortRouteList.add(route);
-                }
-            }
-            // Sort by name. This will keep the route positions relatively stable even though they
-            // will be repeatedly added and removed.
-            Collections.sort(mSortRouteList, mComparator);
-
-            mItems.addAll(mSortRouteList);
-            mSortRouteList.clear();
-
-            mItems.add(null); // Sentinel reserving space for the "done" button.
-        }
-
-        @Override
-        public int getCount() {
-            return mItems.size();
-        }
-
-        @Override
-        public int getViewTypeCount() {
-            return 5;
-        }
-
-        @Override
-        public int getItemViewType(int position) {
-            final Object item = getItem(position);
-            if (item instanceof RouteCategory) {
-                return position == 0 ? VIEW_TOP_HEADER : VIEW_SECTION_HEADER;
-            } else if (item == null) {
-                return VIEW_GROUPING_DONE;
-            } else {
-                final RouteInfo info = (RouteInfo) item;
-                if (info.getCategory() == mCategoryEditingGroups) {
-                    return VIEW_GROUPING_ROUTE;
-                }
-                return VIEW_ROUTE;
-            }
-        }
-
-        @Override
-        public boolean areAllItemsEnabled() {
-            return false;
-        }
-
-        @Override
-        public boolean isEnabled(int position) {
-            switch (getItemViewType(position)) {
-                case VIEW_ROUTE:
-                    return ((RouteInfo) mItems.get(position)).isEnabled();
-                case VIEW_GROUPING_ROUTE:
-                case VIEW_GROUPING_DONE:
-                    return true;
-                default:
-                    return false;
-            }
-        }
-
-        @Override
-        public Object getItem(int position) {
-            return mItems.get(position);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final int viewType = getItemViewType(position);
-
-            ViewHolder holder;
-            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.check = (CheckBox) convertView.findViewById(R.id.check);
-                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;
-            }
-
-            switch (viewType) {
-                case VIEW_ROUTE:
-                case VIEW_GROUPING_ROUTE:
-                    bindItemView(position, holder);
-                    break;
-                case VIEW_SECTION_HEADER:
-                case VIEW_TOP_HEADER:
-                    bindHeaderView(position, holder);
-                    break;
-            }
-
-            convertView.setActivated(position == mSelectedItemPosition);
-            convertView.setEnabled(isEnabled(position));
-
-            return convertView;
-        }
-
-        void bindItemView(int position, ViewHolder holder) {
-            RouteInfo info = (RouteInfo) mItems.get(position);
-            holder.text1.setText(info.getName(getActivity()));
-            final CharSequence status = info.getStatus();
-            if (TextUtils.isEmpty(status)) {
-                holder.text2.setVisibility(View.GONE);
-            } else {
-                holder.text2.setVisibility(View.VISIBLE);
-                holder.text2.setText(status);
-            }
-            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 == mCategoryEditingGroups) {
-                RouteGroup group = info.getGroup();
-                holder.check.setEnabled(group.getRouteCount() > 1);
-                holder.check.setChecked(group == mEditingGroup);
-            } else {
-                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);
-                }
-            }
-
-            if (holder.expandGroupButton != null) {
-                holder.expandGroupButton.setVisibility(canGroup ? View.VISIBLE : View.GONE);
-                holder.expandGroupListener.position = position;
-            }
-        }
-
-        void bindHeaderView(int position, ViewHolder holder) {
-            RouteCategory cat = (RouteCategory) mItems.get(position);
-            holder.text1.setText(cat.getName(getActivity()));
-        }
-
-        @Override
-        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            final int type = getItemViewType(position);
-            if (type == VIEW_SECTION_HEADER || type == VIEW_TOP_HEADER) {
-                return;
-            } else if (type == VIEW_GROUPING_DONE) {
-                finishGrouping();
-                return;
-            } else {
-                final Object item = getItem(position);
-                if (!(item instanceof RouteInfo)) {
-                    // Oops. Stale event running around? Skip it.
-                    return;
-                }
-
-                final RouteInfo route = (RouteInfo) item;
-                if (type == VIEW_ROUTE) {
-                    mRouter.selectRouteInt(mRouteTypes, route, true);
-                    dismiss();
-                } else if (type == VIEW_GROUPING_ROUTE) {
-                    final Checkable c = (Checkable) view;
-                    final boolean wasChecked = c.isChecked();
-
-                    mIgnoreUpdates = true;
-                    RouteGroup oldGroup = route.getGroup();
-                    if (!wasChecked && oldGroup != mEditingGroup) {
-                        // Assumption: in a groupable category oldGroup will never be null.
-                        if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) {
-                            // Old group was selected but is now empty. Select the group
-                            // we're manipulating since that's where the last route went.
-                            mRouter.selectRouteInt(mRouteTypes, mEditingGroup, true);
-                        }
-                        oldGroup.removeRoute(route);
-                        mEditingGroup.addRoute(route);
-                        c.setChecked(true);
-                    } else if (wasChecked && mEditingGroup.getRouteCount() > 1) {
-                        mEditingGroup.removeRoute(route);
-
-                        // In a groupable category this will add
-                        // the route into its own new group.
-                        mRouter.addRouteInt(route);
-                    }
-                    mIgnoreUpdates = false;
-                    update();
-                }
-            }
-        }
-
-        boolean isGrouping() {
-            return mCategoryEditingGroups != null;
-        }
-
-        void finishGrouping() {
-            mCategoryEditingGroups = null;
-            mEditingGroup = null;
-            getDialog().setCanceledOnTouchOutside(true);
-            update();
-            scrollToSelectedItem();
-        }
-
-        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.
-                final RouteGroup group = (RouteGroup) getItem(position);
-                mEditingGroup = group;
-                mCategoryEditingGroups = group.getCategory();
-                getDialog().setCanceledOnTouchOutside(false);
-                mRouter.selectRouteInt(mRouteTypes, mEditingGroup, true);
-                update();
-                scrollToEditingGroup();
+            MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog();
+            if (dialog != null) {
+                dialog.setRouteTypes(types);
             }
         }
     }
 
-    class MediaRouterCallback extends MediaRouter.Callback {
-        @Override
-        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
-            mAdapter.update();
-            updateVolume();
-        }
+    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+        if (listener != mExtendedSettingsClickListener) {
+            mExtendedSettingsClickListener = listener;
 
-        @Override
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
-            mAdapter.update();
-        }
-
-        @Override
-        public void onRouteAdded(MediaRouter router, RouteInfo info) {
-            mAdapter.update();
-        }
-
-        @Override
-        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
-            if (info == mAdapter.mEditingGroup) {
-                mAdapter.finishGrouping();
-            }
-            mAdapter.update();
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, RouteInfo info) {
-            mAdapter.notifyDataSetChanged();
-        }
-
-        @Override
-        public void onRouteGrouped(MediaRouter router, RouteInfo info,
-                RouteGroup group, int index) {
-            mAdapter.update();
-        }
-
-        @Override
-        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
-            mAdapter.update();
-        }
-
-        @Override
-        public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
-            if (!mIgnoreCallbackVolumeChanges) {
-                updateVolume();
-            }
-        }
-    }
-
-    class RouteComparator implements Comparator<RouteInfo> {
-        @Override
-        public int compare(RouteInfo lhs, RouteInfo rhs) {
-            return lhs.getName(getActivity()).toString()
-                    .compareTo(rhs.getName(getActivity()).toString());
-        }
-    }
-
-    class RouteChooserDialog extends Dialog {
-        public RouteChooserDialog(Context context, int theme) {
-            super(context, theme);
-        }
-
-        @Override
-        public void onBackPressed() {
-            if (mAdapter != null && mAdapter.isGrouping()) {
-                mAdapter.finishGrouping();
-            } else {
-                super.onBackPressed();
-            }
-        }
-        
-        public boolean onKeyDown(int keyCode, KeyEvent event) {
-            if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) {
-                final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
-                if (selectedRoute != null) {
-                    selectedRoute.requestUpdateVolume(-1);
-                    return true;
-                }
-            } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) {
-                final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
-                if (selectedRoute != null) {
-                    mRouter.getSelectedRoute(mRouteTypes).requestUpdateVolume(1);
-                    return true;
-                }
-            }
-            return super.onKeyDown(keyCode, event);
-        }
-
-        public boolean onKeyUp(int keyCode, KeyEvent event) {
-            if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) {
-                return true;
-            } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) {
-                return true;
-            } else {
-                return super.onKeyUp(keyCode, event);
+            MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog();
+            if (dialog != null) {
+                dialog.setExtendedSettingsClickListener(listener);
             }
         }
     }
 
     /**
-     * Implemented by the MediaRouteButton that launched this dialog
+     * Called when the chooser dialog is being created.
+     * <p>
+     * Subclasses may override this method to customize the dialog.
+     * </p>
      */
-    public interface LauncherListener {
-        public void onDetached(MediaRouteChooserDialogFragment detachedFragment);
+    public MediaRouteChooserDialog onCreateChooserDialog(
+            Context context, Bundle savedInstanceState) {
+        return new MediaRouteChooserDialog(context, getTheme());
     }
 
-    class VolumeSliderChangeListener implements SeekBar.OnSeekBarChangeListener {
-
-        @Override
-        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-            changeVolume(progress);
-        }
-
-        @Override
-        public void onStartTrackingTouch(SeekBar seekBar) {
-            mIgnoreCallbackVolumeChanges = true;
-        }
-
-        @Override
-        public void onStopTrackingTouch(SeekBar seekBar) {
-            mIgnoreCallbackVolumeChanges = false;
-            updateVolume();
-        }
-
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        MediaRouteChooserDialog dialog = onCreateChooserDialog(getActivity(), savedInstanceState);
+        dialog.setRouteTypes(getRouteTypes());
+        dialog.setExtendedSettingsClickListener(mExtendedSettingsClickListener);
+        return dialog;
     }
 }
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
new file mode 100644
index 0000000..8fc99c7
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2013 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.app;
+
+import com.android.internal.R;
+
+import android.app.Dialog;
+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.RouteGroup;
+import android.media.MediaRouter.RouteInfo;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+/**
+ * This class implements the route controller dialog for {@link MediaRouter}.
+ * <p>
+ * This dialog allows the user to control or disconnect from the currently selected route.
+ * </p>
+ *
+ * @see MediaRouteButton
+ * @see MediaRouteActionProvider
+ *
+ * TODO: Move this back into the API, as in the support library media router.
+ */
+public class MediaRouteControllerDialog extends Dialog {
+    // Time to wait before updating the volume when the user lets go of the seek bar
+    // to allow the route provider time to propagate the change and publish a new
+    // route descriptor.
+    private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
+
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mCallback;
+    private final MediaRouter.RouteInfo mRoute;
+
+    private boolean mCreated;
+    private Drawable mMediaRouteConnectingDrawable;
+    private Drawable mMediaRouteOnDrawable;
+    private Drawable mCurrentIconDrawable;
+
+    private boolean mVolumeControlEnabled = true;
+    private LinearLayout mVolumeLayout;
+    private SeekBar mVolumeSlider;
+    private boolean mVolumeSliderTouched;
+
+    private View mControlView;
+
+    private Button mDisconnectButton;
+
+    public MediaRouteControllerDialog(Context context, int theme) {
+        super(context, theme);
+
+        mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        mCallback = new MediaRouterCallback();
+        mRoute = mRouter.getSelectedRoute();
+    }
+
+    /**
+     * Gets the route that this dialog is controlling.
+     */
+    public MediaRouter.RouteInfo getRoute() {
+        return mRoute;
+    }
+
+    /**
+     * Provides the subclass an opportunity to create a view that will
+     * be included within the body of the dialog to offer additional media controls
+     * for the currently playing content.
+     *
+     * @param savedInstanceState The dialog's saved instance state.
+     * @return The media control view, or null if none.
+     */
+    public View onCreateMediaControlView(Bundle savedInstanceState) {
+        return null;
+    }
+
+    /**
+     * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}.
+     *
+     * @return The media control view, or null if none.
+     */
+    public View getMediaControlView() {
+        return mControlView;
+    }
+
+    /**
+     * Sets whether to enable the volume slider and volume control using the volume keys
+     * when the route supports it.
+     * <p>
+     * The default value is true.
+     * </p>
+     */
+    public void setVolumeControlEnabled(boolean enable) {
+        if (mVolumeControlEnabled != enable) {
+            mVolumeControlEnabled = enable;
+            if (mCreated) {
+                updateVolume();
+            }
+        }
+    }
+
+    /**
+     * Returns whether to enable the volume slider and volume control using the volume keys
+     * when the route supports it.
+     */
+    public boolean isVolumeControlEnabled() {
+        return mVolumeControlEnabled;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
+
+        setContentView(R.layout.media_route_controller_dialog);
+
+        mVolumeLayout = (LinearLayout)findViewById(R.id.media_route_volume_layout);
+        mVolumeSlider = (SeekBar)findViewById(R.id.media_route_volume_slider);
+        mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            private final Runnable mStopTrackingTouch = new Runnable() {
+                @Override
+                public void run() {
+                    if (mVolumeSliderTouched) {
+                        mVolumeSliderTouched = false;
+                        updateVolume();
+                    }
+                }
+            };
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+                if (mVolumeSliderTouched) {
+                    mVolumeSlider.removeCallbacks(mStopTrackingTouch);
+                } else {
+                    mVolumeSliderTouched = true;
+                }
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                // Defer resetting mVolumeSliderTouched to allow the media route provider
+                // a little time to settle into its new state and publish the final
+                // volume update.
+                mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
+            }
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (fromUser) {
+                    mRoute.requestSetVolume(progress);
+                }
+            }
+        });
+
+        mDisconnectButton = (Button)findViewById(R.id.media_route_disconnect_button);
+        mDisconnectButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mRoute.isSelected()) {
+                    mRouter.getDefaultRoute().select();
+                }
+                dismiss();
+            }
+        });
+
+        mCreated = true;
+        if (update()) {
+            mControlView = onCreateMediaControlView(savedInstanceState);
+            FrameLayout controlFrame =
+                    (FrameLayout)findViewById(R.id.media_route_control_frame);
+            if (mControlView != null) {
+                controlFrame.addView(mControlView);
+                controlFrame.setVisibility(View.VISIBLE);
+            } else {
+                controlFrame.setVisibility(View.GONE);
+            }
+        }
+    }
+
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mRouter.addCallback(0, mCallback, MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
+        update();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mRouter.removeCallback(mCallback);
+
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+            mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1);
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    private boolean update() {
+        if (!mRoute.isSelected() || mRoute.isDefault()) {
+            dismiss();
+            return false;
+        }
+
+        setTitle(mRoute.getName());
+        updateVolume();
+
+        Drawable icon = getIconDrawable();
+        if (icon != mCurrentIconDrawable) {
+            mCurrentIconDrawable = icon;
+            getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, icon);
+        }
+        return true;
+    }
+
+    private Drawable getIconDrawable() {
+        if (mRoute.isConnecting()) {
+            if (mMediaRouteConnectingDrawable == null) {
+                mMediaRouteConnectingDrawable = getContext().getResources().getDrawable(
+                        R.drawable.ic_media_route_connecting_holo_dark);
+            }
+            return mMediaRouteConnectingDrawable;
+        } else {
+            if (mMediaRouteOnDrawable == null) {
+                mMediaRouteOnDrawable = getContext().getResources().getDrawable(
+                        R.drawable.ic_media_route_on_holo_dark);
+            }
+            return mMediaRouteOnDrawable;
+        }
+    }
+
+    private void updateVolume() {
+        if (!mVolumeSliderTouched) {
+            if (isVolumeControlAvailable()) {
+                mVolumeLayout.setVisibility(View.VISIBLE);
+                mVolumeSlider.setMax(mRoute.getVolumeMax());
+                mVolumeSlider.setProgress(mRoute.getVolume());
+            } else {
+                mVolumeLayout.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    private boolean isVolumeControlAvailable() {
+        return mVolumeControlEnabled && mRoute.getVolumeHandling() ==
+                MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+    }
+
+    private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
+        @Override
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+            update();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+            update();
+        }
+
+        @Override
+        public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
+            if (route == mRoute) {
+                updateVolume();
+            }
+        }
+
+        @Override
+        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
+                int index) {
+            update();
+        }
+
+        @Override
+        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
+            update();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java
new file mode 100644
index 0000000..108e81f
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 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.app;
+
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * Media route controller dialog fragment.
+ * <p>
+ * Creates a {@link MediaRouteControllerDialog}.  The application may subclass
+ * this dialog fragment to customize the media route controller dialog.
+ * </p>
+ *
+ * TODO: Move this back into the API, as in the support library media router.
+ */
+public class MediaRouteControllerDialogFragment extends DialogFragment {
+    /**
+     * Creates a media route controller dialog fragment.
+     * <p>
+     * All subclasses of this class must also possess a default constructor.
+     * </p>
+     */
+    public MediaRouteControllerDialogFragment() {
+        setCancelable(true);
+        setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog);
+    }
+
+    /**
+     * Called when the controller dialog is being created.
+     * <p>
+     * Subclasses may override this method to customize the dialog.
+     * </p>
+     */
+    public MediaRouteControllerDialog onCreateControllerDialog(
+            Context context, Bundle savedInstanceState) {
+        return new MediaRouteControllerDialog(context, getTheme());
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return onCreateControllerDialog(getActivity(), savedInstanceState);
+    }
+}
diff --git a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
new file mode 100644
index 0000000..fad7fd4
--- /dev/null
+++ b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 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.app;
+
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.media.MediaRouter;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * Shows media route dialog as appropriate.
+ * @hide
+ */
+public abstract class MediaRouteDialogPresenter {
+    private static final String TAG = "MediaRouter";
+
+    private static final String CHOOSER_FRAGMENT_TAG =
+            "android.app.MediaRouteButton:MediaRouteChooserDialogFragment";
+    private static final String CONTROLLER_FRAGMENT_TAG =
+            "android.app.MediaRouteButton:MediaRouteControllerDialogFragment";
+
+    public static DialogFragment showDialogFragment(Activity activity,
+            int routeTypes, View.OnClickListener extendedSettingsClickListener) {
+        final MediaRouter router = (MediaRouter)activity.getSystemService(
+                Context.MEDIA_ROUTER_SERVICE);
+        final FragmentManager fm = activity.getFragmentManager();
+
+        MediaRouter.RouteInfo route = router.getSelectedRoute();
+        if (route.isDefault() || !route.matchesTypes(routeTypes)) {
+            if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) {
+                Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
+                return null;
+            }
+            MediaRouteChooserDialogFragment f = new MediaRouteChooserDialogFragment();
+            f.setRouteTypes(routeTypes);
+            f.setExtendedSettingsClickListener(extendedSettingsClickListener);
+            f.show(fm, CHOOSER_FRAGMENT_TAG);
+            return f;
+        } else {
+            if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) {
+                Log.w(TAG, "showDialog(): Route controller dialog already showing!");
+                return null;
+            }
+            MediaRouteControllerDialogFragment f = new MediaRouteControllerDialogFragment();
+            f.show(fm, CONTROLLER_FRAGMENT_TAG);
+            return f;
+        }
+    }
+
+    public static Dialog createDialog(Context context,
+            int routeTypes, View.OnClickListener extendedSettingsClickListener) {
+        final MediaRouter router = (MediaRouter)context.getSystemService(
+                Context.MEDIA_ROUTER_SERVICE);
+
+        MediaRouter.RouteInfo route = router.getSelectedRoute();
+        if (route.isDefault() || !route.matchesTypes(routeTypes)) {
+            final MediaRouteChooserDialog d = new MediaRouteChooserDialog(
+                    context, android.R.style.Theme_DeviceDefault_Dialog);
+            d.setRouteTypes(routeTypes);
+            d.setExtendedSettingsClickListener(extendedSettingsClickListener);
+            return d;
+        } else {
+            MediaRouteControllerDialog d = new MediaRouteControllerDialog(
+                    context, android.R.style.Theme_DeviceDefault_Dialog);
+            return d;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/CheckableLinearLayout.java b/core/java/com/android/internal/view/CheckableLinearLayout.java
deleted file mode 100644
index 3fb7cec..0000000
--- a/core/java/com/android/internal/view/CheckableLinearLayout.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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/res/res/drawable-hdpi/ic_media_group_collapse.png b/core/res/res/drawable-hdpi/ic_media_group_collapse.png
deleted file mode 100644
index 89abf2c..0000000
--- a/core/res/res/drawable-hdpi/ic_media_group_collapse.png
+++ /dev/null
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
deleted file mode 100644
index d9470b2..0000000
--- a/core/res/res/drawable-hdpi/ic_media_group_expand.png
+++ /dev/null
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
deleted file mode 100644
index 34454ac..0000000
--- a/core/res/res/drawable-mdpi/ic_media_group_collapse.png
+++ /dev/null
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
deleted file mode 100644
index 8ce5a44..0000000
--- a/core/res/res/drawable-mdpi/ic_media_group_expand.png
+++ /dev/null
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
deleted file mode 100644
index 2fb7428..0000000
--- a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png
+++ /dev/null
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
deleted file mode 100644
index 5755b9d..0000000
--- a/core/res/res/drawable-xhdpi/ic_media_group_expand.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/layout/media_route_chooser_dialog.xml b/core/res/res/layout/media_route_chooser_dialog.xml
new file mode 100644
index 0000000..3eba9be
--- /dev/null
+++ b/core/res/res/layout/media_route_chooser_dialog.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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="wrap_content"
+              android:orientation="vertical"
+              android:divider="?android:attr/dividerHorizontal"
+              android:showDividers="middle">
+    <!-- List of routes. -->
+    <ListView android:id="@+id/media_route_list"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content" />
+
+    <!-- Content to show when list is empty. -->
+    <LinearLayout android:id="@android:id/empty"
+              android:layout_width="match_parent"
+              android:layout_height="64dp"
+              android:orientation="horizontal"
+              android:paddingLeft="16dp"
+              android:paddingRight="16dp"
+              android:visibility="gone">
+        <ProgressBar android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
+                     android:layout_gravity="center" />
+        <TextView android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_gravity="center"
+                  android:paddingLeft="16dp"
+                  android:text="@string/media_route_chooser_searching" />
+    </LinearLayout>
+
+    <!-- Settings button. -->
+    <LinearLayout android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  style="?attr/buttonBarStyle">
+        <Button android:id="@+id/media_route_extended_settings_button"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                style="?attr/buttonBarButtonStyle"
+                android:gravity="center"
+                android:text="@string/media_route_chooser_extended_settings"
+                android:visibility="gone" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/media_route_chooser_layout.xml b/core/res/res/layout/media_route_chooser_layout.xml
deleted file mode 100644
index 5fcb8c8..0000000
--- a/core/res/res/layout/media_route_chooser_layout.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?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="wrap_content"
-              android:orientation="vertical"
-              android:showDividers="middle"
-              android:divider="?android:attr/dividerHorizontal">
-    <LinearLayout android:layout_width="match_parent"
-                  android:layout_height="?android:attr/listPreferredItemHeight"
-                  android:gravity="center_vertical"
-                  android:padding="8dp">
-        <ImageView android:id="@+id/volume_icon"
-                   android:layout_width="48dp"
-                   android:layout_height="48dp"
-                   android:src="@android:drawable/ic_audio_vol"
-                   android:gravity="center"
-                   android:scaleType="center" />
-        <SeekBar android:id="@+id/volume_slider"
-                 android:layout_width="0dp"
-                 android:layout_height="wrap_content"
-                 android:layout_weight="1"
-                 android:layout_marginStart="8dp"
-                 android:layout_marginEnd="8dp" />
-        <ImageButton android:id="@+id/extended_settings"
-                     android:layout_width="48dp"
-                     android:layout_height="48dp"
-                     android:background="?android:attr/selectableItemBackground"
-                     android:src="@android:drawable/ic_sysbar_quicksettings"
-                     android:visibility="gone" />
-    </LinearLayout>
-    <ListView android:id="@id/list"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content" />
-</LinearLayout>
diff --git a/core/res/res/layout/media_route_controller_dialog.xml b/core/res/res/layout/media_route_controller_dialog.xml
new file mode 100644
index 0000000..78287e0
--- /dev/null
+++ b/core/res/res/layout/media_route_controller_dialog.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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="wrap_content"
+              android:orientation="vertical"
+              android:divider="?android:attr/dividerHorizontal"
+              android:showDividers="middle">
+    <!-- Optional volume slider section. -->
+    <LinearLayout android:id="@+id/media_route_volume_layout"
+                  android:layout_width="match_parent"
+                  android:layout_height="64dp"
+                  android:gravity="center_vertical"
+                  android:padding="8dp"
+                  android:visibility="gone">
+        <ImageView android:layout_width="48dp"
+                   android:layout_height="48dp"
+                   android:src="@drawable/ic_audio_vol"
+                   android:gravity="center"
+                   android:scaleType="center" />
+        <SeekBar android:id="@+id/media_route_volume_slider"
+                 android:layout_width="0dp"
+                 android:layout_height="wrap_content"
+                 android:layout_weight="1"
+                 android:layout_marginLeft="8dp"
+                 android:layout_marginRight="8dp" />
+    </LinearLayout>
+
+    <!-- Optional content view section. -->
+    <FrameLayout android:id="@+id/media_route_control_frame"
+                 android:layout_width="match_parent"
+                 android:layout_height="wrap_content"
+                 android:visibility="gone" />
+
+    <!-- Disconnect button. -->
+    <LinearLayout android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  style="?attr/buttonBarStyle">
+        <Button android:id="@+id/media_route_disconnect_button"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                style="?attr/buttonBarButtonStyle"
+                android:gravity="center"
+                android:text="@string/media_route_controller_disconnect" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/media_route_list_item.xml b/core/res/res/layout/media_route_list_item.xml
index 423d544..bdca433 100644
--- a/core/res/res/layout/media_route_list_item.xml
+++ b/core/res/res/layout/media_route_list_item.xml
@@ -20,13 +20,6 @@
               android:background="@drawable/item_background_activated_holo_dark"
               android:gravity="center_vertical">
 
-    <ImageView android:layout_width="56dp"
-               android:layout_height="56dp"
-               android:scaleType="center"
-               android:id="@+id/icon"
-               android:visibility="gone"
-               android:duplicateParentState="true" />
-
     <LinearLayout android:layout_width="0dp"
                   android:layout_height="match_parent"
                   android:layout_weight="1"
@@ -53,14 +46,4 @@
                   android:duplicateParentState="true" />
     </LinearLayout>
 
-    <ImageButton
-        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"
-        android:duplicateParentState="true" />
-
 </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
deleted file mode 100644
index 5fb82f7..0000000
--- a/core/res/res/layout/media_route_list_item_checkable.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?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:background="?android:attr/selectableItemBackground"
-              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="start|center_vertical"
-                  android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-                  android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
-
-        <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_marginEnd="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
deleted file mode 100644
index 323e24d..0000000
--- a/core/res/res/layout/media_route_list_item_collapse_group.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:layout_width="match_parent"
-             android:layout_height="wrap_content"
-             android:background="?android:attr/selectableItemBackground">
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="?android:attr/listPreferredItemHeightSmall"
-        android:background="#19ffffff"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        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>
-</FrameLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/media_route_list_item_section_header.xml b/core/res/res/layout/media_route_list_item_section_header.xml
deleted file mode 100644
index 949635f..0000000
--- a/core/res/res/layout/media_route_list_item_section_header.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:layout_width="match_parent"
-             android:layout_height="wrap_content"
-             android:paddingTop="16dp">
-    <TextView
-        android:id="@android:id/text1"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:background="#19ffffff"
-        android:textStyle="bold"
-        android:textAllCaps="true"
-        android:gravity="center_vertical"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:minHeight="24dp"
-        />
-</FrameLayout>
diff --git a/core/res/res/layout/media_route_list_item_top_header.xml b/core/res/res/layout/media_route_list_item_top_header.xml
deleted file mode 100644
index 0c49b24..0000000
--- a/core/res/res/layout/media_route_list_item_top_header.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/text1"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:textAppearance="?android:attr/textAppearanceSmall"
-    android:background="#19ffffff"
-    android:textStyle="bold"
-    android:textAllCaps="true"
-    android:gravity="center_vertical"
-    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-    android:minHeight="24dp"
-/>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 888b0a3..af5ace0 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4096,12 +4096,21 @@
     <!-- Description of a wireless display route. [CHAR LIMIT=50] -->
     <string name="wireless_display_route_description">Wireless display</string>
 
-    <!-- "Done" button for MediaRouter chooser dialog when grouping routes. [CHAR LIMIT=NONE] -->
-    <string name="media_route_chooser_grouping_done">Done</string>
-
     <!-- Content description of a MediaRouteButton for accessibility support -->
     <string name="media_route_button_content_description">Media output</string>
 
+    <!-- Title of the media route chooser dialog. [CHAR LIMIT=30] -->
+    <string name="media_route_chooser_title">Connect to device</string>
+
+    <!-- Placeholder text to show when no devices have been found. [CHAR LIMIT=50] -->
+    <string name="media_route_chooser_searching">Searching for devices\u2026</string>
+
+    <!-- Button to access extended settings.  [CHAR LIMIT=30] -->
+    <string name="media_route_chooser_extended_settings">Settings</string>
+
+    <!-- Button to disconnect from a media route.  [CHAR LIMIT=30] -->
+    <string name="media_route_controller_disconnect">Disconnect</string>
+
     <!-- Status message for remote routes attempting to scan/determine availability -->
     <string name="media_route_status_scanning">Scanning...</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8387c9d..cfdd64c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1255,17 +1255,16 @@
 
   <java-symbol type="attr" name="mediaRouteButtonStyle" />
   <java-symbol type="attr" name="externalRouteEnabledDrawable" />
-  <java-symbol type="id" name="extended_settings" />
-  <java-symbol type="id" name="check" />
-  <java-symbol type="id" name="volume_slider" />
-  <java-symbol type="id" name="volume_icon" />
-  <java-symbol type="drawable" name="ic_media_route_on_holo_dark" />
-  <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_chooser_dialog" />
+  <java-symbol type="layout" name="media_route_controller_dialog" />
   <java-symbol type="layout" name="media_route_list_item" />
-  <java-symbol type="layout" name="media_route_list_item_checkable" />
-  <java-symbol type="layout" name="media_route_list_item_collapse_group" />
+  <java-symbol type="id" name="media_route_list" />
+  <java-symbol type="id" name="media_route_volume_layout" />
+  <java-symbol type="id" name="media_route_volume_slider" />
+  <java-symbol type="id" name="media_route_control_frame" />
+  <java-symbol type="id" name="media_route_disconnect_button" />
+  <java-symbol type="id" name="media_route_extended_settings_button" />
+  <java-symbol type="string" name="media_route_chooser_title" />
   <java-symbol type="string" name="bluetooth_a2dp_audio_route_name" />
 
   <java-symbol type="dimen" name="config_minScalingSpan" />
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index fb62812..27b5451 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -666,6 +666,19 @@
      */
     public static final int CALLBACK_FLAG_PASSIVE_DISCOVERY = 1 << 3;
 
+    /**
+     * Flag for {@link #isRouteAvailable}: Ignore the default route.
+     * <p>
+     * This flag is used to determine whether a matching non-default route is available.
+     * This constraint may be used to decide whether to offer the route chooser dialog
+     * to the user.  There is no point offering the chooser if there are no
+     * non-default choices.
+     * </p>
+     *
+     * @hide Future API ported from support library.  Revisit this later.
+     */
+    public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
+
     // Maps application contexts
     static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
 
@@ -716,6 +729,11 @@
         return sStatic.mSystemCategory;
     }
 
+    /** @hide */
+    public RouteInfo getSelectedRoute() {
+        return getSelectedRoute(ROUTE_TYPE_ANY);
+    }
+
     /**
      * Return the currently selected route for any of the given types
      *
@@ -739,6 +757,38 @@
     }
 
     /**
+     * Returns true if there is a route that matches the specified types.
+     * <p>
+     * This method returns true if there are any available routes that match the types
+     * regardless of whether they are enabled or disabled.  If the
+     * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
+     * the method will only consider non-default routes.
+     * </p>
+     *
+     * @param types The types to match.
+     * @param flags Flags to control the determination of whether a route may be available.
+     * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}.
+     * @return True if a matching route may be available.
+     *
+     * @hide Future API ported from support library.  Revisit this later.
+     */
+    public boolean isRouteAvailable(int types, int flags) {
+        final int count = sStatic.mRoutes.size();
+        for (int i = 0; i < count; i++) {
+            RouteInfo route = sStatic.mRoutes.get(i);
+            if (route.matchesTypes(types)) {
+                if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) == 0
+                        || route != sStatic.mDefaultAudioVideo) {
+                    return true;
+                }
+            }
+        }
+
+        // It doesn't look like we can find a matching route right now.
+        return false;
+    }
+
+    /**
      * Add a callback to listen to events about specific kinds of media routes.
      * If the specified callback is already registered, its registration will be updated for any
      * additional route types specified.
@@ -836,7 +886,7 @@
     static void selectRouteStatic(int types, RouteInfo route, boolean explicit) {
         final RouteInfo oldRoute = sStatic.mSelectedRoute;
         if (oldRoute == route) return;
-        if ((route.getSupportedTypes() & types) == 0) {
+        if (!route.matchesTypes(types)) {
             Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
                     typesToString(route.getSupportedTypes()) + " into route types " +
                     typesToString(types));
@@ -986,7 +1036,7 @@
                     break;
                 }
             }
-            if (info == sStatic.mSelectedRoute) {
+            if (info.isSelected()) {
                 // Removing the currently selected route? Select the default before we remove it.
                 selectDefaultRouteStatic();
             }
@@ -1319,7 +1369,7 @@
             dispatchRouteChanged(route);
         }
 
-        if (!enabled && route == sStatic.mSelectedRoute) {
+        if (!enabled && route.isSelected()) {
             // Oops, no longer available. Reselect the default.
             selectDefaultRouteStatic();
         }
@@ -1526,6 +1576,11 @@
             return mSupportedTypes;
         }
 
+        /** @hide */
+        public boolean matchesTypes(int types) {
+            return (mSupportedTypes & types) != 0;
+        }
+
         /**
          * @return The group that this route belongs to.
          */
@@ -1769,7 +1824,7 @@
             // then report it as connecting even though it has not yet had a chance
             // to move into the CONNECTING state.  Note that routes in the NONE state
             // are assumed to not require an explicit connection lifecycle.
-            if (this == sStatic.mSelectedRoute) {
+            if (isSelected()) {
                 switch (mStatusCode) {
                     case STATUS_AVAILABLE:
                     case STATUS_SCANNING:
@@ -1780,6 +1835,21 @@
             return false;
         }
 
+        /** @hide */
+        public boolean isSelected() {
+            return this == sStatic.mSelectedRoute;
+        }
+
+        /** @hide */
+        public boolean isDefault() {
+            return this == sStatic.mDefaultAudioVideo;
+        }
+
+        /** @hide */
+        public void select() {
+            selectRouteStatic(mSupportedTypes, this, true);
+        }
+
         void setStatusInt(CharSequence status) {
             if (!status.equals(mStatus)) {
                 mStatus = status;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index 2e40ddf..e7b8fa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -37,8 +37,8 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LevelListDrawable;
 import android.hardware.display.DisplayManager;
+import android.media.MediaRouter;
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -61,6 +61,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.internal.app.MediaRouteDialogPresenter;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.QuickSettingsModel.ActivityState;
 import com.android.systemui.statusbar.phone.QuickSettingsModel.BluetoothState;
@@ -676,7 +677,21 @@
         remoteDisplayTile.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                startSettingsActivity(android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS);
+                collapsePanels();
+
+                final Dialog[] dialog = new Dialog[1];
+                dialog[0] = MediaRouteDialogPresenter.createDialog(mContext,
+                        MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
+                        new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        dialog[0].dismiss();
+                        startSettingsActivity(
+                                android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS);
+                    }
+                });
+                dialog[0].getWindow().setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+                dialog[0].show();
             }
         });
         mModel.addRemoteDisplayTile(remoteDisplayTile,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
index 5567e7d..e59dfaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
@@ -684,8 +684,8 @@
     private void updateRemoteDisplays() {
         MediaRouter.RouteInfo connectedRoute = mMediaRouter.getSelectedRoute(
                 MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
-        boolean enabled = connectedRoute != null && (connectedRoute.getSupportedTypes()
-                & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0;
+        boolean enabled = connectedRoute != null
+                && connectedRoute.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
         boolean connecting;
         if (enabled) {
             connecting = connectedRoute.isConnecting();
@@ -695,7 +695,7 @@
             final int count = mMediaRouter.getRouteCount();
             for (int i = 0; i < count; i++) {
                 MediaRouter.RouteInfo route = mMediaRouter.getRouteAt(i);
-                if ((route.getSupportedTypes() & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
+                if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) {
                     enabled = true;
                     break;
                 }