SystemMediaRouteProvider.java revision d11aa1784335270b8d85e385f2c8be79ee6a586c
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;
20c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.Context;
21c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.Intent;
22c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.content.IntentFilter;
23b507e525a61ed761eecfc2eaaf19af7e8db5dca5Jeff Brownimport android.content.res.Resources;
24c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.media.AudioManager;
25c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.os.Build;
26b507e525a61ed761eecfc2eaaf19af7e8db5dca5Jeff Brownimport android.support.v7.mediarouter.R;
27c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport android.view.Display;
28c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
29c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownimport java.util.ArrayList;
3011417b1cfde8f1749905f2d735623af9214148afJeff Brownimport java.util.List;
31c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
32c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown/**
33c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * Provides routes for built-in system destinations such as the local display
34c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * and speaker.  On Jellybean and newer platform releases, queries the framework
35c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * MediaRouter for framework-provided routes and registers non-framework-provided
36c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown * routes as user routes.
37c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown */
38c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brownabstract class SystemMediaRouteProvider extends MediaRouteProvider {
39c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    private static final String TAG = "SystemMediaRouteProvider";
40c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
41fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown    public static final String PACKAGE_NAME = "android";
42c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
43c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
44c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    protected SystemMediaRouteProvider(Context context) {
45fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        super(context, new ProviderMetadata(PACKAGE_NAME));
46c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
47c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
48c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) {
49c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (Build.VERSION.SDK_INT >= 18) {
50c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return new JellybeanMr2Impl(context, syncCallback);
51c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
52c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (Build.VERSION.SDK_INT >= 17) {
53c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return new JellybeanMr1Impl(context, syncCallback);
54c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
55c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        if (Build.VERSION.SDK_INT >= 16) {
56c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return new JellybeanImpl(context, syncCallback);
57c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
58c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        return new LegacyImpl(context);
59c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
60c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
61c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
62c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Called by the media router when a route is added to synchronize state with
63c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * the framework media router.
64c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
65c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
66c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
67c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
68c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
69c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Called by the media router when a route is removed to synchronize state with
70c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * the framework media router.
71c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
72c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
73c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
74c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
75c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
76c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Called by the media router when a route is changed to synchronize state with
77c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * the framework media router.
78c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
79c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
80c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
81c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
82c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
83c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Called by the media router when a route is selected to synchronize state with
84c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * the framework media router.
85c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
86c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
87c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
88c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
89c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
90c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Callbacks into the media router to synchronize state with the framework media router.
91c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
92c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    public interface SyncCallback {
93c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public MediaRouter.RouteInfo getSystemRouteByDescriptorId(String id);
94c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
95c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
96c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
97c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Legacy implementation for platform versions prior to Jellybean.
98c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
99c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    static class LegacyImpl extends SystemMediaRouteProvider {
100c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC;
101c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
10211417b1cfde8f1749905f2d735623af9214148afJeff Brown        private static final ArrayList<IntentFilter> CONTROL_FILTERS;
103fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        static {
10411417b1cfde8f1749905f2d735623af9214148afJeff Brown            IntentFilter f = new IntentFilter();
10511417b1cfde8f1749905f2d735623af9214148afJeff Brown            f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
10611417b1cfde8f1749905f2d735623af9214148afJeff Brown            f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
10711417b1cfde8f1749905f2d735623af9214148afJeff Brown
10811417b1cfde8f1749905f2d735623af9214148afJeff Brown            CONTROL_FILTERS = new ArrayList<IntentFilter>();
10911417b1cfde8f1749905f2d735623af9214148afJeff Brown            CONTROL_FILTERS.add(f);
110fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        }
111fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
112c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final AudioManager mAudioManager;
113c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final VolumeChangeReceiver mVolumeChangeReceiver;
114c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private int mLastReportedVolume = -1;
115c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
116c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public LegacyImpl(Context context) {
117c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            super(context);
118c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
119c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mVolumeChangeReceiver = new VolumeChangeReceiver();
120c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
121c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            context.registerReceiver(mVolumeChangeReceiver,
122c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    new IntentFilter(VolumeChangeReceiver.VOLUME_CHANGED_ACTION));
123c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            publishRoutes();
124c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
125c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
126c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private void publishRoutes() {
127b507e525a61ed761eecfc2eaaf19af7e8db5dca5Jeff Brown            Resources r = getContext().getResources();
12811417b1cfde8f1749905f2d735623af9214148afJeff Brown            int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
129c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mLastReportedVolume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
13011417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouteDescriptor defaultRoute = new MediaRouteDescriptor.Builder(
1313d4c9459ed77f732dd3ba602713af6ebf9280c8cJeff Brown                    DEFAULT_ROUTE_ID, r.getString(R.string.mr_system_route_name))
13211417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .addControlFilters(CONTROL_FILTERS)
13311417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .setPlaybackStream(PLAYBACK_STREAM)
13411417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL)
13511417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
13611417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .setVolumeMax(maxVolume)
13711417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .setVolume(mLastReportedVolume)
13811417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .build();
13911417b1cfde8f1749905f2d735623af9214148afJeff Brown
14011417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouteProviderDescriptor providerDescriptor =
14111417b1cfde8f1749905f2d735623af9214148afJeff Brown                    new MediaRouteProviderDescriptor.Builder()
14211417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .addRoute(defaultRoute)
14311417b1cfde8f1749905f2d735623af9214148afJeff Brown                    .build();
144c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            setDescriptor(providerDescriptor);
145c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
146c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
147c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
148c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public RouteController onCreateRouteController(String routeId) {
149c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (routeId.equals(DEFAULT_ROUTE_ID)) {
150c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                return new DefaultRouteController();
151c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
152c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return null;
153c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
154c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
155c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        final class DefaultRouteController extends RouteController {
156c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            @Override
157129abf73ce9be1bc172b945263c7975ad1a3006fJeff Brown            public void onSetVolume(int volume) {
158c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
159c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                publishRoutes();
160c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
161c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
162c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            @Override
163129abf73ce9be1bc172b945263c7975ad1a3006fJeff Brown            public void onUpdateVolume(int delta) {
164c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int volume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
165c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
166c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int newVolume = Math.min(maxVolume, Math.max(0, volume + delta));
167c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (newVolume != volume) {
168c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
169c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
170c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                publishRoutes();
171c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
172c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
173c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
174c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        final class VolumeChangeReceiver extends BroadcastReceiver {
175c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // These constants come from AudioManager.
176c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final String VOLUME_CHANGED_ACTION =
177c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    "android.media.VOLUME_CHANGED_ACTION";
178c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final String EXTRA_VOLUME_STREAM_TYPE =
179c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    "android.media.EXTRA_VOLUME_STREAM_TYPE";
180c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public static final String EXTRA_VOLUME_STREAM_VALUE =
181c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    "android.media.EXTRA_VOLUME_STREAM_VALUE";
182c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
183c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            @Override
184c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public void onReceive(Context context, Intent intent) {
185c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (intent.getAction().equals(VOLUME_CHANGED_ACTION)) {
186c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    final int streamType = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1);
187c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (streamType == PLAYBACK_STREAM) {
188c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        final int volume = intent.getIntExtra(EXTRA_VOLUME_STREAM_VALUE, -1);
189c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        if (volume >= 0 && volume != mLastReportedVolume) {
190c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            publishRoutes();
191c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        }
192c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
193c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
194c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
195c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
196c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
197c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
198c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
199c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Jellybean implementation.
200c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
201c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    static class JellybeanImpl extends SystemMediaRouteProvider
202c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            implements MediaRouterJellybean.Callback, MediaRouterJellybean.VolumeCallback {
20311417b1cfde8f1749905f2d735623af9214148afJeff Brown        private static final ArrayList<IntentFilter> LIVE_AUDIO_CONTROL_FILTERS;
204fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        static {
20511417b1cfde8f1749905f2d735623af9214148afJeff Brown            IntentFilter f = new IntentFilter();
20611417b1cfde8f1749905f2d735623af9214148afJeff Brown            f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
207fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
20811417b1cfde8f1749905f2d735623af9214148afJeff Brown            LIVE_AUDIO_CONTROL_FILTERS = new ArrayList<IntentFilter>();
20911417b1cfde8f1749905f2d735623af9214148afJeff Brown            LIVE_AUDIO_CONTROL_FILTERS.add(f);
210fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        }
211fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
21211417b1cfde8f1749905f2d735623af9214148afJeff Brown        private static final ArrayList<IntentFilter> LIVE_VIDEO_CONTROL_FILTERS;
213fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        static {
21411417b1cfde8f1749905f2d735623af9214148afJeff Brown            IntentFilter f = new IntentFilter();
21511417b1cfde8f1749905f2d735623af9214148afJeff Brown            f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
21611417b1cfde8f1749905f2d735623af9214148afJeff Brown
21711417b1cfde8f1749905f2d735623af9214148afJeff Brown            LIVE_VIDEO_CONTROL_FILTERS = new ArrayList<IntentFilter>();
21811417b1cfde8f1749905f2d735623af9214148afJeff Brown            LIVE_VIDEO_CONTROL_FILTERS.add(f);
219fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        }
220fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
221c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        private final SyncCallback mSyncCallback;
222c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
223c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final Object mRouterObj;
224c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final Object mCallbackObj;
225c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final Object mVolumeCallbackObj;
226c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final Object mUserRouteCategoryObj;
22711417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected int mRouteTypes;
22811417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected boolean mActiveScan;
22911417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected boolean mCallbackRegistered;
230c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
231c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        // Maintains an association from framework routes to support library routes.
232c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        // Note that we cannot use the tag field for this because an application may
233c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        // have published its own user routes to the framework media router and already
234c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        // used the tag for its own purposes.
235c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final ArrayList<SystemRouteRecord> mSystemRouteRecords =
236c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                new ArrayList<SystemRouteRecord>();
237c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
238c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        // Maintains an association from support library routes to framework routes.
239c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected final ArrayList<UserRouteRecord> mUserRouteRecords =
240c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                new ArrayList<UserRouteRecord>();
241c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
24211417b1cfde8f1749905f2d735623af9214148afJeff Brown        private MediaRouterJellybean.SelectRouteWorkaround mSelectRouteWorkaround;
24311417b1cfde8f1749905f2d735623af9214148afJeff Brown        private MediaRouterJellybean.GetDefaultRouteWorkaround mGetDefaultRouteWorkaround;
24411417b1cfde8f1749905f2d735623af9214148afJeff Brown
245c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public JellybeanImpl(Context context, SyncCallback syncCallback) {
246c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            super(context);
247c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mSyncCallback = syncCallback;
248c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mRouterObj = MediaRouterJellybean.getMediaRouter(context);
249c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mCallbackObj = createCallbackObj();
250c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mVolumeCallbackObj = createVolumeCallbackObj();
251c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
252b507e525a61ed761eecfc2eaaf19af7e8db5dca5Jeff Brown            Resources r = context.getResources();
253c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
2543d4c9459ed77f732dd3ba602713af6ebf9280c8cJeff Brown                    mRouterObj, r.getString(R.string.mr_user_route_category_name), false);
255c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
25611417b1cfde8f1749905f2d735623af9214148afJeff Brown            updateSystemRoutes();
257c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
258c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
259c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
26011417b1cfde8f1749905f2d735623af9214148afJeff Brown        public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
26111417b1cfde8f1749905f2d735623af9214148afJeff Brown            int newRouteTypes = 0;
26211417b1cfde8f1749905f2d735623af9214148afJeff Brown            boolean newActiveScan = false;
26311417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (request != null) {
26411417b1cfde8f1749905f2d735623af9214148afJeff Brown                final MediaRouteSelector selector = request.getSelector();
26511417b1cfde8f1749905f2d735623af9214148afJeff Brown                final List<String> categories = selector.getControlCategories();
26611417b1cfde8f1749905f2d735623af9214148afJeff Brown                final int count = categories.size();
26711417b1cfde8f1749905f2d735623af9214148afJeff Brown                for (int i = 0; i < count; i++) {
26811417b1cfde8f1749905f2d735623af9214148afJeff Brown                    String category = categories.get(i);
26911417b1cfde8f1749905f2d735623af9214148afJeff Brown                    if (category.equals(MediaControlIntent.CATEGORY_LIVE_AUDIO)) {
27011417b1cfde8f1749905f2d735623af9214148afJeff Brown                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO;
27111417b1cfde8f1749905f2d735623af9214148afJeff Brown                    } else if (category.equals(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
27211417b1cfde8f1749905f2d735623af9214148afJeff Brown                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO;
27311417b1cfde8f1749905f2d735623af9214148afJeff Brown                    } else {
27411417b1cfde8f1749905f2d735623af9214148afJeff Brown                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_USER;
27511417b1cfde8f1749905f2d735623af9214148afJeff Brown                    }
276fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                }
27711417b1cfde8f1749905f2d735623af9214148afJeff Brown                newActiveScan = request.isActiveScan();
27811417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
27911417b1cfde8f1749905f2d735623af9214148afJeff Brown
28011417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mRouteTypes != newRouteTypes || mActiveScan != newActiveScan) {
28111417b1cfde8f1749905f2d735623af9214148afJeff Brown                mRouteTypes = newRouteTypes;
28211417b1cfde8f1749905f2d735623af9214148afJeff Brown                mActiveScan = newActiveScan;
28311417b1cfde8f1749905f2d735623af9214148afJeff Brown                updateCallback();
28411417b1cfde8f1749905f2d735623af9214148afJeff Brown                updateSystemRoutes();
285c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
286c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
287c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
28811417b1cfde8f1749905f2d735623af9214148afJeff Brown        @Override
28911417b1cfde8f1749905f2d735623af9214148afJeff Brown        public void onRouteAdded(Object routeObj) {
29011417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (addSystemRouteNoPublish(routeObj)) {
29111417b1cfde8f1749905f2d735623af9214148afJeff Brown                publishRoutes();
29211417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
29311417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
29411417b1cfde8f1749905f2d735623af9214148afJeff Brown
29511417b1cfde8f1749905f2d735623af9214148afJeff Brown        private void updateSystemRoutes() {
29611417b1cfde8f1749905f2d735623af9214148afJeff Brown            boolean changed = false;
297c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (Object routeObj : MediaRouterJellybean.getRoutes(mRouterObj)) {
29811417b1cfde8f1749905f2d735623af9214148afJeff Brown                changed |= addSystemRouteNoPublish(routeObj);
29911417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
30011417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (changed) {
30111417b1cfde8f1749905f2d735623af9214148afJeff Brown                publishRoutes();
302c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
303c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
304c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
30511417b1cfde8f1749905f2d735623af9214148afJeff Brown        private boolean addSystemRouteNoPublish(Object routeObj) {
30611417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (getUserRouteRecord(routeObj) == null
30711417b1cfde8f1749905f2d735623af9214148afJeff Brown                    && findSystemRouteRecord(routeObj) < 0) {
308fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                boolean isDefault = (getDefaultRoute() == routeObj);
309fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                SystemRouteRecord record = new SystemRouteRecord(routeObj, isDefault);
310fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                updateSystemRouteDescriptor(record);
311fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                mSystemRouteRecords.add(record);
31211417b1cfde8f1749905f2d735623af9214148afJeff Brown                return true;
313fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            }
31411417b1cfde8f1749905f2d735623af9214148afJeff Brown            return false;
315c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
316c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
317c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
318c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteRemoved(Object routeObj) {
319fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (getUserRouteRecord(routeObj) == null) {
320fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                int index = findSystemRouteRecord(routeObj);
321fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                if (index >= 0) {
322fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    mSystemRouteRecords.remove(index);
323fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    publishRoutes();
324fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                }
325c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
326c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
327c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
328c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
329c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteChanged(Object routeObj) {
330fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (getUserRouteRecord(routeObj) == null) {
331fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                int index = findSystemRouteRecord(routeObj);
332fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                if (index >= 0) {
333fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    SystemRouteRecord record = mSystemRouteRecords.get(index);
334fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    updateSystemRouteDescriptor(record);
335fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    publishRoutes();
336fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                }
337c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
338c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
339c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
340c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
341c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteVolumeChanged(Object routeObj) {
342fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (getUserRouteRecord(routeObj) == null) {
343fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                int index = findSystemRouteRecord(routeObj);
344fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                if (index >= 0) {
345fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    SystemRouteRecord record = mSystemRouteRecords.get(index);
346fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    int newVolume = MediaRouterJellybean.RouteInfo.getVolume(routeObj);
347fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    if (newVolume != record.mRouteDescriptor.getVolume()) {
34811417b1cfde8f1749905f2d735623af9214148afJeff Brown                        record.mRouteDescriptor =
34911417b1cfde8f1749905f2d735623af9214148afJeff Brown                                new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
35011417b1cfde8f1749905f2d735623af9214148afJeff Brown                                .setVolume(newVolume)
35111417b1cfde8f1749905f2d735623af9214148afJeff Brown                                .build();
352fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                        publishRoutes();
353fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                    }
354c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
355c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
356c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
357c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
358c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
359c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteSelected(int type, Object routeObj) {
36011417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (routeObj != MediaRouterJellybean.getSelectedRoute(mRouterObj,
36111417b1cfde8f1749905f2d735623af9214148afJeff Brown                    MediaRouterJellybean.ALL_ROUTE_TYPES)) {
362fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                // The currently selected route has already changed so this callback
363fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                // is stale.  Drop it to prevent getting into sync loops.
364fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                return;
365fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            }
366fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
367fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            UserRouteRecord userRouteRecord = getUserRouteRecord(routeObj);
368fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (userRouteRecord != null) {
369fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                userRouteRecord.mRoute.select();
370c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            } else {
371c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // Select the route if it already exists in the compat media router.
372c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // If not, we will select it instead when the route is added.
373c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findSystemRouteRecord(routeObj);
374c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
375c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    SystemRouteRecord record = mSystemRouteRecords.get(index);
376c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouter.RouteInfo route = mSyncCallback.getSystemRouteByDescriptorId(
377c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                            record.mRouteDescriptorId);
378c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (route != null) {
379c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        route.select();
380c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
381c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
382c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
383c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
384c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
385c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
386c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteUnselected(int type, Object routeObj) {
387c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // Nothing to do when a route is unselected.
388c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // We only need to handle when a route is selected.
389c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
390c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
391c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
392c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteGrouped(Object routeObj, Object groupObj, int index) {
393c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // Route grouping is deprecated and no longer supported.
394c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
395c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
396c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
397c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRouteUngrouped(Object routeObj, Object groupObj) {
398c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            // Route grouping is deprecated and no longer supported.
399c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
400c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
401c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
402c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onVolumeSetRequest(Object routeObj, int volume) {
403fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            UserRouteRecord record = getUserRouteRecord(routeObj);
404fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (record != null) {
405c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                record.mRoute.requestSetVolume(volume);
406c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
407c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
408c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
409c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
410c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onVolumeUpdateRequest(Object routeObj, int direction) {
411fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            UserRouteRecord record = getUserRouteRecord(routeObj);
412fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (record != null) {
413c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                record.mRoute.requestUpdateVolume(direction);
414c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
415c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
416c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
417c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
418c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
419fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (route.getProviderInstance() != this) {
420c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Object routeObj = MediaRouterJellybean.createUserRoute(
421c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        mRouterObj, mUserRouteCategoryObj);
422c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                UserRouteRecord record = new UserRouteRecord(route, routeObj);
423c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                MediaRouterJellybean.RouteInfo.setTag(routeObj, record);
424c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj);
425c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                updateUserRouteProperties(record);
426c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mUserRouteRecords.add(record);
427c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                MediaRouterJellybean.addUserRoute(mRouterObj, routeObj);
428c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            } else {
429c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // If the newly added route is the counterpart of the currently selected
430c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // route in the framework media router then ensure it is selected in
431c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                // the compat media router.
432c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Object routeObj = MediaRouterJellybean.getSelectedRoute(
43311417b1cfde8f1749905f2d735623af9214148afJeff Brown                        mRouterObj, MediaRouterJellybean.ALL_ROUTE_TYPES);
434c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findSystemRouteRecord(routeObj);
435c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
436c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    SystemRouteRecord record = mSystemRouteRecords.get(index);
437c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    if (record.mRouteDescriptorId.equals(route.getDescriptorId())) {
438c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        route.select();
439c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    }
440c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
441c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
442c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
443c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
444c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
445c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
446fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (route.getProviderInstance() != this) {
447c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findUserRouteRecord(route);
448c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
449c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    UserRouteRecord record = mUserRouteRecords.remove(index);
450c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null);
451c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null);
452c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.removeUserRoute(mRouterObj, record.mRouteObj);
453c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
454c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
455c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
456c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
457c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
458c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
459fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (route.getProviderInstance() != this) {
460c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findUserRouteRecord(route);
461c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
462c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    UserRouteRecord record = mUserRouteRecords.get(index);
463c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    updateUserRouteProperties(record);
464c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
465c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
466c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
467c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
468c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
469c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
470fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (!route.isSelected()) {
471fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                // The currently selected route has already changed so this callback
472fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                // is stale.  Drop it to prevent getting into sync loops.
473fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown                return;
474fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            }
475fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
476fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            if (route.getProviderInstance() != this) {
477c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findUserRouteRecord(route);
478c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
479c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    UserRouteRecord record = mUserRouteRecords.get(index);
480c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    selectRoute(record.mRouteObj);
481c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
482c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            } else {
483c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
484c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (index >= 0) {
485c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    SystemRouteRecord record = mSystemRouteRecords.get(index);
486c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    selectRoute(record.mRouteObj);
487c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
488c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
489c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
490c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
491c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected void publishRoutes() {
49211417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouteProviderDescriptor.Builder builder =
493d11aa1784335270b8d85e385f2c8be79ee6a586cJeff Brown                    new MediaRouteProviderDescriptor.Builder();
494c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int count = mSystemRouteRecords.size();
495c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (int i = 0; i < count; i++) {
49611417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.addRoute(mSystemRouteRecords.get(i).mRouteDescriptor);
497c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
498c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
49911417b1cfde8f1749905f2d735623af9214148afJeff Brown            setDescriptor(builder.build());
500c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
501c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
502c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected int findSystemRouteRecord(Object routeObj) {
503c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            final int count = mSystemRouteRecords.size();
504c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (int i = 0; i < count; i++) {
505c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (mSystemRouteRecords.get(i).mRouteObj == routeObj) {
506c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    return i;
507c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
508c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
509c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return -1;
510c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
511c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
512c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected int findSystemRouteRecordByDescriptorId(String id) {
513c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            final int count = mSystemRouteRecords.size();
514c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (int i = 0; i < count; i++) {
515c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (mSystemRouteRecords.get(i).mRouteDescriptorId.equals(id)) {
516c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    return i;
517c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
518c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
519c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return -1;
520c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
521c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
522c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected int findUserRouteRecord(MediaRouter.RouteInfo route) {
523c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            final int count = mUserRouteRecords.size();
524c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            for (int i = 0; i < count; i++) {
525c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (mUserRouteRecords.get(i).mRoute == route) {
526c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    return i;
527c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
528c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
529c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return -1;
530c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
531c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
532fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        protected UserRouteRecord getUserRouteRecord(Object routeObj) {
533fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj);
534fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            return tag instanceof UserRouteRecord ? (UserRouteRecord)tag : null;
535fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown        }
536fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown
537c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected void updateSystemRouteDescriptor(SystemRouteRecord record) {
538fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            // We must always recreate the route descriptor when making any changes
539fa326a4649d9d0e8113e315f6c8251fe686abce4Jeff Brown            // because they are intended to be immutable once published.
540f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown
541f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown            // Routes should not have null names but it may happen for badly configured
542f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown            // user routes.  We tolerate this by using an empty name string here but
543f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown            // such unnamed routes will be discarded by the media router upstream
544f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown            // (with a log message so we can track down the problem).
545f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown            CharSequence name = MediaRouterJellybean.RouteInfo.getName(
546f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown                    record.mRouteObj, getContext());
54711417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouteDescriptor.Builder builder = new MediaRouteDescriptor.Builder(
548f6a2eb6ce9fef191df8368681ed5f825dcd24340Jeff Brown                    record.mRouteDescriptorId, name != null ? name.toString() : "");
54911417b1cfde8f1749905f2d735623af9214148afJeff Brown            onBuildSystemRouteDescriptor(record, builder);
55011417b1cfde8f1749905f2d735623af9214148afJeff Brown            record.mRouteDescriptor = builder.build();
55111417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
552c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
55311417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
55411417b1cfde8f1749905f2d735623af9214148afJeff Brown                MediaRouteDescriptor.Builder builder) {
555c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes(
556c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj);
557c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) {
55811417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.addControlFilters(LIVE_AUDIO_CONTROL_FILTERS);
55911417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
56011417b1cfde8f1749905f2d735623af9214148afJeff Brown            if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
56111417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.addControlFilters(LIVE_VIDEO_CONTROL_FILTERS);
562c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
563c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
56411417b1cfde8f1749905f2d735623af9214148afJeff Brown            builder.setPlaybackType(
565c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj));
56611417b1cfde8f1749905f2d735623af9214148afJeff Brown            builder.setPlaybackStream(
567c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj));
56811417b1cfde8f1749905f2d735623af9214148afJeff Brown            builder.setVolume(
569c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj));
57011417b1cfde8f1749905f2d735623af9214148afJeff Brown            builder.setVolumeMax(
571c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj));
57211417b1cfde8f1749905f2d735623af9214148afJeff Brown            builder.setVolumeHandling(
573c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj));
574c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
575c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
576c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected void updateUserRouteProperties(UserRouteRecord record) {
577c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setName(
578c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getName());
579c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setPlaybackType(
580c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getPlaybackType());
581c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
582c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getPlaybackStream());
583c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setVolume(
584c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getVolume());
585c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setVolumeMax(
586c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getVolumeMax());
587c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
588c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    record.mRouteObj, record.mRoute.getVolumeHandling());
589c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
590c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
59111417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected void updateCallback() {
59211417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mCallbackRegistered) {
59311417b1cfde8f1749905f2d735623af9214148afJeff Brown                mCallbackRegistered = false;
59411417b1cfde8f1749905f2d735623af9214148afJeff Brown                MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
59511417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
59611417b1cfde8f1749905f2d735623af9214148afJeff Brown
59711417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mRouteTypes != 0) {
59811417b1cfde8f1749905f2d735623af9214148afJeff Brown                mCallbackRegistered = true;
59911417b1cfde8f1749905f2d735623af9214148afJeff Brown                MediaRouterJellybean.addCallback(mRouterObj, mRouteTypes, mCallbackObj);
60011417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
60111417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
60211417b1cfde8f1749905f2d735623af9214148afJeff Brown
603c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected Object createCallbackObj() {
604c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return MediaRouterJellybean.createCallback(this);
605c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
606c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
607c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected Object createVolumeCallbackObj() {
608c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return MediaRouterJellybean.createVolumeCallback(this);
609c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
610c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
611c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected void selectRoute(Object routeObj) {
61211417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mSelectRouteWorkaround == null) {
61311417b1cfde8f1749905f2d735623af9214148afJeff Brown                mSelectRouteWorkaround = new MediaRouterJellybean.SelectRouteWorkaround();
614c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
61511417b1cfde8f1749905f2d735623af9214148afJeff Brown            mSelectRouteWorkaround.selectRoute(mRouterObj,
61611417b1cfde8f1749905f2d735623af9214148afJeff Brown                    MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
617c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
618c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
619c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected Object getDefaultRoute() {
62011417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mGetDefaultRouteWorkaround == null) {
62111417b1cfde8f1749905f2d735623af9214148afJeff Brown                mGetDefaultRouteWorkaround = new MediaRouterJellybean.GetDefaultRouteWorkaround();
622c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
62311417b1cfde8f1749905f2d735623af9214148afJeff Brown            return mGetDefaultRouteWorkaround.getDefaultRoute(mRouterObj);
624c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
625c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
626c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
627c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Represents a route that is provided by the framework media router
628c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * and published by this route provider to the support library media router.
629c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
630c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected static final class SystemRouteRecord {
631c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            private static int sNextId;
632c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
633c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public final Object mRouteObj;
634c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public final String mRouteDescriptorId;
63511417b1cfde8f1749905f2d735623af9214148afJeff Brown            public MediaRouteDescriptor mRouteDescriptor; // assigned immediately after creation
636c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
637c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public SystemRouteRecord(Object routeObj, boolean isDefault) {
638c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mRouteObj = routeObj;
639c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mRouteDescriptorId = isDefault ? DEFAULT_ROUTE_ID : "ROUTE_" + (sNextId++);
640c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
641c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
642c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
643c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        /**
644c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * Represents a route that is provided by the support library media router
645c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         * and published by this route provider to the framework media router.
646c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown         */
647c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected static final class UserRouteRecord {
648c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public final MediaRouter.RouteInfo mRoute;
649c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public final Object mRouteObj;
650c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
651c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) {
652c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mRoute = route;
653c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                mRouteObj = routeObj;
654c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
655c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
656c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
657c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
658c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
659c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Jellybean MR1 implementation.
660c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
661c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    private static class JellybeanMr1Impl extends JellybeanImpl
662c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            implements MediaRouterJellybeanMr1.Callback {
66311417b1cfde8f1749905f2d735623af9214148afJeff Brown        private MediaRouterJellybeanMr1.ActiveScanWorkaround mActiveScanWorkaround;
66411417b1cfde8f1749905f2d735623af9214148afJeff Brown        private MediaRouterJellybeanMr1.IsConnectingWorkaround mIsConnectingWorkaround;
66511417b1cfde8f1749905f2d735623af9214148afJeff Brown
666c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public JellybeanMr1Impl(Context context, SyncCallback syncCallback) {
667c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            super(context, syncCallback);
668c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
669c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
670c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
671c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public void onRoutePresentationDisplayChanged(Object routeObj) {
672c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            int index = findSystemRouteRecord(routeObj);
673c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (index >= 0) {
674c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                SystemRouteRecord record = mSystemRouteRecords.get(index);
675c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                Display newPresentationDisplay =
676c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj);
677c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                int newPresentationDisplayId = (newPresentationDisplay != null
678c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        ? newPresentationDisplay.getDisplayId() : -1);
679c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                if (newPresentationDisplayId
680c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                        != record.mRouteDescriptor.getPresentationDisplayId()) {
68111417b1cfde8f1749905f2d735623af9214148afJeff Brown                    record.mRouteDescriptor =
68211417b1cfde8f1749905f2d735623af9214148afJeff Brown                            new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
68311417b1cfde8f1749905f2d735623af9214148afJeff Brown                            .setPresentationDisplayId(newPresentationDisplayId)
68411417b1cfde8f1749905f2d735623af9214148afJeff Brown                            .build();
685c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    publishRoutes();
686c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                }
687c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
688c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
689c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
690c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
69111417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
69211417b1cfde8f1749905f2d735623af9214148afJeff Brown                MediaRouteDescriptor.Builder builder) {
69311417b1cfde8f1749905f2d735623af9214148afJeff Brown            super.onBuildSystemRouteDescriptor(record, builder);
694c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
695c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) {
69611417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.setEnabled(false);
697c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
69811417b1cfde8f1749905f2d735623af9214148afJeff Brown
69911417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (isConnecting(record)) {
70011417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.setConnecting(true);
70111417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
70211417b1cfde8f1749905f2d735623af9214148afJeff Brown
703c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            Display presentationDisplay =
704c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown                    MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj);
705c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            if (presentationDisplay != null) {
70611417b1cfde8f1749905f2d735623af9214148afJeff Brown                builder.setPresentationDisplayId(presentationDisplay.getDisplayId());
707c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            }
708c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
709c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
710c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
71111417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected void updateCallback() {
71211417b1cfde8f1749905f2d735623af9214148afJeff Brown            super.updateCallback();
71311417b1cfde8f1749905f2d735623af9214148afJeff Brown
71411417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mActiveScanWorkaround == null) {
71511417b1cfde8f1749905f2d735623af9214148afJeff Brown                mActiveScanWorkaround = new MediaRouterJellybeanMr1.ActiveScanWorkaround(
71611417b1cfde8f1749905f2d735623af9214148afJeff Brown                        getContext(), getHandler());
71711417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
71811417b1cfde8f1749905f2d735623af9214148afJeff Brown            mActiveScanWorkaround.setActiveScanRouteTypes(mActiveScan ? mRouteTypes : 0);
71911417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
72011417b1cfde8f1749905f2d735623af9214148afJeff Brown
72111417b1cfde8f1749905f2d735623af9214148afJeff Brown        @Override
722c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected Object createCallbackObj() {
723c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return MediaRouterJellybeanMr1.createCallback(this);
724c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
72511417b1cfde8f1749905f2d735623af9214148afJeff Brown
72611417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected boolean isConnecting(SystemRouteRecord record) {
72711417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mIsConnectingWorkaround == null) {
72811417b1cfde8f1749905f2d735623af9214148afJeff Brown                mIsConnectingWorkaround = new MediaRouterJellybeanMr1.IsConnectingWorkaround();
72911417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
73011417b1cfde8f1749905f2d735623af9214148afJeff Brown            return mIsConnectingWorkaround.isConnecting(record.mRouteObj);
73111417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
732c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
733c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
734c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    /**
735c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     * Jellybean MR2 implementation.
736c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown     */
737c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    private static class JellybeanMr2Impl extends JellybeanMr1Impl {
738c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        public JellybeanMr2Impl(Context context, SyncCallback syncCallback) {
739c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            super(context, syncCallback);
740c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
741c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
742c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
743d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
744d63957d28aaabcec588b8cde12eac16414783aebJeff Brown                MediaRouteDescriptor.Builder builder) {
745d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            super.onBuildSystemRouteDescriptor(record, builder);
746d63957d28aaabcec588b8cde12eac16414783aebJeff Brown
747d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            CharSequence description =
748d63957d28aaabcec588b8cde12eac16414783aebJeff Brown                    MediaRouterJellybeanMr2.RouteInfo.getDescription(record.mRouteObj);
749d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            if (description != null) {
750d63957d28aaabcec588b8cde12eac16414783aebJeff Brown                builder.setDescription(description.toString());
751d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            }
752d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        }
753d63957d28aaabcec588b8cde12eac16414783aebJeff Brown
754d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        @Override
755c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected void selectRoute(Object routeObj) {
75611417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouterJellybean.selectRoute(mRouterObj,
75711417b1cfde8f1749905f2d735623af9214148afJeff Brown                    MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
758c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
759c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown
760c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        @Override
761c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        protected Object getDefaultRoute() {
762c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown            return MediaRouterJellybeanMr2.getDefaultRoute(mRouterObj);
763c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown        }
76411417b1cfde8f1749905f2d735623af9214148afJeff Brown
76511417b1cfde8f1749905f2d735623af9214148afJeff Brown        @Override
766d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        protected void updateUserRouteProperties(UserRouteRecord record) {
767d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            super.updateUserRouteProperties(record);
768d63957d28aaabcec588b8cde12eac16414783aebJeff Brown
769d63957d28aaabcec588b8cde12eac16414783aebJeff Brown            MediaRouterJellybeanMr2.UserRouteInfo.setDescription(
770d63957d28aaabcec588b8cde12eac16414783aebJeff Brown                    record.mRouteObj, record.mRoute.getDescription());
771d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        }
772d63957d28aaabcec588b8cde12eac16414783aebJeff Brown
773d63957d28aaabcec588b8cde12eac16414783aebJeff Brown        @Override
77411417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected void updateCallback() {
77511417b1cfde8f1749905f2d735623af9214148afJeff Brown            if (mCallbackRegistered) {
77611417b1cfde8f1749905f2d735623af9214148afJeff Brown                MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
77711417b1cfde8f1749905f2d735623af9214148afJeff Brown            }
77811417b1cfde8f1749905f2d735623af9214148afJeff Brown
77911417b1cfde8f1749905f2d735623af9214148afJeff Brown            mCallbackRegistered = true;
78011417b1cfde8f1749905f2d735623af9214148afJeff Brown            MediaRouterJellybeanMr2.addCallback(mRouterObj, mRouteTypes, mCallbackObj,
78111417b1cfde8f1749905f2d735623af9214148afJeff Brown                    MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS
782f03da4a9e6cc02251c2f804eb6f25da61821d6a7Jeff Brown                    | (mActiveScan ? MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN : 0));
78311417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
78411417b1cfde8f1749905f2d735623af9214148afJeff Brown
78511417b1cfde8f1749905f2d735623af9214148afJeff Brown        @Override
78611417b1cfde8f1749905f2d735623af9214148afJeff Brown        protected boolean isConnecting(SystemRouteRecord record) {
78711417b1cfde8f1749905f2d735623af9214148afJeff Brown            return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj);
78811417b1cfde8f1749905f2d735623af9214148afJeff Brown        }
789c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown    }
790c21f57ed68b81a77167f1df000b0e272e1598bc0Jeff Brown}
791