Merge "API updates for MediaRouter" into jb-dev
diff --git a/api/16.txt b/api/16.txt
index eb09d85..e4b098f 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -3679,6 +3679,7 @@
   public class MediaRouteActionProvider extends android.view.ActionProvider {
     ctor public MediaRouteActionProvider(android.content.Context);
     method public android.view.View onCreateActionView();
+    method public void setExtendedSettingsClickListener(android.view.View.OnClickListener);
     method public void setRouteTypes(int);
   }
 
@@ -3687,7 +3688,9 @@
     ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet);
     ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet, int);
     method public int getRouteTypes();
+    method public void setExtendedSettingsClickListener(android.view.View.OnClickListener);
     method public void setRouteTypes(int);
+    method public void showDialog();
   }
 
   public class NativeActivity extends android.app.Activity implements android.view.InputQueue.Callback android.view.SurfaceHolder.Callback2 android.view.ViewTreeObserver.OnGlobalLayoutListener {
@@ -11511,6 +11514,7 @@
     method public void addUserRoute(android.media.MediaRouter.UserRouteInfo);
     method public void clearUserRoutes();
     method public android.media.MediaRouter.RouteCategory createRouteCategory(java.lang.CharSequence, boolean);
+    method public android.media.MediaRouter.RouteCategory createRouteCategory(int, boolean);
     method public android.media.MediaRouter.UserRouteInfo createUserRoute(android.media.MediaRouter.RouteCategory);
     method public android.media.MediaRouter.RouteCategory getCategoryAt(int);
     method public int getCategoryCount();
@@ -11524,7 +11528,8 @@
     field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
   }
 
-  public static abstract interface MediaRouter.Callback {
+  public static abstract class MediaRouter.Callback {
+    ctor public MediaRouter.Callback();
     method public abstract void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int);
@@ -11536,6 +11541,7 @@
 
   public static class MediaRouter.RouteCategory {
     method public java.lang.CharSequence getName();
+    method public java.lang.CharSequence getName(android.content.Context);
     method public java.util.List<android.media.MediaRouter.RouteInfo> getRoutes(java.util.List<android.media.MediaRouter.RouteInfo>);
     method public int getSupportedTypes();
     method public boolean isGroupable();
@@ -11548,17 +11554,21 @@
     method public int getRouteCount();
     method public void removeRoute(android.media.MediaRouter.RouteInfo);
     method public void removeRoute(int);
+    method public void setIconDrawable(android.graphics.drawable.Drawable);
+    method public void setIconResource(int);
   }
 
   public static class MediaRouter.RouteInfo {
     method public android.media.MediaRouter.RouteCategory getCategory();
     method public android.media.MediaRouter.RouteGroup getGroup();
+    method public android.graphics.drawable.Drawable getIconDrawable();
     method public java.lang.CharSequence getName();
+    method public java.lang.CharSequence getName(android.content.Context);
     method public java.lang.CharSequence getStatus();
     method public int getSupportedTypes();
   }
 
-  public static class MediaRouter.SimpleCallback implements android.media.MediaRouter.Callback {
+  public static class MediaRouter.SimpleCallback extends android.media.MediaRouter.Callback {
     ctor public MediaRouter.SimpleCallback();
     method public void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
@@ -11570,8 +11580,14 @@
   }
 
   public static class MediaRouter.UserRouteInfo extends android.media.MediaRouter.RouteInfo {
+    method public java.lang.Object getTag();
+    method public void setIconDrawable(android.graphics.drawable.Drawable);
+    method public void setIconResource(int);
     method public void setName(java.lang.CharSequence);
+    method public void setName(int);
+    method public void setRemoteControlClient(android.media.RemoteControlClient);
     method public void setStatus(java.lang.CharSequence);
+    method public void setTag(java.lang.Object);
   }
 
   public class MediaScannerConnection implements android.content.ServiceConnection {
diff --git a/api/current.txt b/api/current.txt
index a62698e..e4b098f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11514,6 +11514,7 @@
     method public void addUserRoute(android.media.MediaRouter.UserRouteInfo);
     method public void clearUserRoutes();
     method public android.media.MediaRouter.RouteCategory createRouteCategory(java.lang.CharSequence, boolean);
+    method public android.media.MediaRouter.RouteCategory createRouteCategory(int, boolean);
     method public android.media.MediaRouter.UserRouteInfo createUserRoute(android.media.MediaRouter.RouteCategory);
     method public android.media.MediaRouter.RouteCategory getCategoryAt(int);
     method public int getCategoryCount();
@@ -11527,7 +11528,8 @@
     field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
   }
 
-  public static abstract interface MediaRouter.Callback {
+  public static abstract class MediaRouter.Callback {
+    ctor public MediaRouter.Callback();
     method public abstract void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public abstract void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int);
@@ -11539,6 +11541,7 @@
 
   public static class MediaRouter.RouteCategory {
     method public java.lang.CharSequence getName();
+    method public java.lang.CharSequence getName(android.content.Context);
     method public java.util.List<android.media.MediaRouter.RouteInfo> getRoutes(java.util.List<android.media.MediaRouter.RouteInfo>);
     method public int getSupportedTypes();
     method public boolean isGroupable();
@@ -11560,11 +11563,12 @@
     method public android.media.MediaRouter.RouteGroup getGroup();
     method public android.graphics.drawable.Drawable getIconDrawable();
     method public java.lang.CharSequence getName();
+    method public java.lang.CharSequence getName(android.content.Context);
     method public java.lang.CharSequence getStatus();
     method public int getSupportedTypes();
   }
 
-  public static class MediaRouter.SimpleCallback implements android.media.MediaRouter.Callback {
+  public static class MediaRouter.SimpleCallback extends android.media.MediaRouter.Callback {
     ctor public MediaRouter.SimpleCallback();
     method public void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
     method public void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
@@ -11580,6 +11584,7 @@
     method public void setIconDrawable(android.graphics.drawable.Drawable);
     method public void setIconResource(int);
     method public void setName(java.lang.CharSequence);
+    method public void setName(int);
     method public void setRemoteControlClient(android.media.RemoteControlClient);
     method public void setStatus(java.lang.CharSequence);
     method public void setTag(java.lang.Object);
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index a4eebda..018b25d 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -103,7 +103,7 @@
 
         if (mToggleMode) {
             if (mRemoteActive) {
-                mRouter.selectRoute(mRouteTypes, mRouter.getSystemAudioRoute());
+                mRouter.selectRouteInt(mRouteTypes, mRouter.getSystemAudioRoute());
             } else {
                 final int N = mRouter.getRouteCount();
                 for (int i = 0; i < N; i++) {
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index bfcfdfa..6c9a047 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -77,7 +77,7 @@
     private GroupAdapter mGroupAdapter;
     private ListView mListView;
 
-    static final RouteComparator sComparator = new RouteComparator();
+    final RouteComparator mComparator = new RouteComparator();
 
     public MediaRouteChooserDialogFragment() {
         setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog);
@@ -100,11 +100,11 @@
             mLauncherListener.onDetached(this);
         }
         if (mGroupAdapter != null) {
-            mRouter.removeCallback(mGroupAdapter);
+            mRouter.removeCallback(mGroupAdapter.mCallback);
             mGroupAdapter = null;
         }
         if (mAdapter != null) {
-            mRouter.removeCallback(mAdapter);
+            mRouter.removeCallback(mAdapter.mCallback);
             mAdapter = null;
         }
         mInflater = null;
@@ -146,14 +146,14 @@
         list.setOnItemClickListener(mAdapter);
 
         mListView = list;
-        mRouter.addCallback(mRouteTypes, mAdapter);
+        mRouter.addCallback(mRouteTypes, mAdapter.mCallback);
 
         return layout;
     }
 
     void onExpandGroup(RouteGroup info) {
         mGroupAdapter = new GroupAdapter(info);
-        mRouter.addCallback(mRouteTypes, mGroupAdapter);
+        mRouter.addCallback(mRouteTypes, mGroupAdapter.mCallback);
         mListView.setAdapter(mGroupAdapter);
         mListView.setOnItemClickListener(mGroupAdapter);
         mListView.setItemsCanFocus(false);
@@ -172,7 +172,7 @@
         mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
         mListView.setItemChecked(mAdapter.getSelectedRoutePosition(), true);
 
-        mRouter.removeCallback(mGroupAdapter);
+        mRouter.removeCallback(mGroupAdapter.mCallback);
         mGroupAdapter = null;
 
         getDialog().setCanceledOnTouchOutside(true);
@@ -205,14 +205,14 @@
         public int position;
     }
 
-    private class RouteAdapter extends BaseAdapter implements MediaRouter.Callback,
-            ListView.OnItemClickListener {
+    private class RouteAdapter extends BaseAdapter implements ListView.OnItemClickListener {
         private static final int VIEW_TOP_HEADER = 0;
         private static final int VIEW_SECTION_HEADER = 1;
         private static final int VIEW_ROUTE = 2;
 
         private int mSelectedItemPosition;
         private final ArrayList<Object> mItems = new ArrayList<Object>();
+        final MediaRouterCallback mCallback = new MediaRouterCallback();
 
         RouteAdapter() {
             update();
@@ -333,7 +333,7 @@
 
         void bindItemView(int position, ViewHolder holder) {
             RouteInfo info = (RouteInfo) mItems.get(position);
-            holder.text1.setText(info.getName());
+            holder.text1.setText(info.getName(getActivity()));
             final CharSequence status = info.getStatus();
             if (TextUtils.isEmpty(status)) {
                 holder.text2.setVisibility(View.GONE);
@@ -363,7 +363,7 @@
 
         void bindHeaderView(int position, ViewHolder holder) {
             RouteCategory cat = (RouteCategory) mItems.get(position);
-            holder.text1.setText(cat.getName());
+            holder.text1.setText(cat.getName(getActivity()));
         }
 
         public int getSelectedRoutePosition() {
@@ -371,42 +371,6 @@
         }
 
         @Override
-        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
-            update();
-        }
-
-        @Override
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
-            update();
-        }
-
-        @Override
-        public void onRouteAdded(MediaRouter router, RouteInfo info) {
-            update();
-        }
-
-        @Override
-        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
-            update();
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, RouteInfo info) {
-            notifyDataSetChanged();
-        }
-
-        @Override
-        public void onRouteGrouped(MediaRouter router, RouteInfo info,
-                RouteGroup group, int index) {
-            update();
-        }
-
-        @Override
-        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
-            update();
-        }
-
-        @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
             ListView lv = (ListView) parent;
             final Object item = getItem(lv.getCheckedItemPosition());
@@ -414,7 +378,7 @@
                 // Oops. Stale event running around? Skip it.
                 return;
             }
-            mRouter.selectRoute(mRouteTypes, (RouteInfo) item);
+            mRouter.selectRouteInt(mRouteTypes, (RouteInfo) item);
             dismiss();
         }
 
@@ -428,10 +392,47 @@
                 onExpandGroup((RouteGroup) getItem(position));
             }
         }
+
+        class MediaRouterCallback extends MediaRouter.Callback {
+            @Override
+            public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+                update();
+            }
+
+            @Override
+            public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+                update();
+            }
+
+            @Override
+            public void onRouteAdded(MediaRouter router, RouteInfo info) {
+                update();
+            }
+
+            @Override
+            public void onRouteRemoved(MediaRouter router, RouteInfo info) {
+                update();
+            }
+
+            @Override
+            public void onRouteChanged(MediaRouter router, RouteInfo info) {
+                notifyDataSetChanged();
+            }
+
+            @Override
+            public void onRouteGrouped(MediaRouter router, RouteInfo info,
+                    RouteGroup group, int index) {
+                update();
+            }
+
+            @Override
+            public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
+                update();
+            }
+        }
     }
 
-    private class GroupAdapter extends BaseAdapter implements MediaRouter.Callback,
-            ListView.OnItemClickListener {
+    private class GroupAdapter extends BaseAdapter implements ListView.OnItemClickListener {
         private static final int VIEW_HEADER = 0;
         private static final int VIEW_ROUTE = 1;
         private static final int VIEW_DONE = 2;
@@ -441,6 +442,8 @@
         private final ArrayList<RouteInfo> mTempList = new ArrayList<RouteInfo>();
         private final ArrayList<RouteInfo> mFlatRoutes = new ArrayList<RouteInfo>();
         private boolean mIgnoreUpdates;
+        
+        final MediaRouterCallback mCallback = new MediaRouterCallback();
 
         public GroupAdapter(RouteGroup primary) {
             mPrimary = primary;
@@ -493,7 +496,7 @@
 
             // Sort by name. This will keep the route positions relatively stable even though they
             // will be repeatedly added and removed.
-            Collections.sort(mFlatRoutes, sComparator);
+            Collections.sort(mFlatRoutes, mComparator);
             notifyDataSetChanged();
         }
 
@@ -563,7 +566,7 @@
 
         void bindItemView(int position, ViewHolder holder) {
             RouteInfo info = (RouteInfo) getItem(position);
-            holder.text1.setText(info.getName());
+            holder.text1.setText(info.getName(getActivity()));
             final CharSequence status = info.getStatus();
             if (TextUtils.isEmpty(status)) {
                 holder.text2.setVisibility(View.GONE);
@@ -574,49 +577,7 @@
         }
 
         void bindHeaderView(int position, ViewHolder holder) {
-            holder.text1.setText(mCategory.getName());
-        }
-
-        @Override
-        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
-        }
-
-        @Override
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
-        }
-
-        @Override
-        public void onRouteAdded(MediaRouter router, RouteInfo info) {
-            update();
-            initCheckedItems();
-        }
-
-        @Override
-        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
-            if (info == mPrimary) {
-                // Can't keep grouping, clean it up.
-                onDoneGrouping();
-            } else {
-                update();
-                initCheckedItems();
-            }
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, RouteInfo info) {
-            update();
-        }
-
-        @Override
-        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index) {
-            update();
-            initCheckedItems();
-        }
-
-        @Override
-        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
-            update();
-            initCheckedItems();
+            holder.text1.setText(mCategory.getName(getActivity()));
         }
 
         @Override
@@ -641,7 +602,7 @@
                     if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) {
                         // Old group was selected but is now empty. Select the group
                         // we're manipulating since that's where the last route went.
-                        mRouter.selectRoute(mRouteTypes, mPrimary);
+                        mRouter.selectRouteInt(mRouteTypes, mPrimary);
                     }
                     mRouter.removeRouteInt(oldGroup);
                 }
@@ -664,12 +625,58 @@
             update();
             initCheckedItems();
         }
+
+        class MediaRouterCallback extends MediaRouter.Callback {
+            @Override
+            public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+            }
+
+            @Override
+            public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+            }
+
+            @Override
+            public void onRouteAdded(MediaRouter router, RouteInfo info) {
+                update();
+                initCheckedItems();
+            }
+
+            @Override
+            public void onRouteRemoved(MediaRouter router, RouteInfo info) {
+                if (info == mPrimary) {
+                    // Can't keep grouping, clean it up.
+                    onDoneGrouping();
+                } else {
+                    update();
+                    initCheckedItems();
+                }
+            }
+
+            @Override
+            public void onRouteChanged(MediaRouter router, RouteInfo info) {
+                update();
+            }
+
+            @Override
+            public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
+                    int index) {
+                update();
+                initCheckedItems();
+            }
+
+            @Override
+            public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
+                update();
+                initCheckedItems();
+            }
+        }
     }
 
-    static class RouteComparator implements Comparator<RouteInfo> {
+    class RouteComparator implements Comparator<RouteInfo> {
         @Override
         public int compare(RouteInfo lhs, RouteInfo rhs) {
-            return lhs.getName().toString().compareTo(rhs.getName().toString());
+            return lhs.getName(getActivity()).toString()
+                    .compareTo(rhs.getName(getActivity()).toString());
         }
     }
 
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 8488cd2..b497f63 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -83,8 +83,7 @@
         // Called after sStatic is initialized
         void initDefaultRoutes() {
             mDefaultAudio = new RouteInfo(mSystemCategory);
-            mDefaultAudio.mName = mResources.getText(
-                    com.android.internal.R.string.default_audio_route_name);
+            mDefaultAudio.mNameResId = com.android.internal.R.string.default_audio_route_name;
             mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
             addRoute(mDefaultAudio);
         }
@@ -156,10 +155,15 @@
     }
 
     static void onHeadphonesPlugged(boolean headphonesPresent, String headphonesName) {
-        sStatic.mDefaultAudio.mName = headphonesPresent
-                ? headphonesName
-                : sStatic.mResources.getText(
-                        com.android.internal.R.string.default_audio_route_name);
+        if (headphonesPresent) {
+            sStatic.mDefaultAudio.mName = headphonesName;
+            sStatic.mDefaultAudio.mNameResId = 0;
+        } else {
+            sStatic.mDefaultAudio.mName = null;
+            sStatic.mDefaultAudio.mNameResId =
+                    com.android.internal.R.string.default_audio_route_name;
+        }
+
         dispatchRouteChanged(sStatic.mDefaultAudio);
     }
 
@@ -207,11 +211,25 @@
      * @param route Route to select
      */
     public void selectRoute(int types, RouteInfo route) {
+        // Applications shouldn't programmatically change anything but user routes.
+        types &= ROUTE_TYPE_USER;
+        selectRouteStatic(types, route);
+    }
+    
+    /**
+     * @hide internal use
+     */
+    public void selectRouteInt(int types, RouteInfo route) {
         selectRouteStatic(types, route);
     }
 
     static void selectRouteStatic(int types, RouteInfo route) {
         if (sStatic.mSelectedRoute == route) return;
+        if ((route.getSupportedTypes() & types) == 0) {
+            Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
+                    typesToString(route.getSupportedTypes()) + " into route types " +
+                    typesToString(types));
+        }
 
         if (sStatic.mSelectedRoute != null) {
             // TODO filter types properly
@@ -424,6 +442,17 @@
     public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) {
         return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
     }
+    
+    /**
+     * Create a new route category. Each route must belong to a category.
+     *
+     * @param nameResId Resource ID of the name of the new category
+     * @param isGroupable true if routes in this category may be grouped with one another
+     * @return the new RouteCategory
+     */
+    public RouteCategory createRouteCategory(int nameResId, boolean isGroupable) {
+        return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
+    }
 
     static void updateRoute(final RouteInfo info) {
         dispatchRouteChanged(info);
@@ -501,8 +530,7 @@
 
     static void onA2dpDeviceConnected() {
         final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
-        info.mName = sStatic.mResources.getString(
-                com.android.internal.R.string.bluetooth_a2dp_audio_route_name);
+        info.mNameResId = com.android.internal.R.string.bluetooth_a2dp_audio_route_name;
         sStatic.mBluetoothA2dpRoute = info;
         addRoute(sStatic.mBluetoothA2dpRoute);
     }
@@ -517,6 +545,7 @@
      */
     public static class RouteInfo {
         CharSequence mName;
+        int mNameResId;
         private CharSequence mStatus;
         int mSupportedTypes;
         RouteGroup mGroup;
@@ -532,6 +561,24 @@
          * to users who may select this as the active route.
          */
         public CharSequence getName() {
+            return getName(sStatic.mResources);
+        }
+        
+        /**
+         * Return the properly localized/resource selected name of this route.
+         * 
+         * @param context Context used to resolve the correct configuration to load
+         * @return The user-friendly name of the media route. This is the string presented
+         * to users who may select this as the active route.
+         */
+        public CharSequence getName(Context context) {
+            return getName(context.getResources());
+        }
+        
+        CharSequence getName(Resources res) {
+            if (mNameResId != 0) {
+                return mName = res.getText(mNameResId);
+            }
             return mName;
         }
 
@@ -620,6 +667,16 @@
             mName = name;
             routeUpdated();
         }
+        
+        /**
+         * Set the user-visible name of this route.
+         * @param resId Resource ID of the name to display to the user to describe this route
+         */
+        public void setName(int resId) {
+            mNameResId = resId;
+            mName = null;
+            routeUpdated();
+        }
 
         /**
          * Set the current user-visible status for this route.
@@ -700,9 +757,9 @@
             mGroup = this;
         }
 
-        public CharSequence getName() {
+        CharSequence getName(Resources res) {
             if (mUpdateName) updateName();
-            return super.getName();
+            return super.getName(res);
         }
 
         /**
@@ -870,6 +927,7 @@
      */
     public static class RouteCategory {
         CharSequence mName;
+        int mNameResId;
         int mTypes;
         final boolean mGroupable;
 
@@ -879,10 +937,33 @@
             mGroupable = groupable;
         }
 
+        RouteCategory(int nameResId, int types, boolean groupable) {
+            mNameResId = nameResId;
+            mTypes = types;
+            mGroupable = groupable;
+        }
+
         /**
          * @return the name of this route category
          */
         public CharSequence getName() {
+            return getName(sStatic.mResources);
+        }
+        
+        /**
+         * Return the properly localized/configuration dependent name of this RouteCategory.
+         * 
+         * @param context Context to resolve name resources
+         * @return the name of this route category
+         */
+        public CharSequence getName(Context context) {
+            return getName(context.getResources());
+        }
+        
+        CharSequence getName(Resources res) {
+            if (mNameResId != 0) {
+                return res.getText(mNameResId);
+            }
             return mName;
         }
 
@@ -963,7 +1044,7 @@
      * @see MediaRouter#addCallback(int, Callback)
      * @see MediaRouter#removeCallback(Callback)
      */
-    public interface Callback {
+    public static abstract class Callback {
         /**
          * Called when the supplied route becomes selected as the active route
          * for the given route type.
@@ -972,7 +1053,7 @@
          * @param type Type flag set indicating the routes that have been selected
          * @param info Route that has been selected for the given route types
          */
-        public void onRouteSelected(MediaRouter router, int type, RouteInfo info);
+        public abstract void onRouteSelected(MediaRouter router, int type, RouteInfo info);
 
         /**
          * Called when the supplied route becomes unselected as the active route
@@ -982,7 +1063,7 @@
          * @param type Type flag set indicating the routes that have been unselected
          * @param info Route that has been unselected for the given route types
          */
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
+        public abstract void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
 
         /**
          * Called when a route for the specified type was added.
@@ -990,7 +1071,7 @@
          * @param router the MediaRouter reporting the event
          * @param info Route that has become available for use
          */
-        public void onRouteAdded(MediaRouter router, RouteInfo info);
+        public abstract void onRouteAdded(MediaRouter router, RouteInfo info);
 
         /**
          * Called when a route for the specified type was removed.
@@ -998,7 +1079,7 @@
          * @param router the MediaRouter reporting the event
          * @param info Route that has been removed from availability
          */
-        public void onRouteRemoved(MediaRouter router, RouteInfo info);
+        public abstract void onRouteRemoved(MediaRouter router, RouteInfo info);
 
         /**
          * Called when an aspect of the indicated route has changed.
@@ -1009,7 +1090,7 @@
          * @param router the MediaRouter reporting the event
          * @param info The route that was changed
          */
-        public void onRouteChanged(MediaRouter router, RouteInfo info);
+        public abstract void onRouteChanged(MediaRouter router, RouteInfo info);
 
         /**
          * Called when a route is added to a group.
@@ -1019,7 +1100,8 @@
          * @param group The group the route was added to
          * @param index The route index within group that info was added at
          */
-        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index);
+        public abstract void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
+                int index);
 
         /**
          * Called when a route is removed from a group.
@@ -1028,15 +1110,15 @@
          * @param info The route that was removed
          * @param group The group the route was removed from
          */
-        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
+        public abstract void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
     }
 
     /**
-     * Stub implementation of the {@link MediaRouter.Callback} interface.
-     * Each interface method is defined as a no-op. Override just the ones
+     * Stub implementation of {@link MediaRouter.Callback}.
+     * Each abstract method is defined as a no-op. Override just the ones
      * you need.
      */
-    public static class SimpleCallback implements Callback {
+    public static class SimpleCallback extends Callback {
 
         @Override
         public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {