blob: 3337b480d6a8f3e0c1e9e57065db0d10848dd26c [file] [log] [blame]
Jeff Brown69b07162013-11-07 00:30:16 -08001/*
2 * Copyright (C) 2013 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.server.media;
18
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090019import android.annotation.NonNull;
Jeff Brown69b07162013-11-07 00:30:16 -080020import android.app.ActivityManager;
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +090021import android.bluetooth.BluetoothA2dp;
22import android.bluetooth.BluetoothDevice;
Jeff Brown69b07162013-11-07 00:30:16 -080023import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.pm.PackageManager;
Sungsoo Lim875e6972017-11-03 02:22:35 +000028import android.media.AudioPlaybackConfiguration;
Sungsoob3658562017-05-22 17:10:44 +090029import android.media.AudioRoutesInfo;
Jeff Brown69b07162013-11-07 00:30:16 -080030import android.media.AudioSystem;
Sungsoob3658562017-05-22 17:10:44 +090031import android.media.IAudioRoutesObserver;
32import android.media.IAudioService;
Hyundo Moon56337492020-02-16 16:19:54 +090033import android.media.IMediaRouter2;
Kyunglyul Hyun3aedf022019-04-15 16:38:19 +090034import android.media.IMediaRouter2Manager;
Jeff Brown69b07162013-11-07 00:30:16 -080035import android.media.IMediaRouterClient;
36import android.media.IMediaRouterService;
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +090037import android.media.MediaRoute2Info;
Jeff Brown69b07162013-11-07 00:30:16 -080038import android.media.MediaRouter;
39import android.media.MediaRouterClientState;
40import android.media.RemoteDisplayState;
41import android.media.RemoteDisplayState.RemoteDisplayInfo;
Kyunglyul Hyun616096e2020-01-09 16:15:03 +090042import android.media.RouteDiscoveryPreference;
Hyundo Moonf829e6f2020-01-11 19:31:35 +090043import android.media.RoutingSessionInfo;
Jeff Brown69b07162013-11-07 00:30:16 -080044import android.os.Binder;
Hyundo Moon84e027d2020-01-16 17:39:05 +090045import android.os.Bundle;
Jeff Brown69b07162013-11-07 00:30:16 -080046import android.os.Handler;
47import android.os.IBinder;
48import android.os.Looper;
49import android.os.Message;
50import android.os.RemoteException;
Sungsoob3658562017-05-22 17:10:44 +090051import android.os.ServiceManager;
Jeff Brown69b07162013-11-07 00:30:16 -080052import android.os.SystemClock;
Sungsoob3658562017-05-22 17:10:44 +090053import android.os.UserHandle;
Jeff Brown69b07162013-11-07 00:30:16 -080054import android.text.TextUtils;
55import android.util.ArrayMap;
Sungsoob3658562017-05-22 17:10:44 +090056import android.util.IntArray;
Jeff Brown69b07162013-11-07 00:30:16 -080057import android.util.Log;
58import android.util.Slog;
59import android.util.SparseArray;
60import android.util.TimeUtils;
61
Kyunglyul Hyun1c8188f2019-02-01 10:50:11 +090062import com.android.internal.util.DumpUtils;
63import com.android.server.Watchdog;
64
Jeff Brown69b07162013-11-07 00:30:16 -080065import java.io.FileDescriptor;
66import java.io.PrintWriter;
67import java.util.ArrayList;
68import java.util.Collections;
69import java.util.List;
Kenny Roote6585b32013-12-13 12:00:26 -080070import java.util.Objects;
Jeff Brown69b07162013-11-07 00:30:16 -080071
72/**
73 * Provides a mechanism for discovering media routes and manages media playback
74 * behalf of applications.
75 * <p>
76 * Currently supports discovering remote displays via remote display provider
77 * services that have been registered by applications.
78 * </p>
79 */
80public final class MediaRouterService extends IMediaRouterService.Stub
81 implements Watchdog.Monitor {
82 private static final String TAG = "MediaRouterService";
83 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
84
85 /**
86 * Timeout in milliseconds for a selected route to transition from a
87 * disconnected state to a connecting state. If we don't observe any
88 * progress within this interval, then we will give up and unselect the route.
89 */
90 static final long CONNECTING_TIMEOUT = 5000;
91
92 /**
93 * Timeout in milliseconds for a selected route to transition from a
94 * connecting state to a connected state. If we don't observe any
95 * progress within this interval, then we will give up and unselect the route.
96 */
97 static final long CONNECTED_TIMEOUT = 60000;
98
99 private final Context mContext;
100
101 // State guarded by mLock.
102 private final Object mLock = new Object();
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900103 private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
104 private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>();
Jeff Brown69b07162013-11-07 00:30:16 -0800105 private int mCurrentUserId = -1;
Sungsoob3658562017-05-22 17:10:44 +0900106 private final IAudioService mAudioService;
Sungsoo Lim875e6972017-11-03 02:22:35 +0000107 private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
108 private final Handler mHandler = new Handler();
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900109 private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
110 private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
Jeff Brown69b07162013-11-07 00:30:16 -0800111
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900112 private final BroadcastReceiver mReceiver = new MediaRouterServiceBroadcastReceiver();
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900113 BluetoothDevice mActiveBluetoothDevice;
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900114 int mAudioRouteMainType = AudioRoutesInfo.MAIN_SPEAKER;
115 boolean mGlobalBluetoothA2dpOn = false;
116
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900117 //TODO: remove this when it's finished
118 private final MediaRouter2ServiceImpl mService2;
119
Jeff Brown69b07162013-11-07 00:30:16 -0800120 public MediaRouterService(Context context) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900121 mService2 = new MediaRouter2ServiceImpl(context);
122
Jeff Brown69b07162013-11-07 00:30:16 -0800123 mContext = context;
124 Watchdog.getInstance().addMonitor(this);
Sungsoob3658562017-05-22 17:10:44 +0900125
126 mAudioService = IAudioService.Stub.asInterface(
127 ServiceManager.getService(Context.AUDIO_SERVICE));
Sungsoo Limd4d11872019-04-04 02:19:52 +0900128 mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(context);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000129 mAudioPlayerStateMonitor.registerListener(
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900130 new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000131 static final long WAIT_MS = 500;
132 final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
133 @Override
134 public void run() {
135 restoreBluetoothA2dp();
136 }
137 };
138
Sungsoob3658562017-05-22 17:10:44 +0900139 @Override
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900140 public void onAudioPlayerActiveStateChanged(
141 @NonNull AudioPlaybackConfiguration config, boolean isRemoved) {
142 final boolean active = !isRemoved && config.isActive();
143 final int pii = config.getPlayerInterfaceId();
144 final int uid = config.getClientUid();
145
146 final int idx = mActivePlayerMinPriorityQueue.indexOf(pii);
147 // Keep the latest active player and its uid at the end of the queue.
148 if (idx >= 0) {
149 mActivePlayerMinPriorityQueue.remove(idx);
150 mActivePlayerUidMinPriorityQueue.remove(idx);
151 }
152
Sungsoo Lim875e6972017-11-03 02:22:35 +0000153 int restoreUid = -1;
Sungsoob3658562017-05-22 17:10:44 +0900154 if (active) {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900155 mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId());
156 mActivePlayerUidMinPriorityQueue.add(uid);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000157 restoreUid = uid;
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900158 } else if (mActivePlayerUidMinPriorityQueue.size() > 0) {
159 restoreUid = mActivePlayerUidMinPriorityQueue.get(
160 mActivePlayerUidMinPriorityQueue.size() - 1);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000161 }
162
163 mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
164 if (restoreUid >= 0) {
165 restoreRoute(restoreUid);
166 if (DEBUG) {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900167 Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
168 + ", active=" + active + ", restoreUid=" + restoreUid);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000169 }
170 } else {
171 mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
172 if (DEBUG) {
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900173 Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900174 + ", active=" + active + ", delaying");
Sungsoob3658562017-05-22 17:10:44 +0900175 }
176 }
177 }
Sungsoo Lim875e6972017-11-03 02:22:35 +0000178 }, mHandler);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000179
Sungsoob3658562017-05-22 17:10:44 +0900180 AudioRoutesInfo audioRoutes = null;
181 try {
182 audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
183 @Override
184 public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
Sungsoo Lim76512a32017-08-24 10:25:06 +0900185 synchronized (mLock) {
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900186 if (newRoutes.mainType != mAudioRouteMainType) {
Sungsoo Lim76512a32017-08-24 10:25:06 +0900187 if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
188 | AudioRoutesInfo.MAIN_HEADPHONES
189 | AudioRoutesInfo.MAIN_USB)) == 0) {
190 // headset was plugged out.
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900191 mGlobalBluetoothA2dpOn = (newRoutes.bluetoothName != null
192 || mActiveBluetoothDevice != null);
Sungsoo Lim76512a32017-08-24 10:25:06 +0900193 } else {
194 // headset was plugged in.
195 mGlobalBluetoothA2dpOn = false;
196 }
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900197 mAudioRouteMainType = newRoutes.mainType;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900198 }
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900199 // The new audio routes info could be delivered with several seconds delay.
200 // In order to avoid such delay, Bluetooth device info will be updated
201 // via MediaRouterServiceBroadcastReceiver.
Sungsoo Lim76512a32017-08-24 10:25:06 +0900202 }
Sungsoob3658562017-05-22 17:10:44 +0900203 }
204 });
205 } catch (RemoteException e) {
206 Slog.w(TAG, "RemoteException in the audio service.");
207 }
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900208
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900209 IntentFilter intentFilter = new IntentFilter(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900210 context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
Jeff Brown69b07162013-11-07 00:30:16 -0800211 }
212
213 public void systemRunning() {
214 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
215 mContext.registerReceiver(new BroadcastReceiver() {
216 @Override
217 public void onReceive(Context context, Intent intent) {
218 if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
219 switchUser();
220 }
221 }
222 }, filter);
223
224 switchUser();
225 }
226
227 @Override
228 public void monitor() {
229 synchronized (mLock) { /* check for deadlock */ }
230 }
231
232 // Binder call
233 @Override
234 public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
235 if (client == null) {
236 throw new IllegalArgumentException("client must not be null");
237 }
238
239 final int uid = Binder.getCallingUid();
240 if (!validatePackageName(uid, packageName)) {
241 throw new SecurityException("packageName must match the calling uid");
242 }
243
244 final int pid = Binder.getCallingPid();
245 final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
246 false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName);
Jeff Brownaf574182013-11-14 18:16:08 -0800247 final boolean trusted = mContext.checkCallingOrSelfPermission(
248 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) ==
249 PackageManager.PERMISSION_GRANTED;
Jeff Brown69b07162013-11-07 00:30:16 -0800250 final long token = Binder.clearCallingIdentity();
251 try {
252 synchronized (mLock) {
Sungsoob3658562017-05-22 17:10:44 +0900253 registerClientLocked(client, uid, pid, packageName, resolvedUserId, trusted);
Jeff Brown69b07162013-11-07 00:30:16 -0800254 }
255 } finally {
256 Binder.restoreCallingIdentity(token);
257 }
258 }
259
260 // Binder call
261 @Override
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900262 public void registerClientGroupId(IMediaRouterClient client, String groupId) {
263 if (client == null) {
264 throw new NullPointerException("client must not be null");
265 }
266 if (mContext.checkCallingOrSelfPermission(
267 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
268 != PackageManager.PERMISSION_GRANTED) {
269 Log.w(TAG, "Ignoring client group request because "
270 + "the client doesn't have the CONFIGURE_WIFI_DISPLAY permission.");
271 return;
272 }
273 final long token = Binder.clearCallingIdentity();
274 try {
275 synchronized (mLock) {
276 registerClientGroupIdLocked(client, groupId);
277 }
278 } finally {
279 Binder.restoreCallingIdentity(token);
280 }
281 }
282
283 // Binder call
284 @Override
Jeff Brown69b07162013-11-07 00:30:16 -0800285 public void unregisterClient(IMediaRouterClient client) {
286 if (client == null) {
287 throw new IllegalArgumentException("client must not be null");
288 }
289
290 final long token = Binder.clearCallingIdentity();
291 try {
292 synchronized (mLock) {
293 unregisterClientLocked(client, false);
294 }
295 } finally {
296 Binder.restoreCallingIdentity(token);
297 }
298 }
299
300 // Binder call
301 @Override
302 public MediaRouterClientState getState(IMediaRouterClient client) {
303 if (client == null) {
304 throw new IllegalArgumentException("client must not be null");
305 }
306
307 final long token = Binder.clearCallingIdentity();
308 try {
309 synchronized (mLock) {
310 return getStateLocked(client);
311 }
312 } finally {
313 Binder.restoreCallingIdentity(token);
314 }
315 }
316
317 // Binder call
318 @Override
Sungsoob3658562017-05-22 17:10:44 +0900319 public boolean isPlaybackActive(IMediaRouterClient client) {
320 if (client == null) {
321 throw new IllegalArgumentException("client must not be null");
322 }
323
324 final long token = Binder.clearCallingIdentity();
325 try {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000326 ClientRecord clientRecord;
Sungsoob3658562017-05-22 17:10:44 +0900327 synchronized (mLock) {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000328 clientRecord = mAllClientRecords.get(client.asBinder());
Sungsoob3658562017-05-22 17:10:44 +0900329 }
Sungsoo Lim875e6972017-11-03 02:22:35 +0000330 if (clientRecord != null) {
331 return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
332 }
333 return false;
Sungsoob3658562017-05-22 17:10:44 +0900334 } finally {
335 Binder.restoreCallingIdentity(token);
336 }
337 }
338
339 // Binder call
340 @Override
Jeff Brown69b07162013-11-07 00:30:16 -0800341 public void setDiscoveryRequest(IMediaRouterClient client,
342 int routeTypes, boolean activeScan) {
343 if (client == null) {
344 throw new IllegalArgumentException("client must not be null");
345 }
346
347 final long token = Binder.clearCallingIdentity();
348 try {
349 synchronized (mLock) {
350 setDiscoveryRequestLocked(client, routeTypes, activeScan);
351 }
352 } finally {
353 Binder.restoreCallingIdentity(token);
354 }
355 }
356
357 // Binder call
358 // A null routeId means that the client wants to unselect its current route.
359 // The explicit flag indicates whether the change was explicitly requested by the
360 // user or the application which may cause changes to propagate out to the rest
Sungsood103e562017-03-09 15:35:07 +0900361 // of the system. Should be false when the change is in response to a new
Jeff Brown69b07162013-11-07 00:30:16 -0800362 // selected route or a default selection.
363 @Override
364 public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
365 if (client == null) {
366 throw new IllegalArgumentException("client must not be null");
367 }
368
369 final long token = Binder.clearCallingIdentity();
370 try {
371 synchronized (mLock) {
372 setSelectedRouteLocked(client, routeId, explicit);
373 }
374 } finally {
375 Binder.restoreCallingIdentity(token);
376 }
377 }
378
379 // Binder call
380 @Override
381 public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
382 if (client == null) {
383 throw new IllegalArgumentException("client must not be null");
384 }
385 if (routeId == null) {
386 throw new IllegalArgumentException("routeId must not be null");
387 }
388
389 final long token = Binder.clearCallingIdentity();
390 try {
391 synchronized (mLock) {
392 requestSetVolumeLocked(client, routeId, volume);
393 }
394 } finally {
395 Binder.restoreCallingIdentity(token);
396 }
397 }
398
399 // Binder call
400 @Override
401 public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
402 if (client == null) {
403 throw new IllegalArgumentException("client must not be null");
404 }
405 if (routeId == null) {
406 throw new IllegalArgumentException("routeId must not be null");
407 }
408
409 final long token = Binder.clearCallingIdentity();
410 try {
411 synchronized (mLock) {
412 requestUpdateVolumeLocked(client, routeId, direction);
413 }
414 } finally {
415 Binder.restoreCallingIdentity(token);
416 }
417 }
418
419 // Binder call
420 @Override
421 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600422 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Jeff Brown69b07162013-11-07 00:30:16 -0800423
424 pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
425 pw.println();
426 pw.println("Global state");
427 pw.println(" mCurrentUserId=" + mCurrentUserId);
428
429 synchronized (mLock) {
430 final int count = mUserRecords.size();
431 for (int i = 0; i < count; i++) {
432 UserRecord userRecord = mUserRecords.valueAt(i);
433 pw.println();
434 userRecord.dump(pw, "");
435 }
436 }
437 }
438
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000439 // Binder call
440 @Override
Hyundo Moon5736a612019-11-19 15:08:32 +0900441 public List<MediaRoute2Info> getSystemRoutes() {
442 return mService2.getSystemRoutes();
443 }
444
445 // Binder call
446 @Override
Hyundo Moon67c41fd2020-01-17 14:22:42 +0900447 public RoutingSessionInfo getSystemSessionInfo() {
448 return mService2.getSystemSessionInfo();
449 }
450
451 // Binder call
452 @Override
Hyundo Moon56337492020-02-16 16:19:54 +0900453 public void registerRouter2(IMediaRouter2 router, String packageName) {
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900454 final int uid = Binder.getCallingUid();
455 if (!validatePackageName(uid, packageName)) {
456 throw new SecurityException("packageName must match the calling uid");
457 }
Hyundo Moon56337492020-02-16 16:19:54 +0900458 mService2.registerRouter2(router, packageName);
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900459 }
460
461 // Binder call
462 @Override
Hyundo Moon56337492020-02-16 16:19:54 +0900463 public void unregisterRouter2(IMediaRouter2 router) {
464 mService2.unregisterRouter2(router);
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900465 }
466
467 // Binder call
468 @Override
Hyundo Moon56337492020-02-16 16:19:54 +0900469 public void setDiscoveryRequestWithRouter2(IMediaRouter2 router,
470 RouteDiscoveryPreference request) {
471 mService2.setDiscoveryRequestWithRouter2(router, request);
472 }
473
474 // Binder call
475 @Override
476 public void setRouteVolumeWithRouter2(IMediaRouter2 router,
477 MediaRoute2Info route, int volume) {
478 mService2.setRouteVolumeWithRouter2(router, route, volume);
479 }
480
481 // Binder call
482 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900483 public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
484 MediaRoute2Info route, Bundle sessionHints) {
485 mService2.requestCreateSessionWithRouter2(router, requestId, route, sessionHints);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900486 }
487
488 // Binder call
489 @Override
Kyunglyul Hyunf0eb51b2020-04-14 19:56:16 +0900490 public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
491 long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
492 mService2.notifySessionHintsForCreatingSession(router,
493 uniqueRequestId, route, sessionHints);
494 }
495
496 // Binder call
497 @Override
Hyundo Moon56337492020-02-16 16:19:54 +0900498 public void selectRouteWithRouter2(IMediaRouter2 router, String sessionId,
Hyundo Moon0a926572019-12-28 15:01:21 +0900499 MediaRoute2Info route) {
Hyundo Moon56337492020-02-16 16:19:54 +0900500 mService2.selectRouteWithRouter2(router, sessionId, route);
Hyundo Moon0a926572019-12-28 15:01:21 +0900501 }
502
503 // Binder call
504 @Override
Hyundo Moon56337492020-02-16 16:19:54 +0900505 public void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId,
506 MediaRoute2Info route) {
507 mService2.deselectRouteWithRouter2(router, sessionId, route);
508 }
509
510 // Binder call
511 @Override
512 public void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
513 MediaRoute2Info route) {
514 mService2.transferToRouteWithRouter2(router, sessionId, route);
515 }
516
517 // Binder call
518 @Override
519 public void setSessionVolumeWithRouter2(IMediaRouter2 router, String sessionId, int volume) {
520 mService2.setSessionVolumeWithRouter2(router, sessionId, volume);
521 }
522
523 // Binder call
524 @Override
525 public void releaseSessionWithRouter2(IMediaRouter2 router, String sessionId) {
526 mService2.releaseSessionWithRouter2(router, sessionId);
527 }
528
529 // Binder call
530 @Override
531 public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
532 return mService2.getActiveSessions(manager);
Hyundo Moonca9db782020-01-01 18:31:15 +0900533 }
534
535 // Binder call
536 @Override
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900537 public void registerManager(IMediaRouter2Manager manager, String packageName) {
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000538 final int uid = Binder.getCallingUid();
539 if (!validatePackageName(uid, packageName)) {
540 throw new SecurityException("packageName must match the calling uid");
541 }
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900542 mService2.registerManager(manager, packageName);
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000543 }
544
545 // Binder call
546 @Override
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900547 public void unregisterManager(IMediaRouter2Manager manager) {
548 mService2.unregisterManager(manager);
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000549 }
550
551 // Binder call
552 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900553 public void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
554 MediaRoute2Info route, int volume) {
555 mService2.setRouteVolumeWithManager(manager, requestId, route, volume);
Hyundo Moon56337492020-02-16 16:19:54 +0900556 }
557
558 // Binder call
559 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900560 public void requestCreateSessionWithManager(IMediaRouter2Manager manager,
561 int requestId, String packageName, MediaRoute2Info route) {
562 mService2.requestCreateSessionWithManager(manager, requestId, packageName, route);
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900563 }
564
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900565 // Binder call
566 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900567 public void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
568 String sessionId, MediaRoute2Info route) {
569 mService2.selectRouteWithManager(manager, requestId, sessionId, route);
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900570 }
571
572 // Binder call
573 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900574 public void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
575 String sessionId, MediaRoute2Info route) {
576 mService2.deselectRouteWithManager(manager, requestId, sessionId, route);
Kyunglyul Hyun5161b372020-02-05 18:45:35 +0900577 }
578
579 // Binder call
580 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900581 public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
582 String sessionId, MediaRoute2Info route) {
583 mService2.transferToRouteWithManager(manager, requestId, sessionId, route);
Kyunglyul Hyun5161b372020-02-05 18:45:35 +0900584 }
585
586 // Binder call
587 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900588 public void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId,
589 String sessionId, int volume) {
590 mService2.setSessionVolumeWithManager(manager, requestId, sessionId, volume);
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900591 }
592
593 // Binder call
594 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900595 public void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId,
596 String sessionId) {
597 mService2.releaseSessionWithManager(manager, requestId, sessionId);
Kyunglyul Hyunc6583832020-01-17 11:11:46 +0000598 }
599
Sungsoob3658562017-05-22 17:10:44 +0900600 void restoreBluetoothA2dp() {
601 try {
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900602 boolean a2dpOn;
603 BluetoothDevice btDevice;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900604 synchronized (mLock) {
605 a2dpOn = mGlobalBluetoothA2dpOn;
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900606 btDevice = mActiveBluetoothDevice;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900607 }
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900608 // We don't need to change a2dp status when bluetooth is not connected.
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900609 if (btDevice != null) {
Sungsoo Limc3b52952018-06-11 12:55:25 +0900610 if (DEBUG) {
611 Slog.d(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
612 }
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900613 mAudioService.setBluetoothA2dpOn(a2dpOn);
614 }
Sungsoob3658562017-05-22 17:10:44 +0900615 } catch (RemoteException e) {
616 Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
617 }
618 }
619
620 void restoreRoute(int uid) {
621 ClientRecord clientRecord = null;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900622 synchronized (mLock) {
Sungsoo Lim277ea282020-01-14 16:35:05 +0900623 UserRecord userRecord = mUserRecords.get(
624 UserHandle.getUserHandleForUid(uid).getIdentifier());
Sungsoo Lim76512a32017-08-24 10:25:06 +0900625 if (userRecord != null && userRecord.mClientRecords != null) {
626 for (ClientRecord cr : userRecord.mClientRecords) {
627 if (validatePackageName(uid, cr.mPackageName)) {
628 clientRecord = cr;
629 break;
630 }
Sungsoob3658562017-05-22 17:10:44 +0900631 }
632 }
633 }
634 if (clientRecord != null) {
635 try {
636 clientRecord.mClient.onRestoreRoute();
637 } catch (RemoteException e) {
638 Slog.w(TAG, "Failed to call onRestoreRoute. Client probably died.");
639 }
640 } else {
641 restoreBluetoothA2dp();
642 }
643 }
644
Jeff Brown69b07162013-11-07 00:30:16 -0800645 void switchUser() {
646 synchronized (mLock) {
647 int userId = ActivityManager.getCurrentUser();
648 if (mCurrentUserId != userId) {
649 final int oldUserId = mCurrentUserId;
650 mCurrentUserId = userId; // do this first
651
652 UserRecord oldUser = mUserRecords.get(oldUserId);
653 if (oldUser != null) {
654 oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
655 disposeUserIfNeededLocked(oldUser); // since no longer current user
656 }
657
658 UserRecord newUser = mUserRecords.get(userId);
659 if (newUser != null) {
660 newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
661 }
662 }
663 }
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900664 mService2.switchUser();
Jeff Brown69b07162013-11-07 00:30:16 -0800665 }
666
667 void clientDied(ClientRecord clientRecord) {
668 synchronized (mLock) {
669 unregisterClientLocked(clientRecord.mClient, true);
670 }
671 }
672
673 private void registerClientLocked(IMediaRouterClient client,
Sungsoob3658562017-05-22 17:10:44 +0900674 int uid, int pid, String packageName, int userId, boolean trusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800675 final IBinder binder = client.asBinder();
676 ClientRecord clientRecord = mAllClientRecords.get(binder);
677 if (clientRecord == null) {
678 boolean newUser = false;
679 UserRecord userRecord = mUserRecords.get(userId);
680 if (userRecord == null) {
681 userRecord = new UserRecord(userId);
682 newUser = true;
683 }
Sungsoob3658562017-05-22 17:10:44 +0900684 clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted);
Jeff Brown69b07162013-11-07 00:30:16 -0800685 try {
686 binder.linkToDeath(clientRecord, 0);
687 } catch (RemoteException ex) {
688 throw new RuntimeException("Media router client died prematurely.", ex);
689 }
690
691 if (newUser) {
692 mUserRecords.put(userId, userRecord);
693 initializeUserLocked(userRecord);
694 }
695
696 userRecord.mClientRecords.add(clientRecord);
697 mAllClientRecords.put(binder, clientRecord);
698 initializeClientLocked(clientRecord);
699 }
700 }
701
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900702 private void registerClientGroupIdLocked(IMediaRouterClient client, String groupId) {
703 final IBinder binder = client.asBinder();
704 ClientRecord clientRecord = mAllClientRecords.get(binder);
705 if (clientRecord == null) {
706 Log.w(TAG, "Ignoring group id register request of a unregistered client.");
707 return;
708 }
709 if (TextUtils.equals(clientRecord.mGroupId, groupId)) {
710 return;
711 }
712 UserRecord userRecord = clientRecord.mUserRecord;
713 if (clientRecord.mGroupId != null) {
714 userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord);
715 }
716 clientRecord.mGroupId = groupId;
717 if (groupId != null) {
718 userRecord.addToGroup(groupId, clientRecord);
719 userRecord.mHandler.obtainMessage(UserHandler.MSG_UPDATE_SELECTED_ROUTE, groupId)
720 .sendToTarget();
721 }
722 }
723
Jeff Brown69b07162013-11-07 00:30:16 -0800724 private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
725 ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
726 if (clientRecord != null) {
727 UserRecord userRecord = clientRecord.mUserRecord;
728 userRecord.mClientRecords.remove(clientRecord);
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900729 if (clientRecord.mGroupId != null) {
730 userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord);
731 clientRecord.mGroupId = null;
732 }
Jeff Brown69b07162013-11-07 00:30:16 -0800733 disposeClientLocked(clientRecord, died);
734 disposeUserIfNeededLocked(userRecord); // since client removed from user
735 }
736 }
737
738 private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
739 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
740 if (clientRecord != null) {
Jeff Brownaf574182013-11-14 18:16:08 -0800741 return clientRecord.getState();
Jeff Brown69b07162013-11-07 00:30:16 -0800742 }
743 return null;
744 }
745
746 private void setDiscoveryRequestLocked(IMediaRouterClient client,
747 int routeTypes, boolean activeScan) {
748 final IBinder binder = client.asBinder();
749 ClientRecord clientRecord = mAllClientRecords.get(binder);
750 if (clientRecord != null) {
Jeff Brownaf574182013-11-14 18:16:08 -0800751 // Only let the system discover remote display routes for now.
752 if (!clientRecord.mTrusted) {
753 routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
754 }
755
Jeff Brown69b07162013-11-07 00:30:16 -0800756 if (clientRecord.mRouteTypes != routeTypes
757 || clientRecord.mActiveScan != activeScan) {
758 if (DEBUG) {
759 Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
760 + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
761 }
762 clientRecord.mRouteTypes = routeTypes;
763 clientRecord.mActiveScan = activeScan;
764 clientRecord.mUserRecord.mHandler.sendEmptyMessage(
765 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
766 }
767 }
768 }
769
770 private void setSelectedRouteLocked(IMediaRouterClient client,
771 String routeId, boolean explicit) {
772 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
773 if (clientRecord != null) {
774 final String oldRouteId = clientRecord.mSelectedRouteId;
Kenny Roote6585b32013-12-13 12:00:26 -0800775 if (!Objects.equals(routeId, oldRouteId)) {
Jeff Brown69b07162013-11-07 00:30:16 -0800776 if (DEBUG) {
777 Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
778 + ", oldRouteId=" + oldRouteId
779 + ", explicit=" + explicit);
780 }
781
782 clientRecord.mSelectedRouteId = routeId;
Sungsood103e562017-03-09 15:35:07 +0900783 // Only let the system connect to new global routes for now.
784 // A similar check exists in the display manager for wifi display.
785 if (explicit && clientRecord.mTrusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800786 if (oldRouteId != null) {
787 clientRecord.mUserRecord.mHandler.obtainMessage(
788 UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
789 }
Sungsood103e562017-03-09 15:35:07 +0900790 if (routeId != null) {
Jeff Brown69b07162013-11-07 00:30:16 -0800791 clientRecord.mUserRecord.mHandler.obtainMessage(
792 UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
793 }
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900794 if (clientRecord.mGroupId != null) {
795 ClientGroup group =
796 clientRecord.mUserRecord.mClientGroupMap.get(clientRecord.mGroupId);
797 if (group != null) {
798 group.mSelectedRouteId = routeId;
799 clientRecord.mUserRecord.mHandler.obtainMessage(
800 UserHandler.MSG_UPDATE_SELECTED_ROUTE, clientRecord.mGroupId)
801 .sendToTarget();
802 }
803 }
Jeff Brown69b07162013-11-07 00:30:16 -0800804 }
805 }
806 }
807 }
808
809 private void requestSetVolumeLocked(IMediaRouterClient client,
810 String routeId, int volume) {
811 final IBinder binder = client.asBinder();
812 ClientRecord clientRecord = mAllClientRecords.get(binder);
813 if (clientRecord != null) {
814 clientRecord.mUserRecord.mHandler.obtainMessage(
815 UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
816 }
817 }
818
819 private void requestUpdateVolumeLocked(IMediaRouterClient client,
820 String routeId, int direction) {
821 final IBinder binder = client.asBinder();
822 ClientRecord clientRecord = mAllClientRecords.get(binder);
823 if (clientRecord != null) {
824 clientRecord.mUserRecord.mHandler.obtainMessage(
825 UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
826 }
827 }
828
829 private void initializeUserLocked(UserRecord userRecord) {
830 if (DEBUG) {
831 Slog.d(TAG, userRecord + ": Initialized");
832 }
833 if (userRecord.mUserId == mCurrentUserId) {
834 userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
835 }
836 }
837
838 private void disposeUserIfNeededLocked(UserRecord userRecord) {
839 // If there are no records left and the user is no longer current then go ahead
840 // and purge the user record and all of its associated state. If the user is current
841 // then leave it alone since we might be connected to a route or want to query
842 // the same route information again soon.
843 if (userRecord.mUserId != mCurrentUserId
844 && userRecord.mClientRecords.isEmpty()) {
845 if (DEBUG) {
846 Slog.d(TAG, userRecord + ": Disposed");
847 }
848 mUserRecords.remove(userRecord.mUserId);
849 // Note: User already stopped (by switchUser) so no need to send stop message here.
850 }
851 }
852
853 private void initializeClientLocked(ClientRecord clientRecord) {
854 if (DEBUG) {
855 Slog.d(TAG, clientRecord + ": Registered");
856 }
857 }
858
859 private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
860 if (DEBUG) {
861 if (died) {
862 Slog.d(TAG, clientRecord + ": Died!");
863 } else {
864 Slog.d(TAG, clientRecord + ": Unregistered");
865 }
866 }
867 if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
868 clientRecord.mUserRecord.mHandler.sendEmptyMessage(
869 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
870 }
871 clientRecord.dispose();
872 }
873
874 private boolean validatePackageName(int uid, String packageName) {
875 if (packageName != null) {
876 String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
877 if (packageNames != null) {
878 for (String n : packageNames) {
879 if (n.equals(packageName)) {
880 return true;
881 }
882 }
883 }
884 }
885 return false;
886 }
887
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900888 final class MediaRouterServiceBroadcastReceiver extends BroadcastReceiver {
889 @Override
890 public void onReceive(Context context, Intent intent) {
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900891 if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
892 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
893 synchronized (mLock) {
Sungsoo Lim07e40312020-06-11 16:01:31 +0900894 boolean wasA2dpOn = mGlobalBluetoothA2dpOn;
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900895 mActiveBluetoothDevice = btDevice;
896 mGlobalBluetoothA2dpOn = btDevice != null;
Sungsoo Lim07e40312020-06-11 16:01:31 +0900897 if (wasA2dpOn != mGlobalBluetoothA2dpOn) {
898 UserRecord userRecord = mUserRecords.get(mCurrentUserId);
899 if (userRecord != null) {
900 for (ClientRecord cr : userRecord.mClientRecords) {
901 // mSelectedRouteId will be null for BT and phone speaker.
902 if (cr.mSelectedRouteId == null) {
903 try {
904 cr.mClient.onGlobalA2dpChanged(mGlobalBluetoothA2dpOn);
905 } catch (RemoteException e) {
906 // Ignore exception
907 }
908 }
909 }
910 }
911 }
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900912 }
913 }
914 }
915 }
916
Jeff Brown69b07162013-11-07 00:30:16 -0800917 /**
918 * Information about a particular client of the media router.
919 * The contents of this object is guarded by mLock.
920 */
921 final class ClientRecord implements DeathRecipient {
922 public final UserRecord mUserRecord;
923 public final IMediaRouterClient mClient;
Sungsoob3658562017-05-22 17:10:44 +0900924 public final int mUid;
Jeff Brown69b07162013-11-07 00:30:16 -0800925 public final int mPid;
926 public final String mPackageName;
Jeff Brownaf574182013-11-14 18:16:08 -0800927 public final boolean mTrusted;
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000928 public List<String> mControlCategories;
Jeff Brown69b07162013-11-07 00:30:16 -0800929
930 public int mRouteTypes;
931 public boolean mActiveScan;
932 public String mSelectedRouteId;
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900933 public String mGroupId;
Jeff Brown69b07162013-11-07 00:30:16 -0800934
935 public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
Sungsoob3658562017-05-22 17:10:44 +0900936 int uid, int pid, String packageName, boolean trusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800937 mUserRecord = userRecord;
938 mClient = client;
Sungsoob3658562017-05-22 17:10:44 +0900939 mUid = uid;
Jeff Brown69b07162013-11-07 00:30:16 -0800940 mPid = pid;
941 mPackageName = packageName;
Jeff Brownaf574182013-11-14 18:16:08 -0800942 mTrusted = trusted;
Jeff Brown69b07162013-11-07 00:30:16 -0800943 }
944
945 public void dispose() {
946 mClient.asBinder().unlinkToDeath(this, 0);
947 }
948
949 @Override
950 public void binderDied() {
951 clientDied(this);
952 }
953
Jeff Brownaf574182013-11-14 18:16:08 -0800954 MediaRouterClientState getState() {
Sungsood103e562017-03-09 15:35:07 +0900955 return mTrusted ? mUserRecord.mRouterState : null;
Jeff Brownaf574182013-11-14 18:16:08 -0800956 }
957
Jeff Brown69b07162013-11-07 00:30:16 -0800958 public void dump(PrintWriter pw, String prefix) {
959 pw.println(prefix + this);
960
961 final String indent = prefix + " ";
Jeff Brownaf574182013-11-14 18:16:08 -0800962 pw.println(indent + "mTrusted=" + mTrusted);
Jeff Brown69b07162013-11-07 00:30:16 -0800963 pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
964 pw.println(indent + "mActiveScan=" + mActiveScan);
965 pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
966 }
967
968 @Override
969 public String toString() {
970 return "Client " + mPackageName + " (pid " + mPid + ")";
971 }
972 }
973
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900974 final class ClientGroup {
975 public String mSelectedRouteId;
976 public final List<ClientRecord> mClientRecords = new ArrayList<>();
977 }
978
Jeff Brown69b07162013-11-07 00:30:16 -0800979 /**
980 * Information about a particular user.
981 * The contents of this object is guarded by mLock.
982 */
983 final class UserRecord {
984 public final int mUserId;
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000985 public final ArrayList<ClientRecord> mClientRecords = new ArrayList<>();
Jeff Brown69b07162013-11-07 00:30:16 -0800986 public final UserHandler mHandler;
Sungsood103e562017-03-09 15:35:07 +0900987 public MediaRouterClientState mRouterState;
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900988 private final ArrayMap<String, ClientGroup> mClientGroupMap = new ArrayMap<>();
Jeff Brown69b07162013-11-07 00:30:16 -0800989
990 public UserRecord(int userId) {
991 mUserId = userId;
992 mHandler = new UserHandler(MediaRouterService.this, this);
993 }
994
995 public void dump(final PrintWriter pw, String prefix) {
996 pw.println(prefix + this);
997
998 final String indent = prefix + " ";
999 final int clientCount = mClientRecords.size();
1000 if (clientCount != 0) {
1001 for (int i = 0; i < clientCount; i++) {
1002 mClientRecords.get(i).dump(pw, indent);
1003 }
1004 } else {
1005 pw.println(indent + "<no clients>");
1006 }
1007
Jeff Brownaf574182013-11-14 18:16:08 -08001008 pw.println(indent + "State");
Sungsood103e562017-03-09 15:35:07 +09001009 pw.println(indent + "mRouterState=" + mRouterState);
Jeff Brownaf574182013-11-14 18:16:08 -08001010
Jeff Brown69b07162013-11-07 00:30:16 -08001011 if (!mHandler.runWithScissors(new Runnable() {
1012 @Override
1013 public void run() {
1014 mHandler.dump(pw, indent);
1015 }
1016 }, 1000)) {
1017 pw.println(indent + "<could not dump handler state>");
1018 }
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +09001019 }
1020
1021 public void addToGroup(String groupId, ClientRecord clientRecord) {
1022 ClientGroup group = mClientGroupMap.get(groupId);
1023 if (group == null) {
1024 group = new ClientGroup();
1025 mClientGroupMap.put(groupId, group);
1026 }
1027 group.mClientRecords.add(clientRecord);
1028 }
1029
1030 public void removeFromGroup(String groupId, ClientRecord clientRecord) {
1031 ClientGroup group = mClientGroupMap.get(groupId);
1032 if (group != null) {
1033 group.mClientRecords.remove(clientRecord);
1034 if (group.mClientRecords.size() == 0) {
1035 mClientGroupMap.remove(groupId);
1036 }
1037 }
1038 }
Jeff Brown69b07162013-11-07 00:30:16 -08001039
1040 @Override
1041 public String toString() {
1042 return "User " + mUserId;
1043 }
1044 }
1045
1046 /**
1047 * Media router handler
1048 * <p>
1049 * Since remote display providers are designed to be single-threaded by nature,
1050 * this class encapsulates all of the associated functionality and exports state
1051 * to the service as it evolves.
1052 * </p><p>
Jeff Brown69b07162013-11-07 00:30:16 -08001053 * This class is currently hardcoded to work with remote display providers but
1054 * it is intended to be eventually extended to support more general route providers
1055 * similar to the support library media router.
1056 * </p>
1057 */
1058 static final class UserHandler extends Handler
1059 implements RemoteDisplayProviderWatcher.Callback,
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001060 RemoteDisplayProviderProxy.Callback {
Jeff Brown69b07162013-11-07 00:30:16 -08001061 public static final int MSG_START = 1;
1062 public static final int MSG_STOP = 2;
1063 public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
1064 public static final int MSG_SELECT_ROUTE = 4;
1065 public static final int MSG_UNSELECT_ROUTE = 5;
1066 public static final int MSG_REQUEST_SET_VOLUME = 6;
1067 public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
1068 private static final int MSG_UPDATE_CLIENT_STATE = 8;
1069 private static final int MSG_CONNECTION_TIMED_OUT = 9;
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +09001070 private static final int MSG_UPDATE_SELECTED_ROUTE = 10;
Jeff Brown69b07162013-11-07 00:30:16 -08001071
1072 private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001073 private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
1074 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
1075 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4;
1076
1077 // The relative order of these constants is important and expresses progress
1078 // through the process of connecting to a route.
1079 private static final int PHASE_NOT_AVAILABLE = -1;
1080 private static final int PHASE_NOT_CONNECTED = 0;
1081 private static final int PHASE_CONNECTING = 1;
1082 private static final int PHASE_CONNECTED = 2;
Jeff Brown69b07162013-11-07 00:30:16 -08001083
1084 private final MediaRouterService mService;
1085 private final UserRecord mUserRecord;
1086 private final RemoteDisplayProviderWatcher mWatcher;
1087 private final ArrayList<ProviderRecord> mProviderRecords =
1088 new ArrayList<ProviderRecord>();
1089 private final ArrayList<IMediaRouterClient> mTempClients =
1090 new ArrayList<IMediaRouterClient>();
1091
1092 private boolean mRunning;
1093 private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
Sungsood103e562017-03-09 15:35:07 +09001094 private RouteRecord mSelectedRouteRecord;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001095 private int mConnectionPhase = PHASE_NOT_AVAILABLE;
Jeff Brown69b07162013-11-07 00:30:16 -08001096 private int mConnectionTimeoutReason;
1097 private long mConnectionTimeoutStartTime;
1098 private boolean mClientStateUpdateScheduled;
1099
1100 public UserHandler(MediaRouterService service, UserRecord userRecord) {
1101 super(Looper.getMainLooper(), null, true);
1102 mService = service;
1103 mUserRecord = userRecord;
1104 mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
1105 this, mUserRecord.mUserId);
1106 }
1107
1108 @Override
1109 public void handleMessage(Message msg) {
1110 switch (msg.what) {
1111 case MSG_START: {
1112 start();
1113 break;
1114 }
1115 case MSG_STOP: {
1116 stop();
1117 break;
1118 }
1119 case MSG_UPDATE_DISCOVERY_REQUEST: {
1120 updateDiscoveryRequest();
1121 break;
1122 }
1123 case MSG_SELECT_ROUTE: {
1124 selectRoute((String)msg.obj);
1125 break;
1126 }
1127 case MSG_UNSELECT_ROUTE: {
1128 unselectRoute((String)msg.obj);
1129 break;
1130 }
1131 case MSG_REQUEST_SET_VOLUME: {
1132 requestSetVolume((String)msg.obj, msg.arg1);
1133 break;
1134 }
1135 case MSG_REQUEST_UPDATE_VOLUME: {
1136 requestUpdateVolume((String)msg.obj, msg.arg1);
1137 break;
1138 }
1139 case MSG_UPDATE_CLIENT_STATE: {
1140 updateClientState();
1141 break;
1142 }
1143 case MSG_CONNECTION_TIMED_OUT: {
1144 connectionTimedOut();
1145 break;
1146 }
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +09001147 case MSG_UPDATE_SELECTED_ROUTE: {
1148 updateSelectedRoute((String) msg.obj);
1149 break;
1150 }
Jeff Brown69b07162013-11-07 00:30:16 -08001151 }
1152 }
1153
1154 public void dump(PrintWriter pw, String prefix) {
1155 pw.println(prefix + "Handler");
1156
1157 final String indent = prefix + " ";
1158 pw.println(indent + "mRunning=" + mRunning);
1159 pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
Sungsood103e562017-03-09 15:35:07 +09001160 pw.println(indent + "mSelectedRouteRecord=" + mSelectedRouteRecord);
Jeff Brown39ad0e52013-11-11 17:55:08 -08001161 pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
Jeff Brown69b07162013-11-07 00:30:16 -08001162 pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
1163 pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
1164 TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
1165
1166 mWatcher.dump(pw, prefix);
1167
1168 final int providerCount = mProviderRecords.size();
1169 if (providerCount != 0) {
1170 for (int i = 0; i < providerCount; i++) {
1171 mProviderRecords.get(i).dump(pw, prefix);
1172 }
1173 } else {
1174 pw.println(indent + "<no providers>");
1175 }
1176 }
1177
1178 private void start() {
1179 if (!mRunning) {
1180 mRunning = true;
1181 mWatcher.start(); // also starts all providers
1182 }
1183 }
1184
1185 private void stop() {
1186 if (mRunning) {
1187 mRunning = false;
Sungsood103e562017-03-09 15:35:07 +09001188 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001189 mWatcher.stop(); // also stops all providers
1190 }
1191 }
1192
1193 private void updateDiscoveryRequest() {
1194 int routeTypes = 0;
1195 boolean activeScan = false;
1196 synchronized (mService.mLock) {
1197 final int count = mUserRecord.mClientRecords.size();
1198 for (int i = 0; i < count; i++) {
1199 ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
1200 routeTypes |= clientRecord.mRouteTypes;
1201 activeScan |= clientRecord.mActiveScan;
1202 }
1203 }
1204
1205 final int newDiscoveryMode;
Jeff Brownaf574182013-11-14 18:16:08 -08001206 if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
Jeff Brown69b07162013-11-07 00:30:16 -08001207 if (activeScan) {
1208 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
1209 } else {
1210 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
1211 }
1212 } else {
1213 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
1214 }
1215
1216 if (mDiscoveryMode != newDiscoveryMode) {
1217 mDiscoveryMode = newDiscoveryMode;
1218 final int count = mProviderRecords.size();
1219 for (int i = 0; i < count; i++) {
1220 mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
1221 }
1222 }
1223 }
1224
1225 private void selectRoute(String routeId) {
1226 if (routeId != null
Sungsood103e562017-03-09 15:35:07 +09001227 && (mSelectedRouteRecord == null
1228 || !routeId.equals(mSelectedRouteRecord.getUniqueId()))) {
Jeff Brown69b07162013-11-07 00:30:16 -08001229 RouteRecord routeRecord = findRouteRecord(routeId);
1230 if (routeRecord != null) {
Sungsood103e562017-03-09 15:35:07 +09001231 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001232
Sungsood103e562017-03-09 15:35:07 +09001233 Slog.i(TAG, "Selected route:" + routeRecord);
1234 mSelectedRouteRecord = routeRecord;
1235 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001236 routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
1237
1238 scheduleUpdateClientState();
1239 }
1240 }
1241 }
1242
1243 private void unselectRoute(String routeId) {
1244 if (routeId != null
Sungsood103e562017-03-09 15:35:07 +09001245 && mSelectedRouteRecord != null
1246 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1247 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001248 }
1249 }
1250
Sungsood103e562017-03-09 15:35:07 +09001251 private void unselectSelectedRoute() {
1252 if (mSelectedRouteRecord != null) {
1253 Slog.i(TAG, "Unselected route:" + mSelectedRouteRecord);
1254 mSelectedRouteRecord.getProvider().setSelectedDisplay(null);
1255 mSelectedRouteRecord = null;
1256 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001257
1258 scheduleUpdateClientState();
1259 }
1260 }
1261
1262 private void requestSetVolume(String routeId, int volume) {
Sungsood103e562017-03-09 15:35:07 +09001263 if (mSelectedRouteRecord != null
1264 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1265 mSelectedRouteRecord.getProvider().setDisplayVolume(volume);
Jeff Brown69b07162013-11-07 00:30:16 -08001266 }
1267 }
1268
1269 private void requestUpdateVolume(String routeId, int direction) {
Sungsood103e562017-03-09 15:35:07 +09001270 if (mSelectedRouteRecord != null
1271 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1272 mSelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
Jeff Brown69b07162013-11-07 00:30:16 -08001273 }
1274 }
1275
1276 @Override
1277 public void addProvider(RemoteDisplayProviderProxy provider) {
1278 provider.setCallback(this);
1279 provider.setDiscoveryMode(mDiscoveryMode);
1280 provider.setSelectedDisplay(null); // just to be safe
1281
1282 ProviderRecord providerRecord = new ProviderRecord(provider);
1283 mProviderRecords.add(providerRecord);
1284 providerRecord.updateDescriptor(provider.getDisplayState());
1285
1286 scheduleUpdateClientState();
1287 }
1288
1289 @Override
1290 public void removeProvider(RemoteDisplayProviderProxy provider) {
1291 int index = findProviderRecord(provider);
1292 if (index >= 0) {
1293 ProviderRecord providerRecord = mProviderRecords.remove(index);
1294 providerRecord.updateDescriptor(null); // mark routes invalid
1295 provider.setCallback(null);
1296 provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
1297
Sungsood103e562017-03-09 15:35:07 +09001298 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001299 scheduleUpdateClientState();
1300 }
1301 }
1302
1303 @Override
1304 public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
1305 RemoteDisplayState state) {
1306 updateProvider(provider, state);
1307 }
1308
1309 private void updateProvider(RemoteDisplayProviderProxy provider,
1310 RemoteDisplayState state) {
1311 int index = findProviderRecord(provider);
1312 if (index >= 0) {
1313 ProviderRecord providerRecord = mProviderRecords.get(index);
1314 if (providerRecord.updateDescriptor(state)) {
Sungsood103e562017-03-09 15:35:07 +09001315 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001316 scheduleUpdateClientState();
1317 }
1318 }
1319 }
1320
1321 /**
Sungsood103e562017-03-09 15:35:07 +09001322 * This function is called whenever the state of the selected route may have changed.
1323 * It checks the state and updates timeouts or unselects the route as appropriate.
Jeff Brown69b07162013-11-07 00:30:16 -08001324 */
Sungsood103e562017-03-09 15:35:07 +09001325 private void checkSelectedRouteState() {
Jeff Brown69b07162013-11-07 00:30:16 -08001326 // Unschedule timeouts when the route is unselected.
Sungsood103e562017-03-09 15:35:07 +09001327 if (mSelectedRouteRecord == null) {
Jeff Brown39ad0e52013-11-11 17:55:08 -08001328 mConnectionPhase = PHASE_NOT_AVAILABLE;
Jeff Brown69b07162013-11-07 00:30:16 -08001329 updateConnectionTimeout(0);
1330 return;
1331 }
1332
1333 // Ensure that the route is still present and enabled.
Sungsood103e562017-03-09 15:35:07 +09001334 if (!mSelectedRouteRecord.isValid()
1335 || !mSelectedRouteRecord.isEnabled()) {
Jeff Brown69b07162013-11-07 00:30:16 -08001336 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
1337 return;
1338 }
1339
Jeff Brown39ad0e52013-11-11 17:55:08 -08001340 // Make sure we haven't lost our connection.
1341 final int oldPhase = mConnectionPhase;
Sungsood103e562017-03-09 15:35:07 +09001342 mConnectionPhase = getConnectionPhase(mSelectedRouteRecord.getStatus());
Jeff Brown39ad0e52013-11-11 17:55:08 -08001343 if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
1344 updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
1345 return;
1346 }
1347
Jeff Brown69b07162013-11-07 00:30:16 -08001348 // Check the route status.
Jeff Brown39ad0e52013-11-11 17:55:08 -08001349 switch (mConnectionPhase) {
1350 case PHASE_CONNECTED:
1351 if (oldPhase != PHASE_CONNECTED) {
Sungsood103e562017-03-09 15:35:07 +09001352 Slog.i(TAG, "Connected to route: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001353 }
1354 updateConnectionTimeout(0);
1355 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001356 case PHASE_CONNECTING:
1357 if (oldPhase != PHASE_CONNECTING) {
Sungsood103e562017-03-09 15:35:07 +09001358 Slog.i(TAG, "Connecting to route: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001359 }
1360 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
1361 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001362 case PHASE_NOT_CONNECTED:
Jeff Brown69b07162013-11-07 00:30:16 -08001363 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
1364 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001365 case PHASE_NOT_AVAILABLE:
Jeff Brown69b07162013-11-07 00:30:16 -08001366 default:
1367 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
1368 break;
1369 }
1370 }
1371
1372 private void updateConnectionTimeout(int reason) {
1373 if (reason != mConnectionTimeoutReason) {
1374 if (mConnectionTimeoutReason != 0) {
1375 removeMessages(MSG_CONNECTION_TIMED_OUT);
1376 }
1377 mConnectionTimeoutReason = reason;
1378 mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
1379 switch (reason) {
1380 case TIMEOUT_REASON_NOT_AVAILABLE:
Jeff Brown39ad0e52013-11-11 17:55:08 -08001381 case TIMEOUT_REASON_CONNECTION_LOST:
1382 // Route became unavailable or connection lost.
1383 // Unselect it immediately.
Jeff Brown69b07162013-11-07 00:30:16 -08001384 sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
1385 break;
1386 case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
1387 // Waiting for route to start connecting.
1388 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
1389 break;
1390 case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
1391 // Waiting for route to complete connection.
1392 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
1393 break;
1394 }
1395 }
1396 }
1397
1398 private void connectionTimedOut() {
Sungsood103e562017-03-09 15:35:07 +09001399 if (mConnectionTimeoutReason == 0 || mSelectedRouteRecord == null) {
Jeff Brown69b07162013-11-07 00:30:16 -08001400 // Shouldn't get here. There must be a bug somewhere.
1401 Log.wtf(TAG, "Handled connection timeout for no reason.");
1402 return;
1403 }
1404
1405 switch (mConnectionTimeoutReason) {
1406 case TIMEOUT_REASON_NOT_AVAILABLE:
Sungsood103e562017-03-09 15:35:07 +09001407 Slog.i(TAG, "Selected route no longer available: "
1408 + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001409 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001410 case TIMEOUT_REASON_CONNECTION_LOST:
Sungsood103e562017-03-09 15:35:07 +09001411 Slog.i(TAG, "Selected route connection lost: "
1412 + mSelectedRouteRecord);
Jeff Brown39ad0e52013-11-11 17:55:08 -08001413 break;
Jeff Brown69b07162013-11-07 00:30:16 -08001414 case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
Sungsood103e562017-03-09 15:35:07 +09001415 Slog.i(TAG, "Selected route timed out while waiting for "
Jeff Brown69b07162013-11-07 00:30:16 -08001416 + "connection attempt to begin after "
1417 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
Sungsood103e562017-03-09 15:35:07 +09001418 + " ms: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001419 break;
1420 case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
Sungsood103e562017-03-09 15:35:07 +09001421 Slog.i(TAG, "Selected route timed out while connecting after "
Jeff Brown69b07162013-11-07 00:30:16 -08001422 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
Sungsood103e562017-03-09 15:35:07 +09001423 + " ms: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001424 break;
1425 }
1426 mConnectionTimeoutReason = 0;
1427
Sungsood103e562017-03-09 15:35:07 +09001428 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001429 }
1430
1431 private void scheduleUpdateClientState() {
1432 if (!mClientStateUpdateScheduled) {
1433 mClientStateUpdateScheduled = true;
1434 sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
1435 }
1436 }
1437
1438 private void updateClientState() {
1439 mClientStateUpdateScheduled = false;
1440
Jeff Brownaf574182013-11-14 18:16:08 -08001441 // Build a new client state for trusted clients.
Sungsood103e562017-03-09 15:35:07 +09001442 MediaRouterClientState routerState = new MediaRouterClientState();
Jeff Brown69b07162013-11-07 00:30:16 -08001443 final int providerCount = mProviderRecords.size();
1444 for (int i = 0; i < providerCount; i++) {
Sungsood103e562017-03-09 15:35:07 +09001445 mProviderRecords.get(i).appendClientState(routerState);
Jeff Brown69b07162013-11-07 00:30:16 -08001446 }
Jeff Brown69b07162013-11-07 00:30:16 -08001447 try {
1448 synchronized (mService.mLock) {
1449 // Update the UserRecord.
Sungsood103e562017-03-09 15:35:07 +09001450 mUserRecord.mRouterState = routerState;
Jeff Brown69b07162013-11-07 00:30:16 -08001451
1452 // Collect all clients.
1453 final int count = mUserRecord.mClientRecords.size();
1454 for (int i = 0; i < count; i++) {
1455 mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
1456 }
1457 }
1458
1459 // Notify all clients (outside of the lock).
1460 final int count = mTempClients.size();
1461 for (int i = 0; i < count; i++) {
1462 try {
1463 mTempClients.get(i).onStateChanged();
1464 } catch (RemoteException ex) {
Sungsoob3658562017-05-22 17:10:44 +09001465 Slog.w(TAG, "Failed to call onStateChanged. Client probably died.");
Jeff Brown69b07162013-11-07 00:30:16 -08001466 }
1467 }
1468 } finally {
1469 // Clear the list in preparation for the next time.
1470 mTempClients.clear();
1471 }
1472 }
1473
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +09001474 private void updateSelectedRoute(String groupId) {
1475 try {
1476 String selectedRouteId = null;
1477 synchronized (mService.mLock) {
1478 ClientGroup group = mUserRecord.mClientGroupMap.get(groupId);
1479 if (group == null) {
1480 return;
1481 }
1482 selectedRouteId = group.mSelectedRouteId;
1483 final int count = group.mClientRecords.size();
1484 for (int i = 0; i < count; i++) {
1485 ClientRecord clientRecord = group.mClientRecords.get(i);
1486 if (!TextUtils.equals(selectedRouteId, clientRecord.mSelectedRouteId)) {
1487 mTempClients.add(clientRecord.mClient);
1488 }
1489 }
1490 }
1491
1492 final int count = mTempClients.size();
1493 for (int i = 0; i < count; i++) {
1494 try {
1495 mTempClients.get(i).onSelectedRouteChanged(selectedRouteId);
1496 } catch (RemoteException ex) {
1497 Slog.w(TAG, "Failed to call onSelectedRouteChanged. Client probably died.");
1498 }
1499 }
1500 } finally {
1501 mTempClients.clear();
1502 }
1503 }
1504
Jeff Brown69b07162013-11-07 00:30:16 -08001505 private int findProviderRecord(RemoteDisplayProviderProxy provider) {
1506 final int count = mProviderRecords.size();
1507 for (int i = 0; i < count; i++) {
1508 ProviderRecord record = mProviderRecords.get(i);
1509 if (record.getProvider() == provider) {
1510 return i;
1511 }
1512 }
1513 return -1;
1514 }
1515
1516 private RouteRecord findRouteRecord(String uniqueId) {
1517 final int count = mProviderRecords.size();
1518 for (int i = 0; i < count; i++) {
1519 RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
1520 if (record != null) {
1521 return record;
1522 }
1523 }
1524 return null;
1525 }
1526
Jeff Brown39ad0e52013-11-11 17:55:08 -08001527 private static int getConnectionPhase(int status) {
1528 switch (status) {
1529 case MediaRouter.RouteInfo.STATUS_NONE:
1530 case MediaRouter.RouteInfo.STATUS_CONNECTED:
1531 return PHASE_CONNECTED;
1532 case MediaRouter.RouteInfo.STATUS_CONNECTING:
1533 return PHASE_CONNECTING;
1534 case MediaRouter.RouteInfo.STATUS_SCANNING:
1535 case MediaRouter.RouteInfo.STATUS_AVAILABLE:
1536 return PHASE_NOT_CONNECTED;
1537 case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
1538 case MediaRouter.RouteInfo.STATUS_IN_USE:
1539 default:
1540 return PHASE_NOT_AVAILABLE;
1541 }
1542 }
1543
Jeff Brown69b07162013-11-07 00:30:16 -08001544 static final class ProviderRecord {
1545 private final RemoteDisplayProviderProxy mProvider;
1546 private final String mUniquePrefix;
1547 private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
1548 private RemoteDisplayState mDescriptor;
1549
1550 public ProviderRecord(RemoteDisplayProviderProxy provider) {
1551 mProvider = provider;
1552 mUniquePrefix = provider.getFlattenedComponentName() + ":";
1553 }
1554
1555 public RemoteDisplayProviderProxy getProvider() {
1556 return mProvider;
1557 }
1558
1559 public String getUniquePrefix() {
1560 return mUniquePrefix;
1561 }
1562
1563 public boolean updateDescriptor(RemoteDisplayState descriptor) {
1564 boolean changed = false;
1565 if (mDescriptor != descriptor) {
1566 mDescriptor = descriptor;
1567
1568 // Update all existing routes and reorder them to match
1569 // the order of their descriptors.
1570 int targetIndex = 0;
1571 if (descriptor != null) {
1572 if (descriptor.isValid()) {
1573 final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
1574 final int routeCount = routeDescriptors.size();
1575 for (int i = 0; i < routeCount; i++) {
1576 final RemoteDisplayInfo routeDescriptor =
1577 routeDescriptors.get(i);
1578 final String descriptorId = routeDescriptor.id;
1579 final int sourceIndex = findRouteByDescriptorId(descriptorId);
1580 if (sourceIndex < 0) {
1581 // Add the route to the provider.
1582 String uniqueId = assignRouteUniqueId(descriptorId);
1583 RouteRecord route =
1584 new RouteRecord(this, descriptorId, uniqueId);
1585 mRoutes.add(targetIndex++, route);
1586 route.updateDescriptor(routeDescriptor);
1587 changed = true;
1588 } else if (sourceIndex < targetIndex) {
1589 // Ignore route with duplicate id.
1590 Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
1591 + routeDescriptor);
1592 } else {
1593 // Reorder existing route within the list.
1594 RouteRecord route = mRoutes.get(sourceIndex);
1595 Collections.swap(mRoutes, sourceIndex, targetIndex++);
1596 changed |= route.updateDescriptor(routeDescriptor);
1597 }
1598 }
1599 } else {
1600 Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
1601 + mProvider.getFlattenedComponentName());
1602 }
1603 }
1604
1605 // Dispose all remaining routes that do not have matching descriptors.
1606 for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
1607 RouteRecord route = mRoutes.remove(i);
1608 route.updateDescriptor(null); // mark route invalid
1609 changed = true;
1610 }
1611 }
1612 return changed;
1613 }
1614
1615 public void appendClientState(MediaRouterClientState state) {
1616 final int routeCount = mRoutes.size();
1617 for (int i = 0; i < routeCount; i++) {
1618 state.routes.add(mRoutes.get(i).getInfo());
1619 }
1620 }
1621
1622 public RouteRecord findRouteByUniqueId(String uniqueId) {
1623 final int routeCount = mRoutes.size();
1624 for (int i = 0; i < routeCount; i++) {
1625 RouteRecord route = mRoutes.get(i);
1626 if (route.getUniqueId().equals(uniqueId)) {
1627 return route;
1628 }
1629 }
1630 return null;
1631 }
1632
1633 private int findRouteByDescriptorId(String descriptorId) {
1634 final int routeCount = mRoutes.size();
1635 for (int i = 0; i < routeCount; i++) {
1636 RouteRecord route = mRoutes.get(i);
1637 if (route.getDescriptorId().equals(descriptorId)) {
1638 return i;
1639 }
1640 }
1641 return -1;
1642 }
1643
1644 public void dump(PrintWriter pw, String prefix) {
1645 pw.println(prefix + this);
1646
1647 final String indent = prefix + " ";
1648 mProvider.dump(pw, indent);
1649
1650 final int routeCount = mRoutes.size();
1651 if (routeCount != 0) {
1652 for (int i = 0; i < routeCount; i++) {
1653 mRoutes.get(i).dump(pw, indent);
1654 }
1655 } else {
1656 pw.println(indent + "<no routes>");
1657 }
1658 }
1659
1660 @Override
1661 public String toString() {
1662 return "Provider " + mProvider.getFlattenedComponentName();
1663 }
1664
1665 private String assignRouteUniqueId(String descriptorId) {
1666 return mUniquePrefix + descriptorId;
1667 }
1668 }
1669
1670 static final class RouteRecord {
1671 private final ProviderRecord mProviderRecord;
1672 private final String mDescriptorId;
1673 private final MediaRouterClientState.RouteInfo mMutableInfo;
1674 private MediaRouterClientState.RouteInfo mImmutableInfo;
1675 private RemoteDisplayInfo mDescriptor;
1676
1677 public RouteRecord(ProviderRecord providerRecord,
1678 String descriptorId, String uniqueId) {
1679 mProviderRecord = providerRecord;
1680 mDescriptorId = descriptorId;
1681 mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
1682 }
1683
1684 public RemoteDisplayProviderProxy getProvider() {
1685 return mProviderRecord.getProvider();
1686 }
1687
1688 public ProviderRecord getProviderRecord() {
1689 return mProviderRecord;
1690 }
1691
1692 public String getDescriptorId() {
1693 return mDescriptorId;
1694 }
1695
1696 public String getUniqueId() {
1697 return mMutableInfo.id;
1698 }
1699
1700 public MediaRouterClientState.RouteInfo getInfo() {
1701 if (mImmutableInfo == null) {
1702 mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
1703 }
1704 return mImmutableInfo;
1705 }
1706
1707 public boolean isValid() {
1708 return mDescriptor != null;
1709 }
1710
1711 public boolean isEnabled() {
1712 return mMutableInfo.enabled;
1713 }
1714
1715 public int getStatus() {
1716 return mMutableInfo.statusCode;
1717 }
1718
1719 public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
1720 boolean changed = false;
1721 if (mDescriptor != descriptor) {
1722 mDescriptor = descriptor;
1723 if (descriptor != null) {
1724 final String name = computeName(descriptor);
Kenny Roote6585b32013-12-13 12:00:26 -08001725 if (!Objects.equals(mMutableInfo.name, name)) {
Jeff Brown69b07162013-11-07 00:30:16 -08001726 mMutableInfo.name = name;
1727 changed = true;
1728 }
1729 final String description = computeDescription(descriptor);
Kenny Roote6585b32013-12-13 12:00:26 -08001730 if (!Objects.equals(mMutableInfo.description, description)) {
Jeff Brown69b07162013-11-07 00:30:16 -08001731 mMutableInfo.description = description;
1732 changed = true;
1733 }
1734 final int supportedTypes = computeSupportedTypes(descriptor);
1735 if (mMutableInfo.supportedTypes != supportedTypes) {
1736 mMutableInfo.supportedTypes = supportedTypes;
1737 changed = true;
1738 }
1739 final boolean enabled = computeEnabled(descriptor);
1740 if (mMutableInfo.enabled != enabled) {
1741 mMutableInfo.enabled = enabled;
1742 changed = true;
1743 }
1744 final int statusCode = computeStatusCode(descriptor);
1745 if (mMutableInfo.statusCode != statusCode) {
1746 mMutableInfo.statusCode = statusCode;
1747 changed = true;
1748 }
1749 final int playbackType = computePlaybackType(descriptor);
1750 if (mMutableInfo.playbackType != playbackType) {
1751 mMutableInfo.playbackType = playbackType;
1752 changed = true;
1753 }
1754 final int playbackStream = computePlaybackStream(descriptor);
1755 if (mMutableInfo.playbackStream != playbackStream) {
1756 mMutableInfo.playbackStream = playbackStream;
1757 changed = true;
1758 }
1759 final int volume = computeVolume(descriptor);
1760 if (mMutableInfo.volume != volume) {
1761 mMutableInfo.volume = volume;
1762 changed = true;
1763 }
1764 final int volumeMax = computeVolumeMax(descriptor);
1765 if (mMutableInfo.volumeMax != volumeMax) {
1766 mMutableInfo.volumeMax = volumeMax;
1767 changed = true;
1768 }
1769 final int volumeHandling = computeVolumeHandling(descriptor);
1770 if (mMutableInfo.volumeHandling != volumeHandling) {
1771 mMutableInfo.volumeHandling = volumeHandling;
1772 changed = true;
1773 }
1774 final int presentationDisplayId = computePresentationDisplayId(descriptor);
1775 if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
1776 mMutableInfo.presentationDisplayId = presentationDisplayId;
1777 changed = true;
1778 }
1779 }
1780 }
1781 if (changed) {
1782 mImmutableInfo = null;
1783 }
1784 return changed;
1785 }
1786
1787 public void dump(PrintWriter pw, String prefix) {
1788 pw.println(prefix + this);
1789
1790 final String indent = prefix + " ";
1791 pw.println(indent + "mMutableInfo=" + mMutableInfo);
1792 pw.println(indent + "mDescriptorId=" + mDescriptorId);
1793 pw.println(indent + "mDescriptor=" + mDescriptor);
1794 }
1795
1796 @Override
1797 public String toString() {
1798 return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
1799 }
1800
1801 private static String computeName(RemoteDisplayInfo descriptor) {
1802 // Note that isValid() already ensures the name is non-empty.
1803 return descriptor.name;
1804 }
1805
1806 private static String computeDescription(RemoteDisplayInfo descriptor) {
1807 final String description = descriptor.description;
1808 return TextUtils.isEmpty(description) ? null : description;
1809 }
1810
1811 private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
1812 return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
1813 | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
1814 | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
1815 }
1816
1817 private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
1818 switch (descriptor.status) {
1819 case RemoteDisplayInfo.STATUS_CONNECTED:
1820 case RemoteDisplayInfo.STATUS_CONNECTING:
1821 case RemoteDisplayInfo.STATUS_AVAILABLE:
1822 return true;
1823 default:
1824 return false;
1825 }
1826 }
1827
1828 private static int computeStatusCode(RemoteDisplayInfo descriptor) {
1829 switch (descriptor.status) {
1830 case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
1831 return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
1832 case RemoteDisplayInfo.STATUS_AVAILABLE:
1833 return MediaRouter.RouteInfo.STATUS_AVAILABLE;
1834 case RemoteDisplayInfo.STATUS_IN_USE:
1835 return MediaRouter.RouteInfo.STATUS_IN_USE;
1836 case RemoteDisplayInfo.STATUS_CONNECTING:
1837 return MediaRouter.RouteInfo.STATUS_CONNECTING;
1838 case RemoteDisplayInfo.STATUS_CONNECTED:
1839 return MediaRouter.RouteInfo.STATUS_CONNECTED;
1840 default:
1841 return MediaRouter.RouteInfo.STATUS_NONE;
1842 }
1843 }
1844
1845 private static int computePlaybackType(RemoteDisplayInfo descriptor) {
1846 return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
1847 }
1848
1849 private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
1850 return AudioSystem.STREAM_MUSIC;
1851 }
1852
1853 private static int computeVolume(RemoteDisplayInfo descriptor) {
1854 final int volume = descriptor.volume;
1855 final int volumeMax = descriptor.volumeMax;
1856 if (volume < 0) {
1857 return 0;
1858 } else if (volume > volumeMax) {
1859 return volumeMax;
1860 }
1861 return volume;
1862 }
1863
1864 private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
1865 final int volumeMax = descriptor.volumeMax;
1866 return volumeMax > 0 ? volumeMax : 0;
1867 }
1868
1869 private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
1870 final int volumeHandling = descriptor.volumeHandling;
1871 switch (volumeHandling) {
1872 case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
1873 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
1874 case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
1875 default:
1876 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
1877 }
1878 }
1879
1880 private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
1881 // The MediaRouter class validates that the id corresponds to an extant
1882 // presentation display. So all we do here is canonicalize the null case.
1883 final int displayId = descriptor.presentationDisplayId;
1884 return displayId < 0 ? -1 : displayId;
1885 }
1886 }
1887 }
Jeff Brown69b07162013-11-07 00:30:16 -08001888}