MediaRouter.java revision 9a1de308cea2d160778fd977825f10a07b49d738
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media;
18
19import android.bluetooth.BluetoothA2dp;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.os.Handler;
25import android.util.Log;
26
27import java.util.ArrayList;
28import java.util.HashMap;
29
30/**
31 * MediaRouter allows applications to control the routing of media channels
32 * and streams from the current device to external speakers and destination devices.
33 *
34 * <p>Media routes should only be modified on your application's main thread.</p>
35 */
36public class MediaRouter {
37    private static final String TAG = "MediaRouter";
38
39    private Context mAppContext;
40    private AudioManager mAudioManager;
41    private Handler mHandler;
42    private final ArrayList<CallbackInfo> mCallbacks = new ArrayList<CallbackInfo>();
43
44    private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
45    private final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
46
47    private final RouteCategory mSystemCategory;
48    private RouteInfo mDefaultAudio;
49    private RouteInfo mBluetoothA2dpRoute;
50
51    private RouteInfo mSelectedRoute;
52
53    // These get removed when an activity dies
54    final ArrayList<BroadcastReceiver> mRegisteredReceivers = new ArrayList<BroadcastReceiver>();
55
56    /**
57     * Route type flag for live audio.
58     *
59     * <p>A device that supports live audio routing will allow the media audio stream
60     * to be routed to supported destinations. This can include internal speakers or
61     * audio jacks on the device itself, A2DP devices, and more.</p>
62     *
63     * <p>Once initiated this routing is transparent to the application. All audio
64     * played on the media stream will be routed to the selected destination.</p>
65     */
66    public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
67
68    /**
69     * Route type flag for application-specific usage.
70     *
71     * <p>Unlike other media route types, user routes are managed by the application.
72     * The MediaRouter will manage and dispatch events for user routes, but the application
73     * is expected to interpret the meaning of these events and perform the requested
74     * routing tasks.</p>
75     */
76    public static final int ROUTE_TYPE_USER = 0x00800000;
77
78    // Maps application contexts
79    static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
80
81    /**
82     * Return a MediaRouter for the application that the specified Context belongs to.
83     * The behavior or availability of media routing may depend on
84     * various parameters of the context.
85     *
86     * @param context Context for the desired router
87     * @return Router for the supplied Context
88     */
89    public static MediaRouter forApplication(Context context) {
90        final Context appContext = context.getApplicationContext();
91        if (!sRouters.containsKey(appContext)) {
92            final MediaRouter r = new MediaRouter(appContext);
93            sRouters.put(appContext, r);
94            return r;
95        } else {
96            return sRouters.get(appContext);
97        }
98    }
99
100    static String typesToString(int types) {
101        final StringBuilder result = new StringBuilder();
102        if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
103            result.append("ROUTE_TYPE_LIVE_AUDIO ");
104        }
105        if ((types & ROUTE_TYPE_USER) != 0) {
106            result.append("ROUTE_TYPE_USER ");
107        }
108        return result.toString();
109    }
110
111    private MediaRouter(Context context) {
112        mAppContext = context;
113        mHandler = new Handler(mAppContext.getMainLooper());
114
115        mAudioManager = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
116        mSystemCategory = new RouteCategory(mAppContext.getText(
117                com.android.internal.R.string.default_audio_route_category_name),
118                ROUTE_TYPE_LIVE_AUDIO, false);
119
120        registerReceivers();
121
122        createDefaultRoutes();
123    }
124
125    private void registerReceivers() {
126        final BroadcastReceiver volumeReceiver = new VolumeChangedBroadcastReceiver();
127        mAppContext.registerReceiver(volumeReceiver,
128                new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
129        mRegisteredReceivers.add(volumeReceiver);
130
131        final IntentFilter speakerFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
132        speakerFilter.addAction(Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG);
133        speakerFilter.addAction(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG);
134        speakerFilter.addAction(Intent.ACTION_HDMI_AUDIO_PLUG);
135        final BroadcastReceiver plugReceiver = new HeadphoneChangedBroadcastReceiver();
136        mAppContext.registerReceiver(plugReceiver, speakerFilter);
137        mRegisteredReceivers.add(plugReceiver);
138    }
139
140    void unregisterReceivers() {
141        final int count = mRegisteredReceivers.size();
142        for (int i = 0; i < count; i++) {
143            final BroadcastReceiver r = mRegisteredReceivers.get(i);
144            mAppContext.unregisterReceiver(r);
145        }
146        mRegisteredReceivers.clear();
147    }
148
149    private void createDefaultRoutes() {
150        mDefaultAudio = new RouteInfo(mSystemCategory);
151        mDefaultAudio.mName = mAppContext.getText(
152                com.android.internal.R.string.default_audio_route_name);
153        mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
154        addRoute(mDefaultAudio);
155    }
156
157    void onHeadphonesPlugged(boolean headphonesPresent, String headphonesName) {
158        mDefaultAudio.mName = headphonesPresent ? headphonesName : mAppContext.getText(
159                com.android.internal.R.string.default_audio_route_name);
160        dispatchRouteChanged(mDefaultAudio);
161    }
162
163    /**
164     * Set volume for the specified route types.
165     *
166     * @param types Volume will be set for these route types
167     * @param volume Volume to set in the range 0.f (inaudible) to 1.f (full volume).
168     */
169    public void setRouteVolume(int types, float volume) {
170        if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
171            final int index = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
172            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0);
173        }
174        if ((types & ROUTE_TYPE_USER) != 0) {
175            dispatchVolumeChanged(ROUTE_TYPE_USER, volume);
176        }
177    }
178
179    /**
180     * Add a callback to listen to events about specific kinds of media routes.
181     * If the specified callback is already registered, its registration will be updated for any
182     * additional route types specified.
183     *
184     * @param types Types of routes this callback is interested in
185     * @param cb Callback to add
186     */
187    public void addCallback(int types, Callback cb) {
188        final int count = mCallbacks.size();
189        for (int i = 0; i < count; i++) {
190            final CallbackInfo info = mCallbacks.get(i);
191            if (info.cb == cb) {
192                info.type &= types;
193                return;
194            }
195        }
196        mCallbacks.add(new CallbackInfo(cb, types));
197    }
198
199    /**
200     * Remove the specified callback. It will no longer receive events about media routing.
201     *
202     * @param cb Callback to remove
203     */
204    public void removeCallback(Callback cb) {
205        final int count = mCallbacks.size();
206        for (int i = 0; i < count; i++) {
207            if (mCallbacks.get(i).cb == cb) {
208                mCallbacks.remove(i);
209                return;
210            }
211        }
212        Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
213    }
214
215    public void selectRoute(int types, RouteInfo route) {
216        if (mSelectedRoute == route) return;
217
218        if (mSelectedRoute != null) {
219            // TODO filter types properly
220            dispatchRouteUnselected(types & mSelectedRoute.getSupportedTypes(), mSelectedRoute);
221        }
222        mSelectedRoute = route;
223        if (route != null) {
224            // TODO filter types properly
225            dispatchRouteSelected(types & route.getSupportedTypes(), route);
226        }
227    }
228
229    /**
230     * Add an app-specified route for media to the MediaRouter.
231     * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
232     *
233     * @param info Definition of the route to add
234     * @see #createUserRoute()
235     * @see #removeUserRoute(UserRouteInfo)
236     */
237    public void addUserRoute(UserRouteInfo info) {
238        addRoute(info);
239    }
240
241    void addRoute(RouteInfo info) {
242        final RouteCategory cat = info.getCategory();
243        if (!mCategories.contains(cat)) {
244            mCategories.add(cat);
245        }
246        if (info.getCategory().isGroupable() && !(info instanceof RouteGroup)) {
247            // Enforce that any added route in a groupable category must be in a group.
248            final RouteGroup group = new RouteGroup(info.getCategory());
249            group.addRoute(info);
250            info = group;
251        }
252        final boolean onlyRoute = mRoutes.isEmpty();
253        mRoutes.add(info);
254        dispatchRouteAdded(info);
255        if (onlyRoute) {
256            selectRoute(info.getSupportedTypes(), info);
257        }
258    }
259
260    /**
261     * Remove an app-specified route for media from the MediaRouter.
262     *
263     * @param info Definition of the route to remove
264     * @see #addUserRoute(UserRouteInfo)
265     */
266    public void removeUserRoute(UserRouteInfo info) {
267        removeRoute(info);
268    }
269
270    void removeRoute(RouteInfo info) {
271        if (mRoutes.remove(info)) {
272            final RouteCategory removingCat = info.getCategory();
273            final int count = mRoutes.size();
274            boolean found = false;
275            for (int i = 0; i < count; i++) {
276                final RouteCategory cat = mRoutes.get(i).getCategory();
277                if (removingCat == cat) {
278                    found = true;
279                    break;
280                }
281            }
282            if (!found) {
283                mCategories.remove(removingCat);
284            }
285            dispatchRouteRemoved(info);
286        }
287    }
288
289    /**
290     * Return the number of {@link MediaRouter.RouteCategory categories} currently
291     * represented by routes known to this MediaRouter.
292     *
293     * @return the number of unique categories represented by this MediaRouter's known routes
294     */
295    public int getCategoryCount() {
296        return mCategories.size();
297    }
298
299    /**
300     * Return the {@link MediaRouter.RouteCategory category} at the given index.
301     * Valid indices are in the range [0-getCategoryCount).
302     *
303     * @param index which category to return
304     * @return the category at index
305     */
306    public RouteCategory getCategoryAt(int index) {
307        return mCategories.get(index);
308    }
309
310    /**
311     * Return the number of {@link MediaRouter.RouteInfo routes} currently known
312     * to this MediaRouter.
313     *
314     * @return the number of routes tracked by this router
315     */
316    public int getRouteCount() {
317        return mRoutes.size();
318    }
319
320    /**
321     * Return the route at the specified index.
322     *
323     * @param index index of the route to return
324     * @return the route at index
325     */
326    public RouteInfo getRouteAt(int index) {
327        return mRoutes.get(index);
328    }
329
330    /**
331     * Create a new user route that may be modified and registered for use by the application.
332     *
333     * @param category The category the new route will belong to
334     * @return A new UserRouteInfo for use by the application
335     *
336     * @see #addUserRoute(UserRouteInfo)
337     * @see #removeUserRoute(UserRouteInfo)
338     * @see #createRouteCategory(CharSequence)
339     */
340    public UserRouteInfo createUserRoute(RouteCategory category) {
341        return new UserRouteInfo(category);
342    }
343
344    /**
345     * Create a new route category. Each route must belong to a category.
346     *
347     * @param name Name of the new category
348     * @param isGroupable true if routes in this category may be grouped with one another
349     * @return the new RouteCategory
350     */
351    public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) {
352        return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
353    }
354
355    void updateRoute(final RouteInfo info) {
356        dispatchRouteChanged(info);
357    }
358
359    void dispatchRouteSelected(int type, RouteInfo info) {
360        final int count = mCallbacks.size();
361        for (int i = 0; i < count; i++) {
362            final CallbackInfo cbi = mCallbacks.get(i);
363            if ((cbi.type & type) != 0) {
364                cbi.cb.onRouteSelected(type, info);
365            }
366        }
367    }
368
369    void dispatchRouteUnselected(int type, RouteInfo info) {
370        final int count = mCallbacks.size();
371        for (int i = 0; i < count; i++) {
372            final CallbackInfo cbi = mCallbacks.get(i);
373            if ((cbi.type & type) != 0) {
374                cbi.cb.onRouteUnselected(type, info);
375            }
376        }
377    }
378
379    void dispatchRouteChanged(RouteInfo info) {
380        final int count = mCallbacks.size();
381        for (int i = 0; i < count; i++) {
382            final CallbackInfo cbi = mCallbacks.get(i);
383            if ((cbi.type & info.mSupportedTypes) != 0) {
384                cbi.cb.onRouteChanged(info);
385            }
386        }
387    }
388
389    void dispatchRouteAdded(RouteInfo info) {
390        final int count = mCallbacks.size();
391        for (int i = 0; i < count; i++) {
392            final CallbackInfo cbi = mCallbacks.get(i);
393            if ((cbi.type & info.mSupportedTypes) != 0) {
394                cbi.cb.onRouteAdded(info.mSupportedTypes, info);
395            }
396        }
397    }
398
399    void dispatchRouteRemoved(RouteInfo info) {
400        final int count = mCallbacks.size();
401        for (int i = 0; i < count; i++) {
402            final CallbackInfo cbi = mCallbacks.get(i);
403            if ((cbi.type & info.mSupportedTypes) != 0) {
404                cbi.cb.onRouteRemoved(info.mSupportedTypes, info);
405            }
406        }
407    }
408
409    void dispatchVolumeChanged(int type, float volume) {
410        final int count = mCallbacks.size();
411        for (int i = 0; i < count; i++) {
412            final CallbackInfo cbi = mCallbacks.get(i);
413            if ((cbi.type & type) != 0) {
414                cbi.cb.onVolumeChanged(type, volume);
415            }
416        }
417    }
418
419    void onA2dpDeviceConnected() {
420        final RouteInfo info = new RouteInfo(mSystemCategory);
421        info.mName = "Bluetooth"; // TODO Fetch the real name of the device
422        mBluetoothA2dpRoute = info;
423        addRoute(mBluetoothA2dpRoute);
424    }
425
426    void onA2dpDeviceDisconnected() {
427        removeRoute(mBluetoothA2dpRoute);
428        mBluetoothA2dpRoute = null;
429    }
430
431    /**
432     * Information about a media route.
433     */
434    public class RouteInfo {
435        CharSequence mName;
436        private CharSequence mStatus;
437        int mSupportedTypes;
438        RouteGroup mGroup;
439        final RouteCategory mCategory;
440
441        RouteInfo(RouteCategory category) {
442            mCategory = category;
443            category.mRoutes.add(this);
444        }
445
446        /**
447         * @return The user-friendly name of a media route. This is the string presented
448         * to users who may select this as the active route.
449         */
450        public CharSequence getName() {
451            return mName;
452        }
453
454        /**
455         * @return The user-friendly status for a media route. This may include a description
456         * of the currently playing media, if available.
457         */
458        public CharSequence getStatus() {
459            return mStatus;
460        }
461
462        /**
463         * @return A media type flag set describing which types this route supports.
464         */
465        public int getSupportedTypes() {
466            return mSupportedTypes;
467        }
468
469        /**
470         * @return The group that this route belongs to.
471         */
472        public RouteGroup getGroup() {
473            return mGroup;
474        }
475
476        /**
477         * @return the category this route belongs to.
478         */
479        public RouteCategory getCategory() {
480            return mCategory;
481        }
482
483        void setStatusInt(CharSequence status) {
484            if (!status.equals(mStatus)) {
485                mStatus = status;
486                routeUpdated();
487                if (mGroup != null) {
488                    mGroup.memberStatusChanged(this, status);
489                }
490                routeUpdated();
491            }
492        }
493
494        void routeUpdated() {
495            updateRoute(this);
496        }
497
498        @Override
499        public String toString() {
500            String supportedTypes = typesToString(mSupportedTypes);
501            return "RouteInfo{ name=" + mName + ", status=" + mStatus +
502                    " category=" + mCategory +
503                    " supportedTypes=" + supportedTypes + "}";
504        }
505    }
506
507    /**
508     * Information about a route that the application may define and modify.
509     *
510     * @see MediaRouter.RouteInfo
511     */
512    public class UserRouteInfo extends RouteInfo {
513
514        UserRouteInfo(RouteCategory category) {
515            super(category);
516            mSupportedTypes = ROUTE_TYPE_USER;
517        }
518
519        /**
520         * Set the user-visible name of this route.
521         * @param name Name to display to the user to describe this route
522         */
523        public void setName(CharSequence name) {
524            mName = name;
525            routeUpdated();
526        }
527
528        /**
529         * Set the current user-visible status for this route.
530         * @param status Status to display to the user to describe what the endpoint
531         * of this route is currently doing
532         */
533        public void setStatus(CharSequence status) {
534            setStatusInt(status);
535        }
536    }
537
538    /**
539     * Information about a route that consists of multiple other routes in a group.
540     */
541    public class RouteGroup extends RouteInfo {
542        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
543        private boolean mUpdateName;
544
545        RouteGroup(RouteCategory category) {
546            super(category);
547            mGroup = this;
548        }
549
550        public CharSequence getName() {
551            if (mUpdateName) updateName();
552            return super.getName();
553        }
554
555        /**
556         * Add a route to this group. The route must not currently belong to another group.
557         *
558         * @param route route to add to this group
559         */
560        public void addRoute(RouteInfo route) {
561            if (route.getGroup() != null) {
562                throw new IllegalStateException("Route " + route + " is already part of a group.");
563            }
564            if (route.getCategory() != mCategory) {
565                throw new IllegalArgumentException(
566                        "Route cannot be added to a group with a different category. " +
567                            "(Route category=" + route.getCategory() +
568                            " group category=" + mCategory + ")");
569            }
570            mRoutes.add(route);
571            mUpdateName = true;
572            routeUpdated();
573        }
574
575        /**
576         * Add a route to this group before the specified index.
577         *
578         * @param route route to add
579         * @param insertAt insert the new route before this index
580         */
581        public void addRoute(RouteInfo route, int insertAt) {
582            if (route.getGroup() != null) {
583                throw new IllegalStateException("Route " + route + " is already part of a group.");
584            }
585            if (route.getCategory() != mCategory) {
586                throw new IllegalArgumentException(
587                        "Route cannot be added to a group with a different category. " +
588                            "(Route category=" + route.getCategory() +
589                            " group category=" + mCategory + ")");
590            }
591            mRoutes.add(insertAt, route);
592            mUpdateName = true;
593            routeUpdated();
594        }
595
596        /**
597         * Remove a route from this group.
598         *
599         * @param route route to remove
600         */
601        public void removeRoute(RouteInfo route) {
602            if (route.getGroup() != this) {
603                throw new IllegalArgumentException("Route " + route +
604                        " is not a member of this group.");
605            }
606            mRoutes.remove(route);
607            mUpdateName = true;
608            routeUpdated();
609        }
610
611        /**
612         * Remove the route at the specified index from this group.
613         *
614         * @param index index of the route to remove
615         */
616        public void removeRoute(int index) {
617            mRoutes.remove(index);
618            mUpdateName = true;
619            routeUpdated();
620        }
621
622        void memberNameChanged(RouteInfo info, CharSequence name) {
623            mUpdateName = true;
624            routeUpdated();
625        }
626
627        void memberStatusChanged(RouteInfo info, CharSequence status) {
628            setStatusInt(status);
629        }
630
631        void updateName() {
632            final StringBuilder sb = new StringBuilder();
633            final int count = mRoutes.size();
634            for (int i = 0; i < count; i++) {
635                final RouteInfo info = mRoutes.get(i);
636                if (i > 0) sb.append(", ");
637                sb.append(info.mName);
638            }
639            mName = sb.toString();
640            mUpdateName = false;
641        }
642    }
643
644    /**
645     * Definition of a category of routes. All routes belong to a category.
646     */
647    public class RouteCategory {
648        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
649        CharSequence mName;
650        int mTypes;
651        final boolean mGroupable;
652
653        RouteCategory(CharSequence name, int types, boolean groupable) {
654            mName = name;
655            mTypes = types;
656            mGroupable = groupable;
657        }
658
659        /**
660         * @return the name of this route category
661         */
662        public CharSequence getName() {
663            return mName;
664        }
665
666        /**
667         * @return the number of routes in this category
668         */
669        public int getRouteCount() {
670            return mRoutes.size();
671        }
672
673        /**
674         * Return a route from this category
675         *
676         * @param index Index from [0-getRouteCount)
677         * @return the route at the given index
678         */
679        public RouteInfo getRouteAt(int index) {
680            return mRoutes.get(index);
681        }
682
683        /**
684         * @return Flag set describing the route types supported by this category
685         */
686        public int getSupportedTypes() {
687            return mTypes;
688        }
689
690        /**
691         * Return whether or not this category supports grouping.
692         *
693         * <p>If this method returns true, all routes obtained from this category
694         * via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.
695         *
696         * @return true if this category supports
697         */
698        public boolean isGroupable() {
699            return mGroupable;
700        }
701
702        public String toString() {
703            return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
704                    " groupable=" + mGroupable + " routes=" + mRoutes.size() + " }";
705        }
706    }
707
708    static class CallbackInfo {
709        public int type;
710        public Callback cb;
711
712        public CallbackInfo(Callback cb, int type) {
713            this.cb = cb;
714            this.type = type;
715        }
716    }
717
718    /**
719     * Interface for receiving events about media routing changes.
720     * All methods of this interface will be called from the application's main thread.
721     *
722     * <p>A Callback will only receive events relevant to routes that the callback
723     * was registered for.</p>
724     *
725     * @see MediaRouter#addCallback(int, Callback)
726     * @see MediaRouter#removeCallback(Callback)
727     */
728    public interface Callback {
729        /**
730         * Called when the supplied route becomes selected as the active route
731         * for the given route type.
732         *
733         * @param type Type flag set indicating the routes that have been selected
734         * @param info Route that has been selected for the given route types
735         */
736        public void onRouteSelected(int type, RouteInfo info);
737
738        /**
739         * Called when the supplied route becomes unselected as the active route
740         * for the given route type.
741         *
742         * @param type Type flag set indicating the routes that have been unselected
743         * @param info Route that has been unselected for the given route types
744         */
745        public void onRouteUnselected(int type, RouteInfo info);
746
747        /**
748         * Called when the volume is changed for the specified route types.
749         *
750         * @param type Type flags indicating which volume type was changed
751         * @param volume New volume value in the range 0 (inaudible) to 1 (full)
752         */
753        public void onVolumeChanged(int type, float volume);
754
755        /**
756         * Called when a route for the specified type was added.
757         *
758         * @param type Type flags indicating which types the added route supports
759         * @param info Route that has become available for use
760         */
761        public void onRouteAdded(int type, RouteInfo info);
762
763        /**
764         * Called when a route for the specified type was removed.
765         *
766         * @param type Type flags indicating which types the removed route supported
767         * @param info Route that has been removed from availability
768         */
769        public void onRouteRemoved(int type, RouteInfo info);
770
771        /**
772         * Called when an aspect of the indicated route has changed.
773         *
774         * <p>This will not indicate that the types supported by this route have
775         * changed, only that cosmetic info such as name or status have been updated.</p>
776         *
777         * @param info The route that was changed
778         */
779        public void onRouteChanged(RouteInfo info);
780    }
781
782    /**
783     * Stub implementation of the {@link MediaRouter.Callback} interface.
784     * Each interface method is defined as a no-op. Override just the ones
785     * you need.
786     */
787    public static class SimpleCallback implements Callback {
788
789        @Override
790        public void onRouteSelected(int type, RouteInfo info) {
791
792        }
793
794        @Override
795        public void onRouteUnselected(int type, RouteInfo info) {
796
797        }
798
799        @Override
800        public void onVolumeChanged(int type, float volume) {
801
802        }
803
804        @Override
805        public void onRouteAdded(int type, RouteInfo info) {
806
807        }
808
809        @Override
810        public void onRouteRemoved(int type, RouteInfo info) {
811
812        }
813
814        @Override
815        public void onRouteChanged(RouteInfo info) {
816
817        }
818
819    }
820
821    class VolumeChangedBroadcastReceiver extends BroadcastReceiver {
822        @Override
823        public void onReceive(Context context, Intent intent) {
824            final String action = intent.getAction();
825            if (AudioManager.VOLUME_CHANGED_ACTION.equals(action) &&
826                    AudioManager.STREAM_MUSIC == intent.getIntExtra(
827                            AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1)) {
828                final int maxVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
829                final int volExtra = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
830                final float volume = (float) volExtra / maxVol;
831                dispatchVolumeChanged(ROUTE_TYPE_LIVE_AUDIO, volume);
832            }
833        }
834    }
835
836    class BtChangedBroadcastReceiver extends BroadcastReceiver {
837        @Override
838        public void onReceive(Context context, Intent intent) {
839            final String action = intent.getAction();
840            if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
841                final int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1);
842                if (state == BluetoothA2dp.STATE_CONNECTED) {
843                    onA2dpDeviceConnected();
844                } else if (state == BluetoothA2dp.STATE_DISCONNECTING ||
845                        state == BluetoothA2dp.STATE_DISCONNECTED) {
846                    onA2dpDeviceDisconnected();
847                }
848            }
849        }
850    }
851
852    class HeadphoneChangedBroadcastReceiver extends BroadcastReceiver {
853        @Override
854        public void onReceive(Context context, Intent intent) {
855            final String action = intent.getAction();
856            if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
857                final boolean plugged = intent.getIntExtra("state", 0) != 0;
858                final String name = mAppContext.getString(
859                        com.android.internal.R.string.default_audio_route_name_headphones);
860                onHeadphonesPlugged(plugged, name);
861            } else if (Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG.equals(action) ||
862                    Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG.equals(action)) {
863                final boolean plugged = intent.getIntExtra("state", 0) != 0;
864                final String name = mAppContext.getString(
865                        com.android.internal.R.string.default_audio_route_name_dock_speakers);
866                onHeadphonesPlugged(plugged, name);
867            } else if (Intent.ACTION_HDMI_AUDIO_PLUG.equals(action)) {
868                final boolean plugged = intent.getIntExtra("state", 0) != 0;
869                final String name = mAppContext.getString(
870                        com.android.internal.R.string.default_audio_route_name_hdmi);
871                onHeadphonesPlugged(plugged, name);
872            }
873        }
874    }
875}
876