1c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown/*
2c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * Copyright (C) 2013 The Android Open Source Project
3c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown *
4c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * Licensed under the Apache License, Version 2.0 (the "License");
5c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * you may not use this file except in compliance with the License.
6c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * You may obtain a copy of the License at
7c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown *
8c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown *      http://www.apache.org/licenses/LICENSE-2.0
9c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown *
10c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * Unless required by applicable law or agreed to in writing, software
11c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * distributed under the License is distributed on an "AS IS" BASIS,
12c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * See the License for the specific language governing permissions and
14c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * limitations under the License.
15c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown */
16c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
17b507e525a61ed761eecfc2eaaf19af7e8db5dca5Jeff Brownpackage android.support.v7.media;
18c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
19c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.BroadcastReceiver;
20adf0f4a217e14894af07dfa9f46cad7d98b8a7f4Jeff Brownimport android.content.ComponentName;
21c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.Context;
22c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.Intent;
23c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.IntentFilter;
24b507e525a61ed761eecfc2eaaf19af7e8db5dca5Jeff Brownimport android.content.res.Resources;
25c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.media.AudioManager;
26c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.os.Build;
27b507e525a61ed761eecfc2eaaf19af7e8db5dca5Jeff Brownimport android.support.v7.mediarouter.R;
28c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.view.Display;
29c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
30c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport java.util.ArrayList;
3111417b1cfde8f1749905f2d735623af9214148afJeff Brownimport java.util.List;
32cb63b6ecac9786891514f241dec71695f09d3efbJeff Brownimport java.util.Locale;
33c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
34c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown/**
35c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * Provides routes for built-in system destinations such as the local display
36c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * and speaker.  On Jellybean and newer platform releases, queries the framework
37c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * MediaRouter for framework-provided routes and registers non-framework-provided
38c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * routes as user routes.
39c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown */
40c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownabstract class SystemMediaRouteProvider extends MediaRouteProvider {
41c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    private static final String TAG = "SystemMediaRouteProvider";
42c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
43fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown    public static final String PACKAGE_NAME = "android";
44c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
45c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
46c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    protected SystemMediaRouteProvider(Context context) {
47adf0f4a217e14894af07dfa9f46cad7d98b8a7f4Jeff Brown        super(context, new ProviderMetadata(new ComponentName(PACKAGE_NAME,
48adf0f4a217e14894af07dfa9f46cad7d98b8a7f4Jeff Brown                SystemMediaRouteProvider.class.getName())));
49c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
50c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
51c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) {
52c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (Build.VERSION.SDK_INT >= 18) {
53c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return new JellybeanMr2Impl(context, syncCallback);
54c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
55c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (Build.VERSION.SDK_INT >= 17) {
56c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return new JellybeanMr1Impl(context, syncCallback);
57c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
58c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (Build.VERSION.SDK_INT >= 16) {
59c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return new JellybeanImpl(context, syncCallback);
60c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
61c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        return new LegacyImpl(context);
62c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
63c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
64c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
65c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Called by the media router when a route is added to synchronize state with
66c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * the framework media router.
67c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
68c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
69c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
70c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
71c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
72c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Called by the media router when a route is removed to synchronize state with
73c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * the framework media router.
74c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
75c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
76c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
77c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
78c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
79c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Called by the media router when a route is changed to synchronize state with
80c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * the framework media router.
81c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
82c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
83c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
84c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
85c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
86c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Called by the media router when a route is selected to synchronize state with
87c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * the framework media router.
88c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
89c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
90c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
91c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
92c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
93c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Callbacks into the media router to synchronize state with the framework media router.
94c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
95c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public interface SyncCallback {
96c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public MediaRouter.RouteInfo getSystemRouteByDescriptorId(String id);
97c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
98c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
99c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
100c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Legacy implementation for platform versions prior to Jellybean.
101c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
102c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    static class LegacyImpl extends SystemMediaRouteProvider {
103c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC;
104c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
10511417b1cfde8f1749905f2d735623af9214148afJeff Brown        private static final ArrayList<IntentFilter> CONTROL_FILTERS;
106fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        static {
10711417b1cfde8f1749905f2d735623af9214148afJeff Brown            IntentFilter f = new IntentFilter();
10811417b1cfde8f1749905f2d735623af9214148afJeff Brown            f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
10911417b1cfde8f1749905f2d735623af9214148afJeff Brown            f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
11011417b1cfde8f1749905f2d735623af9214148afJeff Brown
11111417b1cfde8f1749905f2d735623af9214148afJeff Brown            CONTROL_FILTERS = new ArrayList<IntentFilter>();
11211417b1cfde8f1749905f2d735623af9214148afJeff Brown            CONTROL_FILTERS.add(f);
113fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        }
114fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
115c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final AudioManager mAudioManager;
116c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final VolumeChangeReceiver mVolumeChangeReceiver;
117c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private int mLastReportedVolume = -1;
118c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
119c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public LegacyImpl(Context context) {
120c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            super(context);
121c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
122c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mVolumeChangeReceiver = new VolumeChangeReceiver();
123c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
124c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            context.registerReceiver(mVolumeChangeReceiver,
125c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    new IntentFilter(VolumeChangeReceiver.VOLUME_CHANGED_ACTION));
126c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            publishRoutes();
127c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
128c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
129c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private void publishRoutes() {
130b507e525a61ed761eecfc2eaaf19af7e8db5dca5Jeff Brown            Resources r = getContext().getResources();
13111417b1cfde8f1749905f2d735623af9214148afJeff Brown            int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
132c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mLastReportedVolume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
13311417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouteDescriptor defaultRoute = new MediaRouteDescriptor.Builder(
1343d4c9459ed77f732dd3ba602713af6ebf9280c8cJeff Brown                    DEFAULT_ROUTE_ID, r.getString(R.string.mr_system_route_name))
13511417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .addControlFilters(CONTROL_FILTERS)
13611417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .setPlaybackStream(PLAYBACK_STREAM)
13711417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL)
13811417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
13911417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .setVolumeMax(maxVolume)
14011417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .setVolume(mLastReportedVolume)
14111417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .build();
14211417b1cfde8f1749905f2d735623af9214148afJeff Brown
14311417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouteProviderDescriptor providerDescriptor =
14411417b1cfde8f1749905f2d735623af9214148afJeff Brown                    new MediaRouteProviderDescriptor.Builder()
14511417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .addRoute(defaultRoute)
14611417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .build();
147c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            setDescriptor(providerDescriptor);
148c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
149c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
150c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
151c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public RouteController onCreateRouteController(String routeId) {
152c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (routeId.equals(DEFAULT_ROUTE_ID)) {
153c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                return new DefaultRouteController();
154c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
155c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return null;
156c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
157c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
158c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        final class DefaultRouteController extends RouteController {
159c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            @Override
160129abf73ce9be1bc172b945263c7975ad1a3006fJeff Brown            public void onSetVolume(int volume) {
161c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
162c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                publishRoutes();
163c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
164c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
165c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            @Override
166129abf73ce9be1bc172b945263c7975ad1a3006fJeff Brown            public void onUpdateVolume(int delta) {
167c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int volume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
168c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
169c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int newVolume = Math.min(maxVolume, Math.max(0, volume + delta));
170c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (newVolume != volume) {
171c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
172c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
173c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                publishRoutes();
174c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
175c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
176c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
177c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        final class VolumeChangeReceiver extends BroadcastReceiver {
178c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // These constants come from AudioManager.
179c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final String VOLUME_CHANGED_ACTION =
180c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    "android.media.VOLUME_CHANGED_ACTION";
181c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final String EXTRA_VOLUME_STREAM_TYPE =
182c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    "android.media.EXTRA_VOLUME_STREAM_TYPE";
183c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final String EXTRA_VOLUME_STREAM_VALUE =
184c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    "android.media.EXTRA_VOLUME_STREAM_VALUE";
185c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
186c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            @Override
187c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public void onReceive(Context context, Intent intent) {
188c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (intent.getAction().equals(VOLUME_CHANGED_ACTION)) {
189c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    final int streamType = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1);
190c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (streamType == PLAYBACK_STREAM) {
191c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        final int volume = intent.getIntExtra(EXTRA_VOLUME_STREAM_VALUE, -1);
192c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        if (volume >= 0 && volume != mLastReportedVolume) {
193c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            publishRoutes();
194c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        }
195c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
196c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
197c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
198c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
199c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
200c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
201c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
202c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Jellybean implementation.
203c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
204c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    static class JellybeanImpl extends SystemMediaRouteProvider
205c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            implements MediaRouterJellybean.Callback, MediaRouterJellybean.VolumeCallback {
20611417b1cfde8f1749905f2d735623af9214148afJeff Brown        private static final ArrayList<IntentFilter> LIVE_AUDIO_CONTROL_FILTERS;
207fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        static {
20811417b1cfde8f1749905f2d735623af9214148afJeff Brown            IntentFilter f = new IntentFilter();
20911417b1cfde8f1749905f2d735623af9214148afJeff Brown            f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
210fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
21111417b1cfde8f1749905f2d735623af9214148afJeff Brown            LIVE_AUDIO_CONTROL_FILTERS = new ArrayList<IntentFilter>();
21211417b1cfde8f1749905f2d735623af9214148afJeff Brown            LIVE_AUDIO_CONTROL_FILTERS.add(f);
213fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        }
214fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
21511417b1cfde8f1749905f2d735623af9214148afJeff Brown        private static final ArrayList<IntentFilter> LIVE_VIDEO_CONTROL_FILTERS;
216fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        static {
21711417b1cfde8f1749905f2d735623af9214148afJeff Brown            IntentFilter f = new IntentFilter();
21811417b1cfde8f1749905f2d735623af9214148afJeff Brown            f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
21911417b1cfde8f1749905f2d735623af9214148afJeff Brown
22011417b1cfde8f1749905f2d735623af9214148afJeff Brown            LIVE_VIDEO_CONTROL_FILTERS = new ArrayList<IntentFilter>();
22111417b1cfde8f1749905f2d735623af9214148afJeff Brown            LIVE_VIDEO_CONTROL_FILTERS.add(f);
222fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        }
223fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
224c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final SyncCallback mSyncCallback;
225c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
226c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final Object mRouterObj;
227c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final Object mCallbackObj;
228c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final Object mVolumeCallbackObj;
229c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final Object mUserRouteCategoryObj;
23011417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected int mRouteTypes;
23111417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected boolean mActiveScan;
23211417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected boolean mCallbackRegistered;
233c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
234c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        // Maintains an association from framework routes to support library routes.
235c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        // Note that we cannot use the tag field for this because an application may
236c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        // have published its own user routes to the framework media router and already
237c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        // used the tag for its own purposes.
238c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final ArrayList<SystemRouteRecord> mSystemRouteRecords =
239c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                new ArrayList<SystemRouteRecord>();
240c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
241c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        // Maintains an association from support library routes to framework routes.
242c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final ArrayList<UserRouteRecord> mUserRouteRecords =
243c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                new ArrayList<UserRouteRecord>();
244c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
24511417b1cfde8f1749905f2d735623af9214148afJeff Brown        private MediaRouterJellybean.SelectRouteWorkaround mSelectRouteWorkaround;
24611417b1cfde8f1749905f2d735623af9214148afJeff Brown        private MediaRouterJellybean.GetDefaultRouteWorkaround mGetDefaultRouteWorkaround;
24711417b1cfde8f1749905f2d735623af9214148afJeff Brown
248c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public JellybeanImpl(Context context, SyncCallback syncCallback) {
249c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            super(context);
250c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mSyncCallback = syncCallback;
251c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mRouterObj = MediaRouterJellybean.getMediaRouter(context);
252c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mCallbackObj = createCallbackObj();
253c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mVolumeCallbackObj = createVolumeCallbackObj();
254c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
255b507e525a61ed761eecfc2eaaf19af7e8db5dca5Jeff Brown            Resources r = context.getResources();
256c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
2573d4c9459ed77f732dd3ba602713af6ebf9280c8cJeff Brown                    mRouterObj, r.getString(R.string.mr_user_route_category_name), false);
258c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
25911417b1cfde8f1749905f2d735623af9214148afJeff Brown            updateSystemRoutes();
260c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
261c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
262c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
26319e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown        public RouteController onCreateRouteController(String routeId) {
26419e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            int index = findSystemRouteRecordByDescriptorId(routeId);
26519e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            if (index >= 0) {
26619e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown                SystemRouteRecord record = mSystemRouteRecords.get(index);
26719e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown                return new SystemRouteController(record.mRouteObj);
26819e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            }
26919e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            return null;
27019e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown        }
27119e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown
27219e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown        @Override
27311417b1cfde8f1749905f2d735623af9214148afJeff Brown        public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
27411417b1cfde8f1749905f2d735623af9214148afJeff Brown            int newRouteTypes = 0;
27511417b1cfde8f1749905f2d735623af9214148afJeff Brown            boolean newActiveScan = false;
27611417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (request != null) {
27711417b1cfde8f1749905f2d735623af9214148afJeff Brown                final MediaRouteSelector selector = request.getSelector();
27811417b1cfde8f1749905f2d735623af9214148afJeff Brown                final List<String> categories = selector.getControlCategories();
27911417b1cfde8f1749905f2d735623af9214148afJeff Brown                final int count = categories.size();
28011417b1cfde8f1749905f2d735623af9214148afJeff Brown                for (int i = 0; i < count; i++) {
28111417b1cfde8f1749905f2d735623af9214148afJeff Brown                    String category = categories.get(i);
28211417b1cfde8f1749905f2d735623af9214148afJeff Brown                    if (category.equals(MediaControlIntent.CATEGORY_LIVE_AUDIO)) {
28311417b1cfde8f1749905f2d735623af9214148afJeff Brown                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO;
28411417b1cfde8f1749905f2d735623af9214148afJeff Brown                    } else if (category.equals(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
28511417b1cfde8f1749905f2d735623af9214148afJeff Brown                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO;
28611417b1cfde8f1749905f2d735623af9214148afJeff Brown                    } else {
28711417b1cfde8f1749905f2d735623af9214148afJeff Brown                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_USER;
28811417b1cfde8f1749905f2d735623af9214148afJeff Brown                    }
289fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                }
29011417b1cfde8f1749905f2d735623af9214148afJeff Brown                newActiveScan = request.isActiveScan();
29111417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
29211417b1cfde8f1749905f2d735623af9214148afJeff Brown
29311417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mRouteTypes != newRouteTypes || mActiveScan != newActiveScan) {
29411417b1cfde8f1749905f2d735623af9214148afJeff Brown                mRouteTypes = newRouteTypes;
29511417b1cfde8f1749905f2d735623af9214148afJeff Brown                mActiveScan = newActiveScan;
29611417b1cfde8f1749905f2d735623af9214148afJeff Brown                updateCallback();
29711417b1cfde8f1749905f2d735623af9214148afJeff Brown                updateSystemRoutes();
298c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
299c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
300c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
30111417b1cfde8f1749905f2d735623af9214148afJeff Brown        @Override
30211417b1cfde8f1749905f2d735623af9214148afJeff Brown        public void onRouteAdded(Object routeObj) {
30311417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (addSystemRouteNoPublish(routeObj)) {
30411417b1cfde8f1749905f2d735623af9214148afJeff Brown                publishRoutes();
30511417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
30611417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
30711417b1cfde8f1749905f2d735623af9214148afJeff Brown
30811417b1cfde8f1749905f2d735623af9214148afJeff Brown        private void updateSystemRoutes() {
30911417b1cfde8f1749905f2d735623af9214148afJeff Brown            boolean changed = false;
310c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (Object routeObj : MediaRouterJellybean.getRoutes(mRouterObj)) {
31111417b1cfde8f1749905f2d735623af9214148afJeff Brown                changed |= addSystemRouteNoPublish(routeObj);
31211417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
31311417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (changed) {
31411417b1cfde8f1749905f2d735623af9214148afJeff Brown                publishRoutes();
315c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
316c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
317c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
31811417b1cfde8f1749905f2d735623af9214148afJeff Brown        private boolean addSystemRouteNoPublish(Object routeObj) {
31911417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (getUserRouteRecord(routeObj) == null
32011417b1cfde8f1749905f2d735623af9214148afJeff Brown                    && findSystemRouteRecord(routeObj) < 0) {
321cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown                String id = assignRouteId(routeObj);
322cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown                SystemRouteRecord record = new SystemRouteRecord(routeObj, id);
323fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                updateSystemRouteDescriptor(record);
324fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                mSystemRouteRecords.add(record);
32511417b1cfde8f1749905f2d735623af9214148afJeff Brown                return true;
326fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            }
32711417b1cfde8f1749905f2d735623af9214148afJeff Brown            return false;
328c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
329c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
330cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown        private String assignRouteId(Object routeObj) {
331cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            // TODO: The framework media router should supply a unique route id that
332cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            // we can use here.  For now we use a hash of the route name and take care
333cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            // to dedupe it.
334cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            boolean isDefault = (getDefaultRoute() == routeObj);
335cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            String id = isDefault ? DEFAULT_ROUTE_ID :
336cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown                    String.format(Locale.US, "ROUTE_%08x", getRouteName(routeObj).hashCode());
337cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            if (findSystemRouteRecordByDescriptorId(id) < 0) {
338cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown                return id;
339cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            }
340cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            for (int i = 2; ; i++) {
341cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown                String newId = String.format(Locale.US, "%s_%d", id, i);
342cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown                if (findSystemRouteRecordByDescriptorId(newId) < 0) {
343cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown                    return newId;
344cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown                }
345cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            }
346cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown        }
347cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown
348c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
349c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteRemoved(Object routeObj) {
350fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (getUserRouteRecord(routeObj) == null) {
351fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                int index = findSystemRouteRecord(routeObj);
352fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                if (index >= 0) {
353fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    mSystemRouteRecords.remove(index);
354fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    publishRoutes();
355fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                }
356c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
357c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
358c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
359c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
360c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteChanged(Object routeObj) {
361fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (getUserRouteRecord(routeObj) == null) {
362fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                int index = findSystemRouteRecord(routeObj);
363fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                if (index >= 0) {
364fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    SystemRouteRecord record = mSystemRouteRecords.get(index);
365fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    updateSystemRouteDescriptor(record);
366fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    publishRoutes();
367fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                }
368c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
369c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
370c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
371c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
372c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteVolumeChanged(Object routeObj) {
373fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (getUserRouteRecord(routeObj) == null) {
374fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                int index = findSystemRouteRecord(routeObj);
375fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                if (index >= 0) {
376fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    SystemRouteRecord record = mSystemRouteRecords.get(index);
377fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    int newVolume = MediaRouterJellybean.RouteInfo.getVolume(routeObj);
378fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    if (newVolume != record.mRouteDescriptor.getVolume()) {
37911417b1cfde8f1749905f2d735623af9214148afJeff Brown                        record.mRouteDescriptor =
38011417b1cfde8f1749905f2d735623af9214148afJeff Brown                                new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
38111417b1cfde8f1749905f2d735623af9214148afJeff Brown                                .setVolume(newVolume)
38211417b1cfde8f1749905f2d735623af9214148afJeff Brown                                .build();
383fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                        publishRoutes();
384fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    }
385c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
386c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
387c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
388c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
389c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
390c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteSelected(int type, Object routeObj) {
39111417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (routeObj != MediaRouterJellybean.getSelectedRoute(mRouterObj,
39211417b1cfde8f1749905f2d735623af9214148afJeff Brown                    MediaRouterJellybean.ALL_ROUTE_TYPES)) {
393fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                // The currently selected route has already changed so this callback
394fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                // is stale.  Drop it to prevent getting into sync loops.
395fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                return;
396fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            }
397fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
398fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            UserRouteRecord userRouteRecord = getUserRouteRecord(routeObj);
399fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (userRouteRecord != null) {
400fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                userRouteRecord.mRoute.select();
401c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            } else {
402c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // Select the route if it already exists in the compat media router.
403c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // If not, we will select it instead when the route is added.
404c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findSystemRouteRecord(routeObj);
405c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
406c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    SystemRouteRecord record = mSystemRouteRecords.get(index);
407c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouter.RouteInfo route = mSyncCallback.getSystemRouteByDescriptorId(
408c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            record.mRouteDescriptorId);
409c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (route != null) {
410c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        route.select();
411c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
412c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
413c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
414c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
415c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
416c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
417c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteUnselected(int type, Object routeObj) {
418c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // Nothing to do when a route is unselected.
419c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // We only need to handle when a route is selected.
420c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
421c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
422c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
423c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteGrouped(Object routeObj, Object groupObj, int index) {
424c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // Route grouping is deprecated and no longer supported.
425c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
426c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
427c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
428c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteUngrouped(Object routeObj, Object groupObj) {
429c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // Route grouping is deprecated and no longer supported.
430c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
431c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
432c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
433c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onVolumeSetRequest(Object routeObj, int volume) {
434fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            UserRouteRecord record = getUserRouteRecord(routeObj);
435fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (record != null) {
436c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                record.mRoute.requestSetVolume(volume);
437c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
438c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
439c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
440c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
441c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onVolumeUpdateRequest(Object routeObj, int direction) {
442fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            UserRouteRecord record = getUserRouteRecord(routeObj);
443fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (record != null) {
444c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                record.mRoute.requestUpdateVolume(direction);
445c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
446c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
447c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
448c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
449c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
450fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (route.getProviderInstance() != this) {
451c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Object routeObj = MediaRouterJellybean.createUserRoute(
452c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mRouterObj, mUserRouteCategoryObj);
453c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                UserRouteRecord record = new UserRouteRecord(route, routeObj);
454c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                MediaRouterJellybean.RouteInfo.setTag(routeObj, record);
455c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj);
456c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                updateUserRouteProperties(record);
457c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mUserRouteRecords.add(record);
458c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                MediaRouterJellybean.addUserRoute(mRouterObj, routeObj);
459c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            } else {
460c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // If the newly added route is the counterpart of the currently selected
461c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // route in the framework media router then ensure it is selected in
462c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // the compat media router.
463c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Object routeObj = MediaRouterJellybean.getSelectedRoute(
46411417b1cfde8f1749905f2d735623af9214148afJeff Brown                        mRouterObj, MediaRouterJellybean.ALL_ROUTE_TYPES);
465c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findSystemRouteRecord(routeObj);
466c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
467c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    SystemRouteRecord record = mSystemRouteRecords.get(index);
468c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (record.mRouteDescriptorId.equals(route.getDescriptorId())) {
469c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        route.select();
470c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
471c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
472c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
473c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
474c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
475c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
476c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
477fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (route.getProviderInstance() != this) {
478c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findUserRouteRecord(route);
479c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
480c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    UserRouteRecord record = mUserRouteRecords.remove(index);
481c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null);
482c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null);
483c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.removeUserRoute(mRouterObj, record.mRouteObj);
484c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
485c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
486c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
487c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
488c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
489c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
490fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (route.getProviderInstance() != this) {
491c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findUserRouteRecord(route);
492c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
493c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    UserRouteRecord record = mUserRouteRecords.get(index);
494c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    updateUserRouteProperties(record);
495c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
496c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
497c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
498c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
499c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
500c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
501fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (!route.isSelected()) {
502fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                // The currently selected route has already changed so this callback
503fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                // is stale.  Drop it to prevent getting into sync loops.
504fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                return;
505fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            }
506fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
507fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (route.getProviderInstance() != this) {
508c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findUserRouteRecord(route);
509c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
510c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    UserRouteRecord record = mUserRouteRecords.get(index);
511c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    selectRoute(record.mRouteObj);
512c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
513c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            } else {
514c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
515c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
516c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    SystemRouteRecord record = mSystemRouteRecords.get(index);
517c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    selectRoute(record.mRouteObj);
518c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
519c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
520c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
521c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
522c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected void publishRoutes() {
52311417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouteProviderDescriptor.Builder builder =
524d11aa1784335270b8d85e385f2c8be79ee6a586cJeff Brown                    new MediaRouteProviderDescriptor.Builder();
525c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int count = mSystemRouteRecords.size();
526c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (int i = 0; i < count; i++) {
52711417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.addRoute(mSystemRouteRecords.get(i).mRouteDescriptor);
528c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
529c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
53011417b1cfde8f1749905f2d735623af9214148afJeff Brown            setDescriptor(builder.build());
531c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
532c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
533c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected int findSystemRouteRecord(Object routeObj) {
534c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            final int count = mSystemRouteRecords.size();
535c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (int i = 0; i < count; i++) {
536c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (mSystemRouteRecords.get(i).mRouteObj == routeObj) {
537c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    return i;
538c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
539c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
540c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return -1;
541c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
542c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
543c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected int findSystemRouteRecordByDescriptorId(String id) {
544c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            final int count = mSystemRouteRecords.size();
545c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (int i = 0; i < count; i++) {
546c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (mSystemRouteRecords.get(i).mRouteDescriptorId.equals(id)) {
547c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    return i;
548c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
549c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
550c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return -1;
551c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
552c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
553c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected int findUserRouteRecord(MediaRouter.RouteInfo route) {
554c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            final int count = mUserRouteRecords.size();
555c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (int i = 0; i < count; i++) {
556c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (mUserRouteRecords.get(i).mRoute == route) {
557c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    return i;
558c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
559c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
560c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return -1;
561c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
562c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
563fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        protected UserRouteRecord getUserRouteRecord(Object routeObj) {
564fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj);
565fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            return tag instanceof UserRouteRecord ? (UserRouteRecord)tag : null;
566fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        }
567fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
568c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected void updateSystemRouteDescriptor(SystemRouteRecord record) {
569fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            // We must always recreate the route descriptor when making any changes
570fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            // because they are intended to be immutable once published.
571cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            MediaRouteDescriptor.Builder builder = new MediaRouteDescriptor.Builder(
572cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown                    record.mRouteDescriptorId, getRouteName(record.mRouteObj));
573cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            onBuildSystemRouteDescriptor(record, builder);
574cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            record.mRouteDescriptor = builder.build();
575cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown        }
576f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown
577cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown        protected String getRouteName(Object routeObj) {
578f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown            // Routes should not have null names but it may happen for badly configured
579f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown            // user routes.  We tolerate this by using an empty name string here but
580f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown            // such unnamed routes will be discarded by the media router upstream
581f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown            // (with a log message so we can track down the problem).
582cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            CharSequence name = MediaRouterJellybean.RouteInfo.getName(routeObj, getContext());
583cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            return name != null ? name.toString() : "";
58411417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
585c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
58611417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
58711417b1cfde8f1749905f2d735623af9214148afJeff Brown                MediaRouteDescriptor.Builder builder) {
588c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes(
589c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj);
590c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) {
59111417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.addControlFilters(LIVE_AUDIO_CONTROL_FILTERS);
59211417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
59311417b1cfde8f1749905f2d735623af9214148afJeff Brown            if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
59411417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.addControlFilters(LIVE_VIDEO_CONTROL_FILTERS);
595c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
596c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
59711417b1cfde8f1749905f2d735623af9214148afJeff Brown            builder.setPlaybackType(
598c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj));
59911417b1cfde8f1749905f2d735623af9214148afJeff Brown            builder.setPlaybackStream(
600c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj));
60111417b1cfde8f1749905f2d735623af9214148afJeff Brown            builder.setVolume(
602c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj));
60311417b1cfde8f1749905f2d735623af9214148afJeff Brown            builder.setVolumeMax(
604c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj));
60511417b1cfde8f1749905f2d735623af9214148afJeff Brown            builder.setVolumeHandling(
606c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj));
607c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
608c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
609c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected void updateUserRouteProperties(UserRouteRecord record) {
610c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setName(
611c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getName());
612c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setPlaybackType(
613c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getPlaybackType());
614c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
615c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getPlaybackStream());
616c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setVolume(
617c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getVolume());
618c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setVolumeMax(
619c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getVolumeMax());
620c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
621c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getVolumeHandling());
622c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
623c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
62411417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected void updateCallback() {
62511417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mCallbackRegistered) {
62611417b1cfde8f1749905f2d735623af9214148afJeff Brown                mCallbackRegistered = false;
62711417b1cfde8f1749905f2d735623af9214148afJeff Brown                MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
62811417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
62911417b1cfde8f1749905f2d735623af9214148afJeff Brown
63011417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mRouteTypes != 0) {
63111417b1cfde8f1749905f2d735623af9214148afJeff Brown                mCallbackRegistered = true;
63211417b1cfde8f1749905f2d735623af9214148afJeff Brown                MediaRouterJellybean.addCallback(mRouterObj, mRouteTypes, mCallbackObj);
63311417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
63411417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
63511417b1cfde8f1749905f2d735623af9214148afJeff Brown
636c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected Object createCallbackObj() {
637c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return MediaRouterJellybean.createCallback(this);
638c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
639c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
640c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected Object createVolumeCallbackObj() {
641c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return MediaRouterJellybean.createVolumeCallback(this);
642c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
643c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
644c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected void selectRoute(Object routeObj) {
64511417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mSelectRouteWorkaround == null) {
64611417b1cfde8f1749905f2d735623af9214148afJeff Brown                mSelectRouteWorkaround = new MediaRouterJellybean.SelectRouteWorkaround();
647c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
64811417b1cfde8f1749905f2d735623af9214148afJeff Brown            mSelectRouteWorkaround.selectRoute(mRouterObj,
64911417b1cfde8f1749905f2d735623af9214148afJeff Brown                    MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
650c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
651c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
652c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected Object getDefaultRoute() {
65311417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mGetDefaultRouteWorkaround == null) {
65411417b1cfde8f1749905f2d735623af9214148afJeff Brown                mGetDefaultRouteWorkaround = new MediaRouterJellybean.GetDefaultRouteWorkaround();
655c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
65611417b1cfde8f1749905f2d735623af9214148afJeff Brown            return mGetDefaultRouteWorkaround.getDefaultRoute(mRouterObj);
657c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
658c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
659c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
660c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Represents a route that is provided by the framework media router
661c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * and published by this route provider to the support library media router.
662c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
663c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected static final class SystemRouteRecord {
664c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public final Object mRouteObj;
665c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public final String mRouteDescriptorId;
66611417b1cfde8f1749905f2d735623af9214148afJeff Brown            public MediaRouteDescriptor mRouteDescriptor; // assigned immediately after creation
667c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
668cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown            public SystemRouteRecord(Object routeObj, String id) {
669c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mRouteObj = routeObj;
670cb63b6ecac9786891514f241dec71695f09d3efbJeff Brown                mRouteDescriptorId = id;
671c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
672c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
673c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
674c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
675c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Represents a route that is provided by the support library media router
676c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * and published by this route provider to the framework media router.
677c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
678c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected static final class UserRouteRecord {
679c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public final MediaRouter.RouteInfo mRoute;
680c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public final Object mRouteObj;
681c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
682c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) {
683c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mRoute = route;
684c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mRouteObj = routeObj;
685c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
686c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
68719e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown
68819e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown        protected final class SystemRouteController extends RouteController {
68919e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            private final Object mRouteObj;
69019e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown
69119e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            public SystemRouteController(Object routeObj) {
69219e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown                mRouteObj = routeObj;
69319e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            }
69419e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown
69519e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            @Override
69619e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            public void onSetVolume(int volume) {
69719e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown                MediaRouterJellybean.RouteInfo.requestSetVolume(mRouteObj, volume);
69819e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            }
69919e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown
70019e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            @Override
70119e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            public void onUpdateVolume(int delta) {
70219e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown                MediaRouterJellybean.RouteInfo.requestUpdateVolume(mRouteObj, delta);
70319e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown            }
70419e77645aa6ce65b466d570375b36e2428f170b3Jeff Brown        }
705c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
706c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
707c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
708c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Jellybean MR1 implementation.
709c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
710c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    private static class JellybeanMr1Impl extends JellybeanImpl
711c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            implements MediaRouterJellybeanMr1.Callback {
71211417b1cfde8f1749905f2d735623af9214148afJeff Brown        private MediaRouterJellybeanMr1.ActiveScanWorkaround mActiveScanWorkaround;
71311417b1cfde8f1749905f2d735623af9214148afJeff Brown        private MediaRouterJellybeanMr1.IsConnectingWorkaround mIsConnectingWorkaround;
71411417b1cfde8f1749905f2d735623af9214148afJeff Brown
715c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public JellybeanMr1Impl(Context context, SyncCallback syncCallback) {
716c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            super(context, syncCallback);
717c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
718c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
719c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
720c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRoutePresentationDisplayChanged(Object routeObj) {
721c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int index = findSystemRouteRecord(routeObj);
722c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (index >= 0) {
723c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                SystemRouteRecord record = mSystemRouteRecords.get(index);
724c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Display newPresentationDisplay =
725c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj);
726c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int newPresentationDisplayId = (newPresentationDisplay != null
727c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        ? newPresentationDisplay.getDisplayId() : -1);
728c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (newPresentationDisplayId
729c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        != record.mRouteDescriptor.getPresentationDisplayId()) {
73011417b1cfde8f1749905f2d735623af9214148afJeff Brown                    record.mRouteDescriptor =
73111417b1cfde8f1749905f2d735623af9214148afJeff Brown                            new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
73211417b1cfde8f1749905f2d735623af9214148afJeff Brown                            .setPresentationDisplayId(newPresentationDisplayId)
73311417b1cfde8f1749905f2d735623af9214148afJeff Brown                            .build();
734c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    publishRoutes();
735c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
736c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
737c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
738c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
739c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
74011417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
74111417b1cfde8f1749905f2d735623af9214148afJeff Brown                MediaRouteDescriptor.Builder builder) {
74211417b1cfde8f1749905f2d735623af9214148afJeff Brown            super.onBuildSystemRouteDescriptor(record, builder);
743c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
744c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) {
74511417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.setEnabled(false);
746c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
74711417b1cfde8f1749905f2d735623af9214148afJeff Brown
74811417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (isConnecting(record)) {
74911417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.setConnecting(true);
75011417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
75111417b1cfde8f1749905f2d735623af9214148afJeff Brown
752c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            Display presentationDisplay =
753c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj);
754c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (presentationDisplay != null) {
75511417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.setPresentationDisplayId(presentationDisplay.getDisplayId());
756c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
757c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
758c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
759c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
76011417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected void updateCallback() {
76111417b1cfde8f1749905f2d735623af9214148afJeff Brown            super.updateCallback();
76211417b1cfde8f1749905f2d735623af9214148afJeff Brown
76311417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mActiveScanWorkaround == null) {
76411417b1cfde8f1749905f2d735623af9214148afJeff Brown                mActiveScanWorkaround = new MediaRouterJellybeanMr1.ActiveScanWorkaround(
76511417b1cfde8f1749905f2d735623af9214148afJeff Brown                        getContext(), getHandler());
76611417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
76711417b1cfde8f1749905f2d735623af9214148afJeff Brown            mActiveScanWorkaround.setActiveScanRouteTypes(mActiveScan ? mRouteTypes : 0);
76811417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
76911417b1cfde8f1749905f2d735623af9214148afJeff Brown
77011417b1cfde8f1749905f2d735623af9214148afJeff Brown        @Override
771c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected Object createCallbackObj() {
772c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return MediaRouterJellybeanMr1.createCallback(this);
773c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
77411417b1cfde8f1749905f2d735623af9214148afJeff Brown
77511417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected boolean isConnecting(SystemRouteRecord record) {
77611417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mIsConnectingWorkaround == null) {
77711417b1cfde8f1749905f2d735623af9214148afJeff Brown                mIsConnectingWorkaround = new MediaRouterJellybeanMr1.IsConnectingWorkaround();
77811417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
77911417b1cfde8f1749905f2d735623af9214148afJeff Brown            return mIsConnectingWorkaround.isConnecting(record.mRouteObj);
78011417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
781c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
782c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
783c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
784c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Jellybean MR2 implementation.
785c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
786c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    private static class JellybeanMr2Impl extends JellybeanMr1Impl {
787c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public JellybeanMr2Impl(Context context, SyncCallback syncCallback) {
788c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            super(context, syncCallback);
789c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
790c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
791c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
792d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
793d63957d28aaabcec588b8cde12eac16414783aebJeff Brown                MediaRouteDescriptor.Builder builder) {
794d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            super.onBuildSystemRouteDescriptor(record, builder);
795d63957d28aaabcec588b8cde12eac16414783aebJeff Brown
796d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            CharSequence description =
797d63957d28aaabcec588b8cde12eac16414783aebJeff Brown                    MediaRouterJellybeanMr2.RouteInfo.getDescription(record.mRouteObj);
798d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            if (description != null) {
799d63957d28aaabcec588b8cde12eac16414783aebJeff Brown                builder.setDescription(description.toString());
800d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            }
801d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        }
802d63957d28aaabcec588b8cde12eac16414783aebJeff Brown
803d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        @Override
804c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected void selectRoute(Object routeObj) {
80511417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouterJellybean.selectRoute(mRouterObj,
80611417b1cfde8f1749905f2d735623af9214148afJeff Brown                    MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
807c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
808c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
809c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
810c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected Object getDefaultRoute() {
811c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return MediaRouterJellybeanMr2.getDefaultRoute(mRouterObj);
812c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
81311417b1cfde8f1749905f2d735623af9214148afJeff Brown
81411417b1cfde8f1749905f2d735623af9214148afJeff Brown        @Override
815d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        protected void updateUserRouteProperties(UserRouteRecord record) {
816d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            super.updateUserRouteProperties(record);
817d63957d28aaabcec588b8cde12eac16414783aebJeff Brown
818d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            MediaRouterJellybeanMr2.UserRouteInfo.setDescription(
819d63957d28aaabcec588b8cde12eac16414783aebJeff Brown                    record.mRouteObj, record.mRoute.getDescription());
820d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        }
821d63957d28aaabcec588b8cde12eac16414783aebJeff Brown
822d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        @Override
82311417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected void updateCallback() {
82411417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mCallbackRegistered) {
82511417b1cfde8f1749905f2d735623af9214148afJeff Brown                MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
82611417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
82711417b1cfde8f1749905f2d735623af9214148afJeff Brown
82811417b1cfde8f1749905f2d735623af9214148afJeff Brown            mCallbackRegistered = true;
82911417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouterJellybeanMr2.addCallback(mRouterObj, mRouteTypes, mCallbackObj,
83011417b1cfde8f1749905f2d735623af9214148afJeff Brown                    MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS
831f03da4a9e6cc02251c2f804eb6f25da61821d6a7Jeff Brown                    | (mActiveScan ? MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN : 0));
83211417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
83311417b1cfde8f1749905f2d735623af9214148afJeff Brown
83411417b1cfde8f1749905f2d735623af9214148afJeff Brown        @Override
83511417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected boolean isConnecting(SystemRouteRecord record) {
83611417b1cfde8f1749905f2d735623af9214148afJeff Brown            return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj);
83711417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
838c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
839c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown}
840