CastControllerImpl.java revision 1e6eb17a22056529601c8e413c2da0541d59d93b
1/*
2 * Copyright (C) 2014 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 com.android.systemui.statusbar.policy;
18
19import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
20
21import android.content.Context;
22import android.media.MediaRouter;
23import android.media.MediaRouter.RouteInfo;
24import android.util.ArrayMap;
25import android.util.ArraySet;
26import android.util.Log;
27
28import java.io.FileDescriptor;
29import java.io.PrintWriter;
30import java.util.ArrayList;
31import java.util.Set;
32import java.util.UUID;
33
34/** Platform implementation of the cast controller. **/
35public class CastControllerImpl implements CastController {
36    private static final String TAG = "CastController";
37    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
38
39    private final Context mContext;
40    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
41    private final MediaRouter mMediaRouter;
42    private final ArrayMap<String, RouteInfo> mRoutes = new ArrayMap<>();
43    private final Object mDiscoveringLock = new Object();
44
45    private boolean mDiscovering;
46
47    public CastControllerImpl(Context context) {
48        mContext = context;
49        mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
50        if (DEBUG) Log.d(TAG, "new CastController()");
51    }
52
53    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
54        pw.println("CastController state:");
55        pw.print("  mDiscovering="); pw.println(mDiscovering);
56        pw.print("  mCallbacks.size="); pw.println(mCallbacks.size());
57        pw.print("  mRoutes.size="); pw.println(mRoutes.size());
58        for (int i = 0; i < mRoutes.size(); i++) {
59            final RouteInfo route = mRoutes.valueAt(i);
60            pw.print("    "); pw.println(routeToString(route));
61        }
62    }
63
64    @Override
65    public void addCallback(Callback callback) {
66        mCallbacks.add(callback);
67        fireOnCastDevicesChanged(callback);
68    }
69
70    @Override
71    public void removeCallback(Callback callback) {
72        mCallbacks.remove(callback);
73    }
74
75    @Override
76    public void setDiscovering(boolean request) {
77        synchronized (mDiscoveringLock) {
78            if (mDiscovering == request) return;
79            mDiscovering = request;
80            if (DEBUG) Log.d(TAG, "setDiscovering: " + request);
81            if (request) {
82                mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
83                        MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
84            } else {
85                mMediaRouter.removeCallback(mMediaCallback);
86            }
87        }
88    }
89
90    @Override
91    public void setCurrentUserId(int currentUserId) {
92        mMediaRouter.rebindAsUser(currentUserId);
93    }
94
95    @Override
96    public Set<CastDevice> getCastDevices() {
97        final ArraySet<CastDevice> devices = new ArraySet<CastDevice>();
98        synchronized(mRoutes) {
99            for (RouteInfo route : mRoutes.values()) {
100                final CastDevice device = new CastDevice();
101                device.id = route.getTag().toString();
102                final CharSequence name = route.getName(mContext);
103                device.name = name != null ? name.toString() : null;
104                final CharSequence description = route.getDescription();
105                device.description = description != null ? description.toString() : null;
106                device.state = route.isConnecting() ? CastDevice.STATE_CONNECTING
107                        : route.isSelected() ? CastDevice.STATE_CONNECTED
108                        : CastDevice.STATE_DISCONNECTED;
109                device.tag = route;
110                devices.add(device);
111            }
112        }
113        return devices;
114    }
115
116    @Override
117    public void startCasting(CastDevice device) {
118        if (device == null || device.tag == null) return;
119        final RouteInfo route = (RouteInfo) device.tag;
120        if (DEBUG) Log.d(TAG, "startCasting: " + routeToString(route));
121        mMediaRouter.selectRoute(ROUTE_TYPE_REMOTE_DISPLAY, route);
122    }
123
124    @Override
125    public void stopCasting() {
126        if (DEBUG) Log.d(TAG, "stopCasting");
127        mMediaRouter.getDefaultRoute().select();
128    }
129
130    private void updateRemoteDisplays() {
131        synchronized(mRoutes) {
132            mRoutes.clear();
133            final int n = mMediaRouter.getRouteCount();
134            for (int i = 0; i < n; i++) {
135                final RouteInfo route = mMediaRouter.getRouteAt(i);
136                if (!route.isEnabled()) continue;
137                if (!route.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) continue;
138                ensureTagExists(route);
139                mRoutes.put(route.getTag().toString(), route);
140            }
141            final RouteInfo selected = mMediaRouter.getSelectedRoute(ROUTE_TYPE_REMOTE_DISPLAY);
142            if (selected != null && !selected.isDefault()) {
143                ensureTagExists(selected);
144                mRoutes.put(selected.getTag().toString(), selected);
145            }
146        }
147        fireOnCastDevicesChanged();
148    }
149
150    private void ensureTagExists(RouteInfo route) {
151        if (route.getTag() == null) {
152            route.setTag(UUID.randomUUID().toString());
153        }
154    }
155
156    private void fireOnCastDevicesChanged() {
157        for (Callback callback : mCallbacks) {
158            fireOnCastDevicesChanged(callback);
159        }
160    }
161
162    private void fireOnCastDevicesChanged(Callback callback) {
163        callback.onCastDevicesChanged();
164    }
165
166    private static String routeToString(RouteInfo route) {
167        if (route == null) return null;
168        final StringBuilder sb = new StringBuilder().append(route.getName()).append('/')
169                .append(route.getDescription()).append('@').append(route.getDeviceAddress())
170                .append(",status=").append(route.getStatus());
171        if (route.isDefault()) sb.append(",default");
172        if (route.isEnabled()) sb.append(",enabled");
173        if (route.isConnecting()) sb.append(",connecting");
174        if (route.isSelected()) sb.append(",selected");
175        return sb.append(",id=").append(route.getTag()).toString();
176    }
177
178    private final MediaRouter.SimpleCallback mMediaCallback = new MediaRouter.SimpleCallback() {
179        @Override
180        public void onRouteAdded(MediaRouter router, RouteInfo route) {
181            if (DEBUG) Log.d(TAG, "onRouteAdded: " + routeToString(route));
182            updateRemoteDisplays();
183        }
184        @Override
185        public void onRouteChanged(MediaRouter router, RouteInfo route) {
186            if (DEBUG) Log.d(TAG, "onRouteChanged: " + routeToString(route));
187            updateRemoteDisplays();
188        }
189        @Override
190        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
191            if (DEBUG) Log.d(TAG, "onRouteRemoved: " + routeToString(route));
192            updateRemoteDisplays();
193        }
194        @Override
195        public void onRouteSelected(MediaRouter router, int type, RouteInfo route) {
196            if (DEBUG) Log.d(TAG, "onRouteSelected(" + type + "): " + routeToString(route));
197            updateRemoteDisplays();
198        }
199        @Override
200        public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
201            if (DEBUG) Log.d(TAG, "onRouteUnselected(" + type + "): " + routeToString(route));
202            updateRemoteDisplays();
203        }
204    };
205}
206