MediaRouter.java revision dbbfa702a09f6d2d36dee1b552442d04a4673f89
19a1de308cea2d160778fd977825f10a07b49d738Adam Powell/*
29a1de308cea2d160778fd977825f10a07b49d738Adam Powell * Copyright (C) 2012 The Android Open Source Project
39a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
49a1de308cea2d160778fd977825f10a07b49d738Adam Powell * Licensed under the Apache License, Version 2.0 (the "License");
59a1de308cea2d160778fd977825f10a07b49d738Adam Powell * you may not use this file except in compliance with the License.
69a1de308cea2d160778fd977825f10a07b49d738Adam Powell * You may obtain a copy of the License at
79a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
89a1de308cea2d160778fd977825f10a07b49d738Adam Powell *      http://www.apache.org/licenses/LICENSE-2.0
99a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
109a1de308cea2d160778fd977825f10a07b49d738Adam Powell * Unless required by applicable law or agreed to in writing, software
119a1de308cea2d160778fd977825f10a07b49d738Adam Powell * distributed under the License is distributed on an "AS IS" BASIS,
129a1de308cea2d160778fd977825f10a07b49d738Adam Powell * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139a1de308cea2d160778fd977825f10a07b49d738Adam Powell * See the License for the specific language governing permissions and
149a1de308cea2d160778fd977825f10a07b49d738Adam Powell * limitations under the License.
159a1de308cea2d160778fd977825f10a07b49d738Adam Powell */
169a1de308cea2d160778fd977825f10a07b49d738Adam Powell
179a1de308cea2d160778fd977825f10a07b49d738Adam Powellpackage android.media;
189a1de308cea2d160778fd977825f10a07b49d738Adam Powell
198e37a85bf3dc39519942698dc90a3951306b934bAdam Powellimport android.content.BroadcastReceiver;
209a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.content.Context;
218e37a85bf3dc39519942698dc90a3951306b934bAdam Powellimport android.content.Intent;
228e37a85bf3dc39519942698dc90a3951306b934bAdam Powellimport android.content.IntentFilter;
23b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackbornimport android.content.res.Resources;
24ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powellimport android.graphics.drawable.Drawable;
259a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.os.Handler;
26632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.IBinder;
27632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.RemoteException;
28632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.os.ServiceManager;
29632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackbornimport android.text.TextUtils;
309a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport android.util.Log;
319a1de308cea2d160778fd977825f10a07b49d738Adam Powell
329a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport java.util.ArrayList;
339a1de308cea2d160778fd977825f10a07b49d738Adam Powellimport java.util.HashMap;
34d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powellimport java.util.List;
3539d5c6172503620ac3761148adac5fd7fa20d02dAdam Powellimport java.util.concurrent.CopyOnWriteArrayList;
369a1de308cea2d160778fd977825f10a07b49d738Adam Powell
379a1de308cea2d160778fd977825f10a07b49d738Adam Powell/**
389a1de308cea2d160778fd977825f10a07b49d738Adam Powell * MediaRouter allows applications to control the routing of media channels
399a1de308cea2d160778fd977825f10a07b49d738Adam Powell * and streams from the current device to external speakers and destination devices.
409a1de308cea2d160778fd977825f10a07b49d738Adam Powell *
41b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * <p>A MediaRouter is retrieved through {@link Context#getSystemService(String)
42b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE
43b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * Context.MEDIA_ROUTER_SERVICE}.
44b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn *
45b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * <p>The media router API is not thread-safe; all interactions with it must be
46b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn * done from the main thread of the process.</p>
479a1de308cea2d160778fd977825f10a07b49d738Adam Powell */
489a1de308cea2d160778fd977825f10a07b49d738Adam Powellpublic class MediaRouter {
499a1de308cea2d160778fd977825f10a07b49d738Adam Powell    private static final String TAG = "MediaRouter";
509a1de308cea2d160778fd977825f10a07b49d738Adam Powell
51b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static class Static {
52b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final Resources mResources;
53632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        final IAudioService mAudioService;
54b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final Handler mHandler;
5539d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
5639d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell                new CopyOnWriteArrayList<CallbackInfo>();
57b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
58b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
59b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
60b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
61b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        final RouteCategory mSystemCategory;
62632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
63632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        final AudioRoutesInfo mCurRoutesInfo = new AudioRoutesInfo();
64b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
65b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mDefaultAudio;
66b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mBluetoothA2dpRoute;
67b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
68b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        RouteInfo mSelectedRoute;
699a1de308cea2d160778fd977825f10a07b49d738Adam Powell
70632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        final IAudioRoutesObserver.Stub mRoutesObserver = new IAudioRoutesObserver.Stub() {
71632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
72632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                mHandler.post(new Runnable() {
73632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    @Override public void run() {
74632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        updateRoutes(newRoutes);
75632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    }
76632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                });
77632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
78632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        };
79632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
80b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        Static(Context appContext) {
81b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mResources = Resources.getSystem();
82b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mHandler = new Handler(appContext.getMainLooper());
839a1de308cea2d160778fd977825f10a07b49d738Adam Powell
84632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
85632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            mAudioService = IAudioService.Stub.asInterface(b);
869a1de308cea2d160778fd977825f10a07b49d738Adam Powell
87dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            mSystemCategory = new RouteCategory(
88dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                    com.android.internal.R.string.default_audio_route_category_name,
89b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                    ROUTE_TYPE_LIVE_AUDIO, false);
90b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        }
91b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
92b35c445f34e1a18e17aef3e3dfbc1c39b4d1815cAdam Powell        // Called after sStatic is initialized
938e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        void startMonitoringRoutes(Context appContext) {
94b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mDefaultAudio = new RouteInfo(mSystemCategory);
950d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mDefaultAudio.mNameResId = com.android.internal.R.string.default_audio_route_name;
96b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
97b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            addRoute(mDefaultAudio);
98632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
998e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            appContext.registerReceiver(new VolumeChangeReceiver(),
1008e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
1018e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
102632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            AudioRoutesInfo newRoutes = null;
103632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            try {
104632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                newRoutes = mAudioService.startWatchingRoutes(mRoutesObserver);
105632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            } catch (RemoteException e) {
106632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
107632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            if (newRoutes != null) {
108632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                updateRoutes(newRoutes);
109632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
110632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        }
111632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn
112632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn        void updateRoutes(AudioRoutesInfo newRoutes) {
113632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            if (newRoutes.mMainType != mCurRoutesInfo.mMainType) {
114632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                mCurRoutesInfo.mMainType = newRoutes.mMainType;
115632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                int name;
116632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
117632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        || (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
118632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_headphones;
119632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
120632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
121632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
122632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name_hdmi;
123632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else {
124632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    name = com.android.internal.R.string.default_audio_route_name;
125632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                }
126632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                sStatic.mDefaultAudio.mNameResId = name;
127632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                dispatchRouteChanged(sStatic.mDefaultAudio);
128632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
129632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            if (!TextUtils.equals(newRoutes.mBluetoothName, mCurRoutesInfo.mBluetoothName)) {
130632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                mCurRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
131632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                if (mCurRoutesInfo.mBluetoothName != null) {
132632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    if (sStatic.mBluetoothA2dpRoute == null) {
133632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
134632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        info.mName = mCurRoutesInfo.mBluetoothName;
135632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
136632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        sStatic.mBluetoothA2dpRoute = info;
137632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        addRoute(sStatic.mBluetoothA2dpRoute);
138dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                        try {
139dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                            if (mAudioService.isBluetoothA2dpOn()) {
140dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
141dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                            }
142dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                        } catch (RemoteException e) {
143dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                            Log.e(TAG, "Error selecting Bluetooth A2DP route", e);
144dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                        }
145632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    } else {
146632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        sStatic.mBluetoothA2dpRoute.mName = mCurRoutesInfo.mBluetoothName;
147632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                        dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
148632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    }
149632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                } else if (sStatic.mBluetoothA2dpRoute != null) {
150632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    removeRoute(sStatic.mBluetoothA2dpRoute);
151632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                    sStatic.mBluetoothA2dpRoute = null;
152632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn                }
153632ca417f0a33e3fa9ccece531afa2db3f0d4a30Dianne Hackborn            }
154b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        }
155b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
1569a1de308cea2d160778fd977825f10a07b49d738Adam Powell
157b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static Static sStatic;
1589a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1599a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
1609a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Route type flag for live audio.
1619a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
1629a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>A device that supports live audio routing will allow the media audio stream
1639a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * to be routed to supported destinations. This can include internal speakers or
1649a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * audio jacks on the device itself, A2DP devices, and more.</p>
1659a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
1669a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>Once initiated this routing is transparent to the application. All audio
1679a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * played on the media stream will be routed to the selected destination.</p>
1689a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1699a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
1709a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1719a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
1729a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Route type flag for application-specific usage.
1739a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
1749a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>Unlike other media route types, user routes are managed by the application.
1759a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * The MediaRouter will manage and dispatch events for user routes, but the application
1769a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * is expected to interpret the meaning of these events and perform the requested
1779a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * routing tasks.</p>
1789a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1799a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public static final int ROUTE_TYPE_USER = 0x00800000;
1809a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1819a1de308cea2d160778fd977825f10a07b49d738Adam Powell    // Maps application contexts
1829a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
1839a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1849a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static String typesToString(int types) {
1859a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final StringBuilder result = new StringBuilder();
1869a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
1879a1de308cea2d160778fd977825f10a07b49d738Adam Powell            result.append("ROUTE_TYPE_LIVE_AUDIO ");
1889a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if ((types & ROUTE_TYPE_USER) != 0) {
1909a1de308cea2d160778fd977825f10a07b49d738Adam Powell            result.append("ROUTE_TYPE_USER ");
1919a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1929a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return result.toString();
1939a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
1949a1de308cea2d160778fd977825f10a07b49d738Adam Powell
195b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    /** @hide */
196b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public MediaRouter(Context context) {
197b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        synchronized (Static.class) {
198b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            if (sStatic == null) {
1998e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final Context appContext = context.getApplicationContext();
2008e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                sStatic = new Static(appContext);
2018e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                sStatic.startMonitoringRoutes(appContext);
202b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            }
2039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2049a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
2059a1de308cea2d160778fd977825f10a07b49d738Adam Powell
206690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
207690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @hide for use by framework routing UI
208690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
209690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public RouteInfo getSystemAudioRoute() {
210b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mDefaultAudio;
211690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
212690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
213690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
2144599696591f745b3a546197d2ba7e5cfc5562484Adam Powell     * @hide for use by framework routing UI
2154599696591f745b3a546197d2ba7e5cfc5562484Adam Powell     */
2164599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    public RouteCategory getSystemAudioCategory() {
2174599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        return sStatic.mSystemCategory;
2184599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    }
2194599696591f745b3a546197d2ba7e5cfc5562484Adam Powell
2204599696591f745b3a546197d2ba7e5cfc5562484Adam Powell    /**
221690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * Return the currently selected route for the given types
222690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     *
223690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @param type route types
224690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @return the selected route
225690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
226690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public RouteInfo getSelectedRoute(int type) {
227b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mSelectedRoute;
228690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
229690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
2309a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
2319a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Add a callback to listen to events about specific kinds of media routes.
2329a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * If the specified callback is already registered, its registration will be updated for any
2339a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * additional route types specified.
2349a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2359a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param types Types of routes this callback is interested in
2369a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param cb Callback to add
2379a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2389a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void addCallback(int types, Callback cb) {
239b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final int count = sStatic.mCallbacks.size();
2409a1de308cea2d160778fd977825f10a07b49d738Adam Powell        for (int i = 0; i < count; i++) {
241b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final CallbackInfo info = sStatic.mCallbacks.get(i);
2429a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (info.cb == cb) {
243dbbfa702a09f6d2d36dee1b552442d04a4673f89Adam Powell                info.type |= types;
2449a1de308cea2d160778fd977825f10a07b49d738Adam Powell                return;
2459a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
2469a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
247b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        sStatic.mCallbacks.add(new CallbackInfo(cb, types, this));
2489a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
2499a1de308cea2d160778fd977825f10a07b49d738Adam Powell
2509a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
2519a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Remove the specified callback. It will no longer receive events about media routing.
2529a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
2539a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param cb Callback to remove
2549a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
2559a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void removeCallback(Callback cb) {
256b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final int count = sStatic.mCallbacks.size();
2579a1de308cea2d160778fd977825f10a07b49d738Adam Powell        for (int i = 0; i < count; i++) {
258b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            if (sStatic.mCallbacks.get(i).cb == cb) {
259b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCallbacks.remove(i);
2609a1de308cea2d160778fd977825f10a07b49d738Adam Powell                return;
2619a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
2629a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
2639a1de308cea2d160778fd977825f10a07b49d738Adam Powell        Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
2649a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
2659a1de308cea2d160778fd977825f10a07b49d738Adam Powell
266d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell    /**
267d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * Select the specified route to use for output of the given media types.
268d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     *
269d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * @param types type flags indicating which types this route should be used for.
270d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     *              The route must support at least a subset.
271d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     * @param route Route to select
272d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell     */
2739a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void selectRoute(int types, RouteInfo route) {
2740d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        // Applications shouldn't programmatically change anything but user routes.
2750d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        types &= ROUTE_TYPE_USER;
2760d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        selectRouteStatic(types, route);
2770d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    }
2780d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
2790d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    /**
2800d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @hide internal use
2810d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     */
2820d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public void selectRouteInt(int types, RouteInfo route) {
283b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        selectRouteStatic(types, route);
284b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
285b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
286b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void selectRouteStatic(int types, RouteInfo route) {
287b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (sStatic.mSelectedRoute == route) return;
2880d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        if ((route.getSupportedTypes() & types) == 0) {
2890d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
2900d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                    typesToString(route.getSupportedTypes()) + " into route types " +
2910d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                    typesToString(types));
2924ee1f55ce0f4909a7430ab44563a81852f335071Adam Powell            return;
2930d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
2949a1de308cea2d160778fd977825f10a07b49d738Adam Powell
295dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
296dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
297dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                (route == btRoute || route == sStatic.mDefaultAudio)) {
298dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            try {
299dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
300dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            } catch (RemoteException e) {
301dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell                Log.e(TAG, "Error changing Bluetooth A2DP state", e);
302dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell            }
303dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell        }
304dd0a19266d5c837069da1ea188744d54c8d723a8Adam Powell
305b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (sStatic.mSelectedRoute != null) {
3069a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // TODO filter types properly
307b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            dispatchRouteUnselected(types & sStatic.mSelectedRoute.getSupportedTypes(),
308b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                    sStatic.mSelectedRoute);
3099a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
310b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        sStatic.mSelectedRoute = route;
3119a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if (route != null) {
3129a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // TODO filter types properly
3139a1de308cea2d160778fd977825f10a07b49d738Adam Powell            dispatchRouteSelected(types & route.getSupportedTypes(), route);
3149a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
3159a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3169a1de308cea2d160778fd977825f10a07b49d738Adam Powell
3179a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
3189a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Add an app-specified route for media to the MediaRouter.
3199a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
3209a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
3219a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param info Definition of the route to add
3229a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #createUserRoute()
3239a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #removeUserRoute(UserRouteInfo)
3249a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
3259a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void addUserRoute(UserRouteInfo info) {
3269a1de308cea2d160778fd977825f10a07b49d738Adam Powell        addRoute(info);
3279a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3289a1de308cea2d160778fd977825f10a07b49d738Adam Powell
329d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    /**
330d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     * @hide Framework use only
331d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     */
332d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    public void addRouteInt(RouteInfo info) {
333d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        addRoute(info);
334d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    }
335d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
336b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void addRoute(RouteInfo info) {
3379a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final RouteCategory cat = info.getCategory();
338b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (!sStatic.mCategories.contains(cat)) {
339b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mCategories.add(cat);
3409a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
341b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        final boolean onlyRoute = sStatic.mRoutes.isEmpty();
342d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        if (cat.isGroupable() && !(info instanceof RouteGroup)) {
3439a1de308cea2d160778fd977825f10a07b49d738Adam Powell            // Enforce that any added route in a groupable category must be in a group.
3449a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final RouteGroup group = new RouteGroup(info.getCategory());
345dbbfa702a09f6d2d36dee1b552442d04a4673f89Adam Powell            group.mSupportedTypes = info.mSupportedTypes;
346b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mRoutes.add(group);
347d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteAdded(group);
348b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            group.addRoute(info);
349d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
3509a1de308cea2d160778fd977825f10a07b49d738Adam Powell            info = group;
351d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        } else {
352b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            sStatic.mRoutes.add(info);
353d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteAdded(info);
3549a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
355d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
3569a1de308cea2d160778fd977825f10a07b49d738Adam Powell        if (onlyRoute) {
357b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            selectRouteStatic(info.getSupportedTypes(), info);
3589a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
3599a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3609a1de308cea2d160778fd977825f10a07b49d738Adam Powell
3619a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
3629a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Remove an app-specified route for media from the MediaRouter.
3639a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
3649a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param info Definition of the route to remove
3659a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #addUserRoute(UserRouteInfo)
3669a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
3679a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public void removeUserRoute(UserRouteInfo info) {
3689a1de308cea2d160778fd977825f10a07b49d738Adam Powell        removeRoute(info);
3699a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
3709a1de308cea2d160778fd977825f10a07b49d738Adam Powell
371690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    /**
372690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * Remove all app-specified routes from the MediaRouter.
373690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     *
374690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     * @see #removeUserRoute(UserRouteInfo)
375690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell     */
376690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    public void clearUserRoutes() {
377b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        for (int i = 0; i < sStatic.mRoutes.size(); i++) {
378b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final RouteInfo info = sStatic.mRoutes.get(i);
379d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            // TODO Right now, RouteGroups only ever contain user routes.
380d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            // The code below will need to change if this assumption does.
381d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
382690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                removeRouteAt(i);
383690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                i--;
384690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
385690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell        }
386690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
387690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
388d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    /**
389d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     * @hide internal use only
390d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell     */
391d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    public void removeRouteInt(RouteInfo info) {
392d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        removeRoute(info);
393d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell    }
394d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
395b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void removeRoute(RouteInfo info) {
396b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (sStatic.mRoutes.remove(info)) {
3979a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final RouteCategory removingCat = info.getCategory();
398b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = sStatic.mRoutes.size();
3999a1de308cea2d160778fd977825f10a07b49d738Adam Powell            boolean found = false;
4009a1de308cea2d160778fd977825f10a07b49d738Adam Powell            for (int i = 0; i < count; i++) {
401b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
4029a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (removingCat == cat) {
4039a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    found = true;
4049a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    break;
4059a1de308cea2d160778fd977825f10a07b49d738Adam Powell                }
4069a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
407d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info == sStatic.mSelectedRoute) {
408d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // Removing the currently selected route? Select the default before we remove it.
409d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // TODO: Be smarter about the route types here; this selects for all valid.
410d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio);
411d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
4129a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (!found) {
413b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCategories.remove(removingCat);
4149a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
4159a1de308cea2d160778fd977825f10a07b49d738Adam Powell            dispatchRouteRemoved(info);
4169a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
4179a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4189a1de308cea2d160778fd977825f10a07b49d738Adam Powell
419690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    void removeRouteAt(int routeIndex) {
420b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) {
421b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final RouteInfo info = sStatic.mRoutes.remove(routeIndex);
422690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            final RouteCategory removingCat = info.getCategory();
423b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = sStatic.mRoutes.size();
424690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            boolean found = false;
425690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            for (int i = 0; i < count; i++) {
426b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
427690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                if (removingCat == cat) {
428690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                    found = true;
429690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                    break;
430690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell                }
431690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
432d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            if (info == sStatic.mSelectedRoute) {
433d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // Removing the currently selected route? Select the default before we remove it.
434d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                // TODO: Be smarter about the route types here; this selects for all valid.
435d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio);
436d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
437690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            if (!found) {
438b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                sStatic.mCategories.remove(removingCat);
439690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            }
440690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell            dispatchRouteRemoved(info);
441690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell        }
442690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell    }
443690ffb4e1f735148a15f2036d9a3c1962fba188cAdam Powell
4449a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4459a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the number of {@link MediaRouter.RouteCategory categories} currently
4469a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * represented by routes known to this MediaRouter.
4479a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4489a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the number of unique categories represented by this MediaRouter's known routes
4499a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4509a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public int getCategoryCount() {
451b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mCategories.size();
4529a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4539a1de308cea2d160778fd977825f10a07b49d738Adam Powell
4549a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4559a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the {@link MediaRouter.RouteCategory category} at the given index.
4569a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Valid indices are in the range [0-getCategoryCount).
4579a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4589a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param index which category to return
4599a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the category at index
4609a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4619a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteCategory getCategoryAt(int index) {
462b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mCategories.get(index);
4639a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4649a1de308cea2d160778fd977825f10a07b49d738Adam Powell
4659a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4669a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the number of {@link MediaRouter.RouteInfo routes} currently known
4679a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * to this MediaRouter.
4689a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4699a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the number of routes tracked by this router
4709a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4719a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public int getRouteCount() {
472b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.size();
4739a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4749a1de308cea2d160778fd977825f10a07b49d738Adam Powell
4759a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4769a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Return the route at the specified index.
4779a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4789a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param index index of the route to return
4799a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the route at index
4809a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
4819a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteInfo getRouteAt(int index) {
482b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.get(index);
483b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
484b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
485b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static int getRouteCountStatic() {
486b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.size();
487b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    }
488b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn
489b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static RouteInfo getRouteAtStatic(int index) {
490b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        return sStatic.mRoutes.get(index);
4919a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
4929a1de308cea2d160778fd977825f10a07b49d738Adam Powell
4939a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
4949a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Create a new user route that may be modified and registered for use by the application.
4959a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4969a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param category The category the new route will belong to
4979a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return A new UserRouteInfo for use by the application
4989a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
4999a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #addUserRoute(UserRouteInfo)
5009a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #removeUserRoute(UserRouteInfo)
5019a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see #createRouteCategory(CharSequence)
5029a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5039a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public UserRouteInfo createUserRoute(RouteCategory category) {
5049a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return new UserRouteInfo(category);
5059a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5069a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5079a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
5089a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Create a new route category. Each route must belong to a category.
5099a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
5109a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param name Name of the new category
5119a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @param isGroupable true if routes in this category may be grouped with one another
5129a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @return the new RouteCategory
5139a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
5149a1de308cea2d160778fd977825f10a07b49d738Adam Powell    public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) {
5159a1de308cea2d160778fd977825f10a07b49d738Adam Powell        return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
5169a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5170d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
5180d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    /**
5190d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Create a new route category. Each route must belong to a category.
5200d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     *
5210d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @param nameResId Resource ID of the name of the new category
5220d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @param isGroupable true if routes in this category may be grouped with one another
5230d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * @return the new RouteCategory
5240d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     */
5250d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public RouteCategory createRouteCategory(int nameResId, boolean isGroupable) {
5260d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
5270d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    }
5289a1de308cea2d160778fd977825f10a07b49d738Adam Powell
529b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void updateRoute(final RouteInfo info) {
5309a1de308cea2d160778fd977825f10a07b49d738Adam Powell        dispatchRouteChanged(info);
5319a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5329a1de308cea2d160778fd977825f10a07b49d738Adam Powell
533b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteSelected(int type, RouteInfo info) {
53439d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5359a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & type) != 0) {
536b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteSelected(cbi.router, type, info);
5379a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5389a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5399a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5409a1de308cea2d160778fd977825f10a07b49d738Adam Powell
541b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteUnselected(int type, RouteInfo info) {
54239d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5439a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & type) != 0) {
544b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteUnselected(cbi.router, type, info);
5459a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5469a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5479a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5489a1de308cea2d160778fd977825f10a07b49d738Adam Powell
549b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteChanged(RouteInfo info) {
55039d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5519a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
552b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteChanged(cbi.router, info);
5539a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5549a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5559a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5569a1de308cea2d160778fd977825f10a07b49d738Adam Powell
557b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteAdded(RouteInfo info) {
55839d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5599a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
560b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteAdded(cbi.router, info);
5619a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5629a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5639a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5649a1de308cea2d160778fd977825f10a07b49d738Adam Powell
565b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteRemoved(RouteInfo info) {
56639d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5679a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
568b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteRemoved(cbi.router, info);
5699a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5709a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5719a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5729a1de308cea2d160778fd977825f10a07b49d738Adam Powell
573b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteGrouped(RouteInfo info, RouteGroup group, int index) {
57439d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
575d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if ((cbi.type & group.mSupportedTypes) != 0) {
576b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteGrouped(cbi.router, info, group, index);
577d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
578d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
579d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell    }
580d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
581b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    static void dispatchRouteUngrouped(RouteInfo info, RouteGroup group) {
58239d5c6172503620ac3761148adac5fd7fa20d02dAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
583d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if ((cbi.type & group.mSupportedTypes) != 0) {
584b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                cbi.cb.onRouteUngrouped(cbi.router, info, group);
5859a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
5869a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
5879a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
5889a1de308cea2d160778fd977825f10a07b49d738Adam Powell
5898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static void dispatchRouteVolumeChanged(RouteInfo info) {
5908e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        for (CallbackInfo cbi : sStatic.mCallbacks) {
5918e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if ((cbi.type & info.mSupportedTypes) != 0) {
5928e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                cbi.cb.onRouteVolumeChanged(cbi.router, info);
5938e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
5948e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
5958e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
5968e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
5978e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static void systemVolumeChanged(int newValue) {
5988e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        final RouteInfo selectedRoute = sStatic.mSelectedRoute;
5998e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        if (selectedRoute == null) return;
6008e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
6018e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
6028e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                selectedRoute == sStatic.mDefaultAudio) {
6038e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            dispatchRouteVolumeChanged(selectedRoute);
6048e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        } else if (sStatic.mBluetoothA2dpRoute != null) {
6058e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            try {
6068e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
6078e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudio);
6088e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } catch (RemoteException e) {
6098e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
6108e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
6118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        } else {
6128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            dispatchRouteVolumeChanged(sStatic.mDefaultAudio);
6138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
6148e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
6158e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
6169a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
6179a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a media route.
6189a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
619b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteInfo {
6209a1de308cea2d160778fd977825f10a07b49d738Adam Powell        CharSequence mName;
6210d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        int mNameResId;
6229a1de308cea2d160778fd977825f10a07b49d738Adam Powell        private CharSequence mStatus;
6239a1de308cea2d160778fd977825f10a07b49d738Adam Powell        int mSupportedTypes;
6249a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteGroup mGroup;
6259a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final RouteCategory mCategory;
626ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        Drawable mIcon;
6271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        // playback information
6281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mPlaybackType = PLAYBACK_TYPE_LOCAL;
6291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
6301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
6311357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
6321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        int mPlaybackStream = AudioManager.STREAM_MUSIC;
6331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        VolumeCallbackInfo mVcb;
6349a1de308cea2d160778fd977825f10a07b49d738Adam Powell
635b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        private Object mTag;
636b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
6371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
6381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * The default playback type, "local", indicating the presentation of the media is happening
6391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * on the same device (e.g. a phone, a tablet) as where it is controlled from.
6401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setPlaybackType(int)
6411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
6421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_TYPE_LOCAL = 0;
6431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
6441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * A playback type indicating the presentation of the media is happening on
6451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * a different device (i.e. the remote device) than where it is controlled from.
6461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setPlaybackType(int)
6471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
6481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_TYPE_REMOTE = 1;
6491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
6501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Playback information indicating the playback volume is fixed, i.e. it cannot be
6511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * controlled from this object. An example of fixed playback volume is a remote player,
6521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
6531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * than attenuate at the source.
6541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see #setVolumeHandling(int)
6551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
6561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_VOLUME_FIXED = 0;
6571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
6581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Playback information indicating the playback volume is variable and can be controlled
6591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * from this object.
6601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
6611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final static int PLAYBACK_VOLUME_VARIABLE = 1;
6621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
6639a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteInfo(RouteCategory category) {
6649a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mCategory = category;
6659a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6669a1de308cea2d160778fd977825f10a07b49d738Adam Powell
6679a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
6689a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The user-friendly name of a media route. This is the string presented
6699a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * to users who may select this as the active route.
6709a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
6719a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getName() {
6720d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(sStatic.mResources);
6730d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
6740d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
6750d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
6760d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Return the properly localized/resource selected name of this route.
6770d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         *
6780d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param context Context used to resolve the correct configuration to load
6790d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @return The user-friendly name of the media route. This is the string presented
6800d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * to users who may select this as the active route.
6810d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
6820d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public CharSequence getName(Context context) {
6830d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(context.getResources());
6840d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
6850d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
6860d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
6870d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            if (mNameResId != 0) {
6880d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                return mName = res.getText(mNameResId);
6890d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            }
6909a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mName;
6919a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
6929a1de308cea2d160778fd977825f10a07b49d738Adam Powell
6939a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
6949a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The user-friendly status for a media route. This may include a description
6959a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * of the currently playing media, if available.
6969a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
6979a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getStatus() {
6989a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mStatus;
6999a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
7019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
7029a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return A media type flag set describing which types this route supports.
7039a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
7049a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int getSupportedTypes() {
7059a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mSupportedTypes;
7069a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7079a1de308cea2d160778fd977825f10a07b49d738Adam Powell
7089a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
7099a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return The group that this route belongs to.
7109a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
7119a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public RouteGroup getGroup() {
7129a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mGroup;
7139a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7149a1de308cea2d160778fd977825f10a07b49d738Adam Powell
7159a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
7169a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return the category this route belongs to.
7179a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
7189a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public RouteCategory getCategory() {
7199a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mCategory;
7209a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
7219a1de308cea2d160778fd977825f10a07b49d738Adam Powell
722ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
723ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Get the icon representing this route.
724ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * This icon will be used in picker UIs if available.
725ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
726ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @return the icon representing this route or null if no icon is available
727ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
728ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public Drawable getIconDrawable() {
729ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            return mIcon;
730ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
731ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
732b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        /**
733b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * Set an application-specific tag object for this route.
734b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * The application may use this to store arbitrary data associated with the
735b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * route for internal tracking.
736b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         *
737b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * <p>Note that the lifespan of a route may be well past the lifespan of
738b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * an Activity or other Context; take care that objects you store here
739b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * will not keep more data in memory alive than you intend.</p>
740b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         *
741b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @param tag Arbitrary, app-specific data for this route to hold for later use
742b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         */
743b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        public void setTag(Object tag) {
744b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            mTag = tag;
745130b4572d1f3df702e5b296a655d15a41f6d4c66Adam Powell            routeUpdated();
746b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        }
747b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
748b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        /**
749b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @return The tag object previously set by the application
750b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         * @see #setTag(Object)
751b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell         */
752b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        public Object getTag() {
753b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            return mTag;
754b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell        }
755b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
7561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
7571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the type of playback associated with this route
7581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setPlaybackType(int)
7591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
7601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getPlaybackType() {
7611357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mPlaybackType;
7621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
7631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
7641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
7651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the stream over which the playback associated with this route is performed
7661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setPlaybackStream(int)
7671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
7681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getPlaybackStream() {
7691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mPlaybackStream;
7701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
7711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
7721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
7738e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Return the current volume for this route. Depending on the route, this may only
7748e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * be valid if the route is currently selected.
7758e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         *
7761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the volume at which the playback associated with this route is performed
7771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolume(int)
7781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
7791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolume() {
7801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
7811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                int vol = 0;
7821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
7831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    vol = sStatic.mAudioService.getStreamVolume(mPlaybackStream);
7841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
7851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error getting local stream volume", e);
7861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
7871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return vol;
7881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            } else {
7891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return mVolume;
7901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
7911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
7921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
7931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
7948e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Request a volume change for this route.
7958e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param volume value between 0 and getVolumeMax
7968e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
7978e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
7988e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
7998e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                try {
8008e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0);
8018e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                } catch (RemoteException e) {
8028e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Error setting local stream volume", e);
8038e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
8048e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } else {
8058e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, getClass().getSimpleName() + ".requestSetVolume(): " +
8068e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Non-local volume playback on system route? " +
8078e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Could not request volume change.");
8088e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
8098e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
8108e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
8118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
8128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Request an incremental volume update for this route.
8138e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param direction Delta to apply to the current volume
8148e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
8158e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
8168e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
8178e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                try {
8188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    final int volume =
8198e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                            Math.max(0, Math.min(getVolume() + direction, getVolumeMax()));
8208e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0);
8218e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                } catch (RemoteException e) {
8228e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Error setting local stream volume", e);
8238e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
8248e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            } else {
8258e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                Log.e(TAG, getClass().getSimpleName() + ".requestChangeVolume(): " +
8268e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Non-local volume playback on system route? " +
8278e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        "Could not request volume change.");
8288e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
8298e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
8308e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
8318e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
8321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return the maximum volume at which the playback associated with this route is performed
8331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolumeMax(int)
8341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
8351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolumeMax() {
8361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
8371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                int volMax = 0;
8381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
8391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    volMax = sStatic.mAudioService.getStreamMaxVolume(mPlaybackStream);
8401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
8411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error getting local stream volume", e);
8421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
8431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return volMax;
8441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            } else {
8451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                return mVolumeMax;
8461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
8471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
8481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
8491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
8501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @return how volume is handling on the route
8511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @see UserRouteInfo#setVolumeHandling(int)
8521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
8531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public int getVolumeHandling() {
8541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            return mVolumeHandling;
8551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
8561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
8579a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void setStatusInt(CharSequence status) {
8589a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (!status.equals(mStatus)) {
8599a1de308cea2d160778fd977825f10a07b49d738Adam Powell                mStatus = status;
8609a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (mGroup != null) {
8619a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    mGroup.memberStatusChanged(this, status);
8629a1de308cea2d160778fd977825f10a07b49d738Adam Powell                }
8639a1de308cea2d160778fd977825f10a07b49d738Adam Powell                routeUpdated();
8649a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
8659a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
8669a1de308cea2d160778fd977825f10a07b49d738Adam Powell
8671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() {
8681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            public void dispatchRemoteVolumeUpdate(final int direction, final int value) {
8691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                sStatic.mHandler.post(new Runnable() {
8701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    @Override
8711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    public void run() {
8721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                      //Log.d(TAG, "dispatchRemoteVolumeUpdate dir=" + direction + " val=" + value);
8731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        if (mVcb != null) {
8741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            if (direction != 0) {
8751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                                mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
8761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            } else {
8771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                                mVcb.vcb.onVolumeSetRequest(mVcb.route, value);
8781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            }
8791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        }
8801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    }
8811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                });
8821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
8831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        };
8841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
8859a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void routeUpdated() {
8869a1de308cea2d160778fd977825f10a07b49d738Adam Powell            updateRoute(this);
8879a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
8889a1de308cea2d160778fd977825f10a07b49d738Adam Powell
8899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
8909a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public String toString() {
891d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            String supportedTypes = typesToString(getSupportedTypes());
892d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            return getClass().getSimpleName() + "{ name=" + getName() + ", status=" + getStatus() +
893d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                    " category=" + getCategory() +
8949a1de308cea2d160778fd977825f10a07b49d738Adam Powell                    " supportedTypes=" + supportedTypes + "}";
8959a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
8969a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
8979a1de308cea2d160778fd977825f10a07b49d738Adam Powell
8989a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
8999a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a route that the application may define and modify.
9008e37a85bf3dc39519942698dc90a3951306b934bAdam Powell     * A user route defaults to {@link RouteInfo#PLAYBACK_TYPE_REMOTE} and
9018e37a85bf3dc39519942698dc90a3951306b934bAdam Powell     * {@link RouteInfo#PLAYBACK_VOLUME_FIXED}.
9029a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
9039a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter.RouteInfo
9049a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
905b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class UserRouteInfo extends RouteInfo {
906ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        RemoteControlClient mRcc;
9079a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9089a1de308cea2d160778fd977825f10a07b49d738Adam Powell        UserRouteInfo(RouteCategory category) {
9099a1de308cea2d160778fd977825f10a07b49d738Adam Powell            super(category);
9109a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mSupportedTypes = ROUTE_TYPE_USER;
9118e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mPlaybackType = PLAYBACK_TYPE_REMOTE;
9128e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = PLAYBACK_VOLUME_FIXED;
9139a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9149a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9159a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
9169a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Set the user-visible name of this route.
9179a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param name Name to display to the user to describe this route
9189a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
9199a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void setName(CharSequence name) {
9209a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = name;
9219a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
9229a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
9230d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
9240d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
9250d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Set the user-visible name of this route.
9260d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param resId Resource ID of the name to display to the user to describe this route
9270d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
9280d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public void setName(int resId) {
9290d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mNameResId = resId;
9300d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mName = null;
9310d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            routeUpdated();
9320d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
9339a1de308cea2d160778fd977825f10a07b49d738Adam Powell
9349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
9359a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Set the current user-visible status for this route.
9369a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param status Status to display to the user to describe what the endpoint
9379a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * of this route is currently doing
9389a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
9399a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void setStatus(CharSequence status) {
9409a1de308cea2d160778fd977825f10a07b49d738Adam Powell            setStatusInt(status);
9419a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
942ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
943ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
944ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set the RemoteControlClient responsible for reporting playback info for this
945ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * user route.
946ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
947ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * <p>If this route manages remote playback, the data exposed by this
948ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * RemoteControlClient will be used to reflect and update information
949ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * such as route volume info in related UIs.</p>
950ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
9511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * <p>The RemoteControlClient must have been previously registered with
9521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * {@link AudioManager#registerRemoteControlClient(RemoteControlClient)}.</p>
9531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *
954ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param rcc RemoteControlClient associated with this route
955ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
956ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setRemoteControlClient(RemoteControlClient rcc) {
957ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mRcc = rcc;
9581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            updatePlaybackInfoOnRcc();
959ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
960ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
961ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
9624599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * Retrieve the RemoteControlClient associated with this route, if one has been set.
9634599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         *
9644599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * @return the RemoteControlClient associated with this route
9654599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         * @see #setRemoteControlClient(RemoteControlClient)
9664599696591f745b3a546197d2ba7e5cfc5562484Adam Powell         */
9674599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        public RemoteControlClient getRemoteControlClient() {
9684599696591f745b3a546197d2ba7e5cfc5562484Adam Powell            return mRcc;
9694599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        }
9704599696591f745b3a546197d2ba7e5cfc5562484Adam Powell
9714599696591f745b3a546197d2ba7e5cfc5562484Adam Powell        /**
972ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this route.
973ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
974ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
975ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param icon icon drawable to use to represent this route
976ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
977ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconDrawable(Drawable icon) {
978ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mIcon = icon;
979ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
980ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
981ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
982ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this route.
983ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
984ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
98571c69897ad0a55d590698bfa399bfe99c763b9dbAdam Powell         * @param resId Resource ID of an icon drawable to use to represent this route
986ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
987ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconResource(int resId) {
988ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            setIconDrawable(sStatic.mResources.getDrawable(resId));
989ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
9901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
9911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
9921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Set a callback to be notified of volume update requests
9931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param vcb
9941357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
9951357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeCallback(VolumeCallback vcb) {
9961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            mVcb = new VolumeCallbackInfo(vcb, this);
9971357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
9981357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
9991357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10001357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines whether playback associated with this route is "local"
10011357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *    ({@link RouteInfo#PLAYBACK_TYPE_LOCAL}) or "remote"
10021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *    ({@link RouteInfo#PLAYBACK_TYPE_REMOTE}).
10031357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param type
10041357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10051357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setPlaybackType(int type) {
10061357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackType != type) {
10071357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mPlaybackType = type;
10081357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, type);
10091357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10101357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10111357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10121357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10131357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines whether volume for the playback associated with this route is fixed
10141357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * ({@link RouteInfo#PLAYBACK_VOLUME_FIXED}) or can modified
10151357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * ({@link RouteInfo#PLAYBACK_VOLUME_VARIABLE}).
10161357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volumeHandling
10171357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10181357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeHandling(int volumeHandling) {
10191357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolumeHandling != volumeHandling) {
10201357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolumeHandling = volumeHandling;
10211357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(
10221357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, volumeHandling);
10231357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10241357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10261357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines at what volume the playback associated with this route is performed (for user
10281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * feedback purposes). This information is only used when the playback is not local.
10291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volume
10301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10311357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolume(int volume) {
10328e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            volume = Math.max(0, Math.min(volume, getVolumeMax()));
10331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolume != volume) {
10341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolume = volume;
10351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME, volume);
10368e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
1037f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (mGroup != null) {
1038f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    mGroup.memberVolumeChanged(this);
1039f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
10408e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
10418e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
10428e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
10438e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
10448e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
10458e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
10468e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (mVcb == null) {
10478e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Cannot requestSetVolume on user route - no volume callback set");
10488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
10498e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
10508e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVcb.vcb.onVolumeSetRequest(this, volume);
10518e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
10528e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
10538e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
10548e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
10558e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
10568e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
10578e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (mVcb == null) {
10588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    Log.e(TAG, "Cannot requestChangeVolume on user route - no volumec callback set");
10598e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
10608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
10618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVcb.vcb.onVolumeUpdateRequest(this, direction);
10621357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10631357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10641357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10651357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10661357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines the maximum volume at which the playback associated with this route is performed
10671357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * (for user feedback purposes). This information is only used when the playback is not
10681357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * local.
10691357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volumeMax
10701357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10711357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setVolumeMax(int volumeMax) {
10721357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mVolumeMax != volumeMax) {
10731357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mVolumeMax = volumeMax;
10741357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, volumeMax);
10751357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10761357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10771357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10781357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
10791357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Defines over what stream type the media is presented.
10801357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param stream
10811357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
10821357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public void setPlaybackStream(int stream) {
10831357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mPlaybackStream != stream) {
10841357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mPlaybackStream = stream;
10851357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_USES_STREAM, stream);
10861357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
10871357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
10881357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
10891357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        private void updatePlaybackInfoOnRcc() {
10901357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if ((mRcc != null) && (mRcc.getRcseId() != RemoteControlClient.RCSE_ID_UNREGISTERED)) {
10911357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
10921357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_MAX, mVolumeMax);
10931357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
10941357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME, mVolume);
10951357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
10961357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_VOLUME_HANDLING, mVolumeHandling);
10971357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
10981357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_USES_STREAM, mPlaybackStream);
10991357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(
11001357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                        RemoteControlClient.PLAYBACKINFO_PLAYBACK_TYPE, mPlaybackType);
11011357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                // let AudioService know whom to call when remote volume needs to be updated
11021357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                try {
11031357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    sStatic.mAudioService.registerRemoteVolumeObserverForRcc(
11041357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                            mRcc.getRcseId() /* rccId */, mRemoteVolObserver /* rvo */);
11051357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                } catch (RemoteException e) {
11061357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                    Log.e(TAG, "Error registering remote volume observer", e);
11071357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                }
11081357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
11091357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11101357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
11111357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        private void setPlaybackInfoOnRcc(int what, int value) {
11121357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            if (mRcc != null) {
11131357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi                mRcc.setPlaybackInformation(what, value);
11141357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            }
11151357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
11169a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
11179a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11189a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
11199a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Information about a route that consists of multiple other routes in a group.
11209a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1121b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteGroup extends RouteInfo {
11229a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
11239a1de308cea2d160778fd977825f10a07b49d738Adam Powell        private boolean mUpdateName;
11249a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11259a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteGroup(RouteCategory category) {
11269a1de308cea2d160778fd977825f10a07b49d738Adam Powell            super(category);
11279a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mGroup = this;
11288e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = PLAYBACK_VOLUME_FIXED;
11299a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11309a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11310d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
11329a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (mUpdateName) updateName();
11330d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return super.getName(res);
11349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11359a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11369a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
11379a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Add a route to this group. The route must not currently belong to another group.
11389a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
11399a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to add to this group
11409a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
11419a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void addRoute(RouteInfo route) {
11429a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != null) {
11439a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalStateException("Route " + route + " is already part of a group.");
11449a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
11459a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getCategory() != mCategory) {
11469a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException(
11479a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        "Route cannot be added to a group with a different category. " +
11489a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            "(Route category=" + route.getCategory() +
11499a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            " group category=" + mCategory + ")");
11509a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
1151d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            final int at = mRoutes.size();
11529a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.add(route);
1153d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = this;
11549a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1155f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
11569a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
1157f3b653a21cdffe04c94c275e69ecb56e00766e82Adam Powell            dispatchRouteGrouped(route, this, at);
11589a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11599a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11609a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
11619a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Add a route to this group before the specified index.
11629a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
11639a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to add
11649a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param insertAt insert the new route before this index
11659a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
11669a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void addRoute(RouteInfo route, int insertAt) {
11679a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != null) {
11689a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalStateException("Route " + route + " is already part of a group.");
11699a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
11709a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getCategory() != mCategory) {
11719a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException(
11729a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        "Route cannot be added to a group with a different category. " +
11739a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            "(Route category=" + route.getCategory() +
11749a1de308cea2d160778fd977825f10a07b49d738Adam Powell                            " group category=" + mCategory + ")");
11759a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
11769a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.add(insertAt, route);
1177d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = this;
11789a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1179f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
11809a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
1181f3b653a21cdffe04c94c275e69ecb56e00766e82Adam Powell            dispatchRouteGrouped(route, this, insertAt);
11829a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
11839a1de308cea2d160778fd977825f10a07b49d738Adam Powell
11849a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
11859a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Remove a route from this group.
11869a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
11879a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param route route to remove
11889a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
11899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void removeRoute(RouteInfo route) {
11909a1de308cea2d160778fd977825f10a07b49d738Adam Powell            if (route.getGroup() != this) {
11919a1de308cea2d160778fd977825f10a07b49d738Adam Powell                throw new IllegalArgumentException("Route " + route +
11929a1de308cea2d160778fd977825f10a07b49d738Adam Powell                        " is not a member of this group.");
11939a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
11949a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mRoutes.remove(route);
1195d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = null;
11969a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1197f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1198d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteUngrouped(route, this);
11999a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
12009a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12019a1de308cea2d160778fd977825f10a07b49d738Adam Powell
12029a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
12039a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Remove the route at the specified index from this group.
12049a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
12059a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param index index of the route to remove
12069a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
12079a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public void removeRoute(int index) {
1208d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            RouteInfo route = mRoutes.remove(index);
1209d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            route.mGroup = null;
12109a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
1211f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1212d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            dispatchRouteUngrouped(route, this);
12139a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
12149a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
12159a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1216d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1217d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return The number of routes in this group
1218d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
1219d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public int getRouteCount() {
1220d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return mRoutes.size();
1221d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
1222d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1223d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1224d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Return the route in this group at the specified index
1225d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1226d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param index Index to fetch
1227d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return The route at index
1228d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
1229d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public RouteInfo getRouteAt(int index) {
1230d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return mRoutes.get(index);
1231d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        }
1232d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1233ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1234ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this group.
1235ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1236ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
1237ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * @param icon icon drawable to use to represent this group
1238ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1239ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconDrawable(Drawable icon) {
1240ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            mIcon = icon;
1241ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1242ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
1243ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        /**
1244ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * Set an icon that will be used to represent this group.
1245ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         * The system may use this icon in picker UIs or similar.
1246ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         *
124771c69897ad0a55d590698bfa399bfe99c763b9dbAdam Powell         * @param resId Resource ID of an icon drawable to use to represent this group
1248ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell         */
1249ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        public void setIconResource(int resId) {
1250ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell            setIconDrawable(sStatic.mResources.getDrawable(resId));
1251ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell        }
1252ae20ae1a8aaa013813c356ae1d9541ca7ff020aeAdam Powell
12538e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
12548e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestSetVolume(int volume) {
12558e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int maxVol = getVolumeMax();
12568e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (maxVol == 0) {
12578e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                return;
12588e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
12598e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
12608e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final float scaledVolume = (float) volume / maxVol;
12618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int routeCount = getRouteCount();
12628e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            for (int i = 0; i < routeCount; i++) {
12638e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = getRouteAt(i);
12648e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int routeVol = (int) (scaledVolume * route.getVolumeMax());
12658e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                route.requestSetVolume(routeVol);
12668e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
12678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (volume != mVolume) {
12688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVolume = volume;
12698e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
12708e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
12718e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
12728e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
12738e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
12748e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void requestUpdateVolume(int direction) {
12758e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int maxVol = getVolumeMax();
12768e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (maxVol == 0) {
12778e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                return;
12788e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
12798e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
12808e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            final int routeCount = getRouteCount();
1281f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            int volume = 0;
12828e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            for (int i = 0; i < routeCount; i++) {
12838e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = getRouteAt(i);
12848e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                route.requestUpdateVolume(direction);
1285f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                final int routeVol = route.getVolume();
1286f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (routeVol > volume) {
1287f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    volume = routeVol;
1288f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
12898e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
12908e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (volume != mVolume) {
12918e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                mVolume = volume;
12928e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                dispatchRouteVolumeChanged(this);
12938e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
12948e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
12958e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
12969a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void memberNameChanged(RouteInfo info, CharSequence name) {
12979a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = true;
12989a1de308cea2d160778fd977825f10a07b49d738Adam Powell            routeUpdated();
12999a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
13009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void memberStatusChanged(RouteInfo info, CharSequence status) {
13029a1de308cea2d160778fd977825f10a07b49d738Adam Powell            setStatusInt(status);
13039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
13049a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1305f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        void memberVolumeChanged(RouteInfo info) {
1306f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            updateVolume();
1307f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        }
1308f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell
1309f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        void updateVolume() {
1310f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            // A group always represents the highest component volume value.
1311f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            final int routeCount = getRouteCount();
1312f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            int volume = 0;
1313f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            for (int i = 0; i < routeCount; i++) {
1314f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                final int routeVol = getRouteAt(i).getVolume();
1315f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                if (routeVol > volume) {
1316f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                    volume = routeVol;
1317f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                }
1318f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            }
1319f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            if (volume != mVolume) {
1320f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                mVolume = volume;
1321f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell                dispatchRouteVolumeChanged(this);
1322f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell            }
1323f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell        }
1324f8ac14a7f5a59b4ec8e89283a2da40b626e42065Adam Powell
1325d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        @Override
1326d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        void routeUpdated() {
1327d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            int types = 0;
1328d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            final int count = mRoutes.size();
1329b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            if (count == 0) {
1330b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                // Don't keep empty groups in the router.
1331b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                MediaRouter.removeRoute(this);
1332b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                return;
1333b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell            }
1334b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell
13358e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            int maxVolume = 0;
13368e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            boolean isLocal = true;
13378e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            boolean isFixedVolume = true;
1338d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            for (int i = 0; i < count; i++) {
13398e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final RouteInfo route = mRoutes.get(i);
13408e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                types |= route.mSupportedTypes;
13418e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int routeMaxVolume = route.getVolumeMax();
13428e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (routeMaxVolume > maxVolume) {
13438e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    maxVolume = routeMaxVolume;
13448e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
13458e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                isLocal &= route.getPlaybackType() == PLAYBACK_TYPE_LOCAL;
13468e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                isFixedVolume &= route.getVolumeHandling() == PLAYBACK_VOLUME_FIXED;
1347d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
13488e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mPlaybackType = isLocal ? PLAYBACK_TYPE_LOCAL : PLAYBACK_TYPE_REMOTE;
13498e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeHandling = isFixedVolume ? PLAYBACK_VOLUME_FIXED : PLAYBACK_VOLUME_VARIABLE;
1350d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            mSupportedTypes = types;
13518e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            mVolumeMax = maxVolume;
1352d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            mIcon = count == 1 ? mRoutes.get(0).getIconDrawable() : null;
1353d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            super.routeUpdated();
1354d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        }
1355d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
13569a1de308cea2d160778fd977825f10a07b49d738Adam Powell        void updateName() {
13579a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final StringBuilder sb = new StringBuilder();
13589a1de308cea2d160778fd977825f10a07b49d738Adam Powell            final int count = mRoutes.size();
13599a1de308cea2d160778fd977825f10a07b49d738Adam Powell            for (int i = 0; i < count; i++) {
13609a1de308cea2d160778fd977825f10a07b49d738Adam Powell                final RouteInfo info = mRoutes.get(i);
1361b5e2af5919351486a385effe77409d2a91ae9c19Adam Powell                // TODO: There's probably a much more correct way to localize this.
13629a1de308cea2d160778fd977825f10a07b49d738Adam Powell                if (i > 0) sb.append(", ");
13639a1de308cea2d160778fd977825f10a07b49d738Adam Powell                sb.append(info.mName);
13649a1de308cea2d160778fd977825f10a07b49d738Adam Powell            }
13659a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = sb.toString();
13669a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mUpdateName = false;
13679a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1368d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell
1369d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        @Override
1370d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        public String toString() {
1371d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            StringBuilder sb = new StringBuilder(super.toString());
1372d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            sb.append('[');
1373d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            final int count = mRoutes.size();
1374d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            for (int i = 0; i < count; i++) {
1375d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                if (i > 0) sb.append(", ");
1376d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                sb.append(mRoutes.get(i));
1377d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            }
1378d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            sb.append(']');
1379d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell            return sb.toString();
1380d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell        }
13819a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
13829a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13839a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
13849a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Definition of a category of routes. All routes belong to a category.
13859a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
1386b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn    public static class RouteCategory {
13879a1de308cea2d160778fd977825f10a07b49d738Adam Powell        CharSequence mName;
13880d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        int mNameResId;
13899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        int mTypes;
13909a1de308cea2d160778fd977825f10a07b49d738Adam Powell        final boolean mGroupable;
13919a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13929a1de308cea2d160778fd977825f10a07b49d738Adam Powell        RouteCategory(CharSequence name, int types, boolean groupable) {
13939a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mName = name;
13949a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mTypes = types;
13959a1de308cea2d160778fd977825f10a07b49d738Adam Powell            mGroupable = groupable;
13969a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
13979a1de308cea2d160778fd977825f10a07b49d738Adam Powell
13980d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        RouteCategory(int nameResId, int types, boolean groupable) {
13990d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mNameResId = nameResId;
14000d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mTypes = types;
14010d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            mGroupable = groupable;
14020d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
14030d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
14049a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
14059a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return the name of this route category
14069a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
14079a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public CharSequence getName() {
14080d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(sStatic.mResources);
14090d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
14100d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
14110d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        /**
14120d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * Return the properly localized/configuration dependent name of this RouteCategory.
14130d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         *
14140d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @param context Context to resolve name resources
14150d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         * @return the name of this route category
14160d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell         */
14170d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public CharSequence getName(Context context) {
14180d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            return getName(context.getResources());
14190d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        }
14200d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell
14210d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        CharSequence getName(Resources res) {
14220d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            if (mNameResId != 0) {
14230d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                return res.getText(mNameResId);
14240d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell            }
14259a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mName;
14269a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14279a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14289a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
1429d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Return the current list of routes in this category that have been added
1430d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * to the MediaRouter.
14319a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1432d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * <p>This list will not include routes that are nested within RouteGroups.
1433d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * A RouteGroup is treated as a single route within its category.</p>
1434d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1435d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param out a List to fill with the routes in this category. If this parameter is
1436d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            non-null, it will be cleared, filled with the current routes with this
1437d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            category, and returned. If this parameter is null, a new List will be
1438d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *            allocated to report the category's current routes.
1439d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @return A list with the routes in this category that have been added to the MediaRouter.
14409a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
1441d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public List<RouteInfo> getRoutes(List<RouteInfo> out) {
1442d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            if (out == null) {
1443d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                out = new ArrayList<RouteInfo>();
1444d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            } else {
1445d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                out.clear();
1446d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
1447d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1448b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            final int count = getRouteCountStatic();
1449d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            for (int i = 0; i < count; i++) {
1450b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn                final RouteInfo route = getRouteAtStatic(i);
1451d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                if (route.mCategory == this) {
1452d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                    out.add(route);
1453d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                }
1454d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            }
1455d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell            return out;
14569a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14579a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14589a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
14599a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return Flag set describing the route types supported by this category
14609a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
14619a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int getSupportedTypes() {
14629a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mTypes;
14639a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14649a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14659a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
14669a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Return whether or not this category supports grouping.
14679a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
14689a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * <p>If this method returns true, all routes obtained from this category
1469d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.</p>
14709a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
14719a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @return true if this category supports
14729a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
14739a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public boolean isGroupable() {
14749a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return mGroupable;
14759a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14769a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14779a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public String toString() {
14789a1de308cea2d160778fd977825f10a07b49d738Adam Powell            return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
1479d6d0bddee363e0c7fe61f63bd9d9864a71d887d6Adam Powell                    " groupable=" + mGroupable + " }";
14809a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14819a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
14829a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14839a1de308cea2d160778fd977825f10a07b49d738Adam Powell    static class CallbackInfo {
14849a1de308cea2d160778fd977825f10a07b49d738Adam Powell        public int type;
1485b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public final Callback cb;
1486b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public final MediaRouter router;
14879a1de308cea2d160778fd977825f10a07b49d738Adam Powell
1488b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn        public CallbackInfo(Callback cb, int type, MediaRouter router) {
14899a1de308cea2d160778fd977825f10a07b49d738Adam Powell            this.cb = cb;
14909a1de308cea2d160778fd977825f10a07b49d738Adam Powell            this.type = type;
1491b58b8f832d06b0ffa8886eba5a4916578a3b8743Dianne Hackborn            this.router = router;
14929a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
14939a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
14949a1de308cea2d160778fd977825f10a07b49d738Adam Powell
14959a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
14969a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * Interface for receiving events about media routing changes.
14979a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * All methods of this interface will be called from the application's main thread.
14989a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
14999a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * <p>A Callback will only receive events relevant to routes that the callback
15009a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * was registered for.</p>
15019a1de308cea2d160778fd977825f10a07b49d738Adam Powell     *
15029a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter#addCallback(int, Callback)
15039a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * @see MediaRouter#removeCallback(Callback)
15049a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
15050d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public static abstract class Callback {
15069a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15079a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when the supplied route becomes selected as the active route
15089a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * for the given route type.
15099a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1510d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
15119a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param type Type flag set indicating the routes that have been selected
15129a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been selected for the given route types
15139a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15140d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteSelected(MediaRouter router, int type, RouteInfo info);
15159a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15169a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15179a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when the supplied route becomes unselected as the active route
15189a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * for the given route type.
15199a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1520d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
15219a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param type Type flag set indicating the routes that have been unselected
15229a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been unselected for the given route types
15239a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15240d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
15259a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15269a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15279a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when a route for the specified type was added.
15289a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1529d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
15309a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has become available for use
15319a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15320d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteAdded(MediaRouter router, RouteInfo info);
15339a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15349a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15359a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when a route for the specified type was removed.
15369a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1537d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
15389a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info Route that has been removed from availability
15399a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15400d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteRemoved(MediaRouter router, RouteInfo info);
15419a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15429a1de308cea2d160778fd977825f10a07b49d738Adam Powell        /**
15439a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * Called when an aspect of the indicated route has changed.
15449a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
15459a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * <p>This will not indicate that the types supported by this route have
15469a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * changed, only that cosmetic info such as name or status have been updated.</p>
15479a1de308cea2d160778fd977825f10a07b49d738Adam Powell         *
1548d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
15499a1de308cea2d160778fd977825f10a07b49d738Adam Powell         * @param info The route that was changed
15509a1de308cea2d160778fd977825f10a07b49d738Adam Powell         */
15510d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteChanged(MediaRouter router, RouteInfo info);
1552d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1553d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1554d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Called when a route is added to a group.
1555d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1556d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
1557d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param info The route that was added
1558d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param group The group the route was added to
1559d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param index The route index within group that info was added at
1560d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
15610d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
15620d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell                int index);
1563d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
1564d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        /**
1565d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * Called when a route is removed from a group.
1566d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         *
1567d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param router the MediaRouter reporting the event
1568d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param info The route that was removed
1569d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         * @param group The group the route was removed from
1570d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell         */
15710d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell        public abstract void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
15728e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
15738e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        /**
15748e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * Called when a route's volume changes.
15758e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         *
15768e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param router the MediaRouter reporting the event
15778e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         * @param info The route with altered volume
15788e37a85bf3dc39519942698dc90a3951306b934bAdam Powell         */
15798e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public abstract void onRouteVolumeChanged(MediaRouter router, RouteInfo info);
15809a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
15819a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15829a1de308cea2d160778fd977825f10a07b49d738Adam Powell    /**
15830d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Stub implementation of {@link MediaRouter.Callback}.
15840d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell     * Each abstract method is defined as a no-op. Override just the ones
15859a1de308cea2d160778fd977825f10a07b49d738Adam Powell     * you need.
15869a1de308cea2d160778fd977825f10a07b49d738Adam Powell     */
15870d03c042f90bf62d5bad7c64e77028a5f9f8fae0Adam Powell    public static class SimpleCallback extends Callback {
15889a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15899a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1590d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
15919a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15929a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15939a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1594d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
15959a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
15969a1de308cea2d160778fd977825f10a07b49d738Adam Powell
15979a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1598d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteAdded(MediaRouter router, RouteInfo info) {
15999a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16009a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16019a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1602d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
16039a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16049a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16059a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1606d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteChanged(MediaRouter router, RouteInfo info) {
16079a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16089a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16099a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1610d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
1611d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell                int index) {
16129a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
16139a1de308cea2d160778fd977825f10a07b49d738Adam Powell
16149a1de308cea2d160778fd977825f10a07b49d738Adam Powell        @Override
1615d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
16169a1de308cea2d160778fd977825f10a07b49d738Adam Powell        }
1617d0d2cda9d414da73773285d7fee9e13aef3495e9Adam Powell
16188e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
16198e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
16208e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
16219a1de308cea2d160778fd977825f10a07b49d738Adam Powell    }
16221357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
16231357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    static class VolumeCallbackInfo {
16241357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final VolumeCallback vcb;
16251357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public final RouteInfo route;
16261357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
16271357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public VolumeCallbackInfo(VolumeCallback vcb, RouteInfo route) {
16281357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            this.vcb = vcb;
16291357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi            this.route = route;
16301357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        }
16311357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    }
16321357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
16331357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    /**
16341357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * Interface for receiving events about volume changes.
16351357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * All methods of this interface will be called from the application's main thread.
16361357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     *
16371357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * <p>A VolumeCallback will only receive events relevant to routes that the callback
16381357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * was registered for.</p>
16391357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     *
16401357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     * @see UserRouteInfo#setVolumeCallback(VolumeCallback)
16411357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi     */
16421357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    public static abstract class VolumeCallback {
16431357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
16441357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Called when the volume for the route should be increased or decreased.
16451357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param info the route affected by this event
16461357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param direction an integer indicating whether the volume is to be increased
16471357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     (positive value) or decreased (negative value).
16481357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     For bundled changes, the absolute value indicates the number of changes
16491357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     in the same direction, e.g. +3 corresponds to three "volume up" changes.
16501357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
16511357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public abstract void onVolumeUpdateRequest(RouteInfo info, int direction);
16521357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        /**
16531357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * Called when the volume for the route should be set to the given value
16541357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param info the route affected by this event
16551357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         * @param volume an integer indicating the new volume value that should be used, always
16561357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         *     between 0 and the value set by {@link UserRouteInfo#setVolumeMax(int)}.
16571357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi         */
16581357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi        public abstract void onVolumeSetRequest(RouteInfo info, int volume);
16591357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi    }
16601357012968f9066ea3051d83995e9bac69526c3cJean-Michel Trivi
16618e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    static class VolumeChangeReceiver extends BroadcastReceiver {
16628e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16638e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        @Override
16648e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        public void onReceive(Context context, Intent intent) {
16658e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
16668e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
16678e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        -1);
16688e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (streamType != AudioManager.STREAM_MUSIC) {
16698e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    return;
16708e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
16718e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16728e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
16738e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                final int oldVolume = intent.getIntExtra(
16748e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                        AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
16758e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                if (newVolume != oldVolume) {
16768e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                    systemVolumeChanged(newVolume);
16778e37a85bf3dc39519942698dc90a3951306b934bAdam Powell                }
16788e37a85bf3dc39519942698dc90a3951306b934bAdam Powell            }
16798e37a85bf3dc39519942698dc90a3951306b934bAdam Powell        }
16808e37a85bf3dc39519942698dc90a3951306b934bAdam Powell
16818e37a85bf3dc39519942698dc90a3951306b934bAdam Powell    }
16829a1de308cea2d160778fd977825f10a07b49d738Adam Powell}
1683