blob: 0e52a67c8d39c33dd6740f17251a5534668522a1 [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,
Kyunglyul Hyun6a2f1d62020-05-19 23:00:17 +0900484 long managerRequestId, RoutingSessionInfo oldSession,
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900485 MediaRoute2Info route, Bundle sessionHints) {
Kyunglyul Hyun6a2f1d62020-05-19 23:00:17 +0900486 mService2.requestCreateSessionWithRouter2(router, requestId, managerRequestId,
487 oldSession, route, sessionHints);
Kyunglyul Hyunf0eb51b2020-04-14 19:56:16 +0900488 }
489
490 // Binder call
491 @Override
Hyundo Moon56337492020-02-16 16:19:54 +0900492 public void selectRouteWithRouter2(IMediaRouter2 router, String sessionId,
Hyundo Moon0a926572019-12-28 15:01:21 +0900493 MediaRoute2Info route) {
Hyundo Moon56337492020-02-16 16:19:54 +0900494 mService2.selectRouteWithRouter2(router, sessionId, route);
Hyundo Moon0a926572019-12-28 15:01:21 +0900495 }
496
497 // Binder call
498 @Override
Hyundo Moon56337492020-02-16 16:19:54 +0900499 public void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId,
500 MediaRoute2Info route) {
501 mService2.deselectRouteWithRouter2(router, sessionId, route);
502 }
503
504 // Binder call
505 @Override
506 public void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
507 MediaRoute2Info route) {
508 mService2.transferToRouteWithRouter2(router, sessionId, route);
509 }
510
511 // Binder call
512 @Override
513 public void setSessionVolumeWithRouter2(IMediaRouter2 router, String sessionId, int volume) {
514 mService2.setSessionVolumeWithRouter2(router, sessionId, volume);
515 }
516
517 // Binder call
518 @Override
519 public void releaseSessionWithRouter2(IMediaRouter2 router, String sessionId) {
520 mService2.releaseSessionWithRouter2(router, sessionId);
521 }
522
523 // Binder call
524 @Override
525 public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
526 return mService2.getActiveSessions(manager);
Hyundo Moonca9db782020-01-01 18:31:15 +0900527 }
528
529 // Binder call
530 @Override
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900531 public void registerManager(IMediaRouter2Manager manager, String packageName) {
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000532 final int uid = Binder.getCallingUid();
533 if (!validatePackageName(uid, packageName)) {
534 throw new SecurityException("packageName must match the calling uid");
535 }
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900536 mService2.registerManager(manager, packageName);
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000537 }
538
539 // Binder call
540 @Override
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900541 public void unregisterManager(IMediaRouter2Manager manager) {
542 mService2.unregisterManager(manager);
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000543 }
544
545 // Binder call
546 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900547 public void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
548 MediaRoute2Info route, int volume) {
549 mService2.setRouteVolumeWithManager(manager, requestId, route, volume);
Hyundo Moon56337492020-02-16 16:19:54 +0900550 }
551
552 // Binder call
553 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900554 public void requestCreateSessionWithManager(IMediaRouter2Manager manager,
Kyunglyul Hyun6a2f1d62020-05-19 23:00:17 +0900555 int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
556 mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route);
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900557 }
558
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900559 // Binder call
560 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900561 public void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
562 String sessionId, MediaRoute2Info route) {
563 mService2.selectRouteWithManager(manager, requestId, sessionId, route);
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900564 }
565
566 // Binder call
567 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900568 public void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
569 String sessionId, MediaRoute2Info route) {
570 mService2.deselectRouteWithManager(manager, requestId, sessionId, route);
Kyunglyul Hyun5161b372020-02-05 18:45:35 +0900571 }
572
573 // Binder call
574 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900575 public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
576 String sessionId, MediaRoute2Info route) {
577 mService2.transferToRouteWithManager(manager, requestId, sessionId, route);
Kyunglyul Hyun5161b372020-02-05 18:45:35 +0900578 }
579
580 // Binder call
581 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900582 public void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId,
583 String sessionId, int volume) {
584 mService2.setSessionVolumeWithManager(manager, requestId, sessionId, volume);
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900585 }
586
587 // Binder call
588 @Override
Hyundo Moonf8e49f4b2020-03-06 17:19:42 +0900589 public void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId,
590 String sessionId) {
591 mService2.releaseSessionWithManager(manager, requestId, sessionId);
Kyunglyul Hyunc6583832020-01-17 11:11:46 +0000592 }
593
Sungsoob3658562017-05-22 17:10:44 +0900594 void restoreBluetoothA2dp() {
595 try {
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900596 boolean a2dpOn;
597 BluetoothDevice btDevice;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900598 synchronized (mLock) {
599 a2dpOn = mGlobalBluetoothA2dpOn;
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900600 btDevice = mActiveBluetoothDevice;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900601 }
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900602 // We don't need to change a2dp status when bluetooth is not connected.
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900603 if (btDevice != null) {
Sungsoo Limc3b52952018-06-11 12:55:25 +0900604 if (DEBUG) {
605 Slog.d(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
606 }
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900607 mAudioService.setBluetoothA2dpOn(a2dpOn);
608 }
Sungsoob3658562017-05-22 17:10:44 +0900609 } catch (RemoteException e) {
610 Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
611 }
612 }
613
614 void restoreRoute(int uid) {
615 ClientRecord clientRecord = null;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900616 synchronized (mLock) {
Sungsoo Lim277ea282020-01-14 16:35:05 +0900617 UserRecord userRecord = mUserRecords.get(
618 UserHandle.getUserHandleForUid(uid).getIdentifier());
Sungsoo Lim76512a32017-08-24 10:25:06 +0900619 if (userRecord != null && userRecord.mClientRecords != null) {
620 for (ClientRecord cr : userRecord.mClientRecords) {
621 if (validatePackageName(uid, cr.mPackageName)) {
622 clientRecord = cr;
623 break;
624 }
Sungsoob3658562017-05-22 17:10:44 +0900625 }
626 }
627 }
628 if (clientRecord != null) {
629 try {
630 clientRecord.mClient.onRestoreRoute();
631 } catch (RemoteException e) {
632 Slog.w(TAG, "Failed to call onRestoreRoute. Client probably died.");
633 }
634 } else {
635 restoreBluetoothA2dp();
636 }
637 }
638
Jeff Brown69b07162013-11-07 00:30:16 -0800639 void switchUser() {
640 synchronized (mLock) {
641 int userId = ActivityManager.getCurrentUser();
642 if (mCurrentUserId != userId) {
643 final int oldUserId = mCurrentUserId;
644 mCurrentUserId = userId; // do this first
645
646 UserRecord oldUser = mUserRecords.get(oldUserId);
647 if (oldUser != null) {
648 oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
649 disposeUserIfNeededLocked(oldUser); // since no longer current user
650 }
651
652 UserRecord newUser = mUserRecords.get(userId);
653 if (newUser != null) {
654 newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
655 }
656 }
657 }
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900658 mService2.switchUser();
Jeff Brown69b07162013-11-07 00:30:16 -0800659 }
660
661 void clientDied(ClientRecord clientRecord) {
662 synchronized (mLock) {
663 unregisterClientLocked(clientRecord.mClient, true);
664 }
665 }
666
667 private void registerClientLocked(IMediaRouterClient client,
Sungsoob3658562017-05-22 17:10:44 +0900668 int uid, int pid, String packageName, int userId, boolean trusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800669 final IBinder binder = client.asBinder();
670 ClientRecord clientRecord = mAllClientRecords.get(binder);
671 if (clientRecord == null) {
672 boolean newUser = false;
673 UserRecord userRecord = mUserRecords.get(userId);
674 if (userRecord == null) {
675 userRecord = new UserRecord(userId);
676 newUser = true;
677 }
Sungsoob3658562017-05-22 17:10:44 +0900678 clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted);
Jeff Brown69b07162013-11-07 00:30:16 -0800679 try {
680 binder.linkToDeath(clientRecord, 0);
681 } catch (RemoteException ex) {
682 throw new RuntimeException("Media router client died prematurely.", ex);
683 }
684
685 if (newUser) {
686 mUserRecords.put(userId, userRecord);
687 initializeUserLocked(userRecord);
688 }
689
690 userRecord.mClientRecords.add(clientRecord);
691 mAllClientRecords.put(binder, clientRecord);
692 initializeClientLocked(clientRecord);
693 }
694 }
695
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900696 private void registerClientGroupIdLocked(IMediaRouterClient client, String groupId) {
697 final IBinder binder = client.asBinder();
698 ClientRecord clientRecord = mAllClientRecords.get(binder);
699 if (clientRecord == null) {
700 Log.w(TAG, "Ignoring group id register request of a unregistered client.");
701 return;
702 }
703 if (TextUtils.equals(clientRecord.mGroupId, groupId)) {
704 return;
705 }
706 UserRecord userRecord = clientRecord.mUserRecord;
707 if (clientRecord.mGroupId != null) {
708 userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord);
709 }
710 clientRecord.mGroupId = groupId;
711 if (groupId != null) {
712 userRecord.addToGroup(groupId, clientRecord);
713 userRecord.mHandler.obtainMessage(UserHandler.MSG_UPDATE_SELECTED_ROUTE, groupId)
714 .sendToTarget();
715 }
716 }
717
Jeff Brown69b07162013-11-07 00:30:16 -0800718 private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
719 ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
720 if (clientRecord != null) {
721 UserRecord userRecord = clientRecord.mUserRecord;
722 userRecord.mClientRecords.remove(clientRecord);
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900723 if (clientRecord.mGroupId != null) {
724 userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord);
725 clientRecord.mGroupId = null;
726 }
Jeff Brown69b07162013-11-07 00:30:16 -0800727 disposeClientLocked(clientRecord, died);
728 disposeUserIfNeededLocked(userRecord); // since client removed from user
729 }
730 }
731
732 private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
733 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
734 if (clientRecord != null) {
Jeff Brownaf574182013-11-14 18:16:08 -0800735 return clientRecord.getState();
Jeff Brown69b07162013-11-07 00:30:16 -0800736 }
737 return null;
738 }
739
740 private void setDiscoveryRequestLocked(IMediaRouterClient client,
741 int routeTypes, boolean activeScan) {
742 final IBinder binder = client.asBinder();
743 ClientRecord clientRecord = mAllClientRecords.get(binder);
744 if (clientRecord != null) {
Jeff Brownaf574182013-11-14 18:16:08 -0800745 // Only let the system discover remote display routes for now.
746 if (!clientRecord.mTrusted) {
747 routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
748 }
749
Jeff Brown69b07162013-11-07 00:30:16 -0800750 if (clientRecord.mRouteTypes != routeTypes
751 || clientRecord.mActiveScan != activeScan) {
752 if (DEBUG) {
753 Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
754 + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
755 }
756 clientRecord.mRouteTypes = routeTypes;
757 clientRecord.mActiveScan = activeScan;
758 clientRecord.mUserRecord.mHandler.sendEmptyMessage(
759 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
760 }
761 }
762 }
763
764 private void setSelectedRouteLocked(IMediaRouterClient client,
765 String routeId, boolean explicit) {
766 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
767 if (clientRecord != null) {
768 final String oldRouteId = clientRecord.mSelectedRouteId;
Kenny Roote6585b32013-12-13 12:00:26 -0800769 if (!Objects.equals(routeId, oldRouteId)) {
Jeff Brown69b07162013-11-07 00:30:16 -0800770 if (DEBUG) {
771 Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
772 + ", oldRouteId=" + oldRouteId
773 + ", explicit=" + explicit);
774 }
775
776 clientRecord.mSelectedRouteId = routeId;
Sungsood103e562017-03-09 15:35:07 +0900777 // Only let the system connect to new global routes for now.
778 // A similar check exists in the display manager for wifi display.
779 if (explicit && clientRecord.mTrusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800780 if (oldRouteId != null) {
781 clientRecord.mUserRecord.mHandler.obtainMessage(
782 UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
783 }
Sungsood103e562017-03-09 15:35:07 +0900784 if (routeId != null) {
Jeff Brown69b07162013-11-07 00:30:16 -0800785 clientRecord.mUserRecord.mHandler.obtainMessage(
786 UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
787 }
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900788 if (clientRecord.mGroupId != null) {
789 ClientGroup group =
790 clientRecord.mUserRecord.mClientGroupMap.get(clientRecord.mGroupId);
791 if (group != null) {
792 group.mSelectedRouteId = routeId;
793 clientRecord.mUserRecord.mHandler.obtainMessage(
794 UserHandler.MSG_UPDATE_SELECTED_ROUTE, clientRecord.mGroupId)
795 .sendToTarget();
796 }
797 }
Jeff Brown69b07162013-11-07 00:30:16 -0800798 }
799 }
800 }
801 }
802
803 private void requestSetVolumeLocked(IMediaRouterClient client,
804 String routeId, int volume) {
805 final IBinder binder = client.asBinder();
806 ClientRecord clientRecord = mAllClientRecords.get(binder);
807 if (clientRecord != null) {
808 clientRecord.mUserRecord.mHandler.obtainMessage(
809 UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
810 }
811 }
812
813 private void requestUpdateVolumeLocked(IMediaRouterClient client,
814 String routeId, int direction) {
815 final IBinder binder = client.asBinder();
816 ClientRecord clientRecord = mAllClientRecords.get(binder);
817 if (clientRecord != null) {
818 clientRecord.mUserRecord.mHandler.obtainMessage(
819 UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
820 }
821 }
822
823 private void initializeUserLocked(UserRecord userRecord) {
824 if (DEBUG) {
825 Slog.d(TAG, userRecord + ": Initialized");
826 }
827 if (userRecord.mUserId == mCurrentUserId) {
828 userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
829 }
830 }
831
832 private void disposeUserIfNeededLocked(UserRecord userRecord) {
833 // If there are no records left and the user is no longer current then go ahead
834 // and purge the user record and all of its associated state. If the user is current
835 // then leave it alone since we might be connected to a route or want to query
836 // the same route information again soon.
837 if (userRecord.mUserId != mCurrentUserId
838 && userRecord.mClientRecords.isEmpty()) {
839 if (DEBUG) {
840 Slog.d(TAG, userRecord + ": Disposed");
841 }
842 mUserRecords.remove(userRecord.mUserId);
843 // Note: User already stopped (by switchUser) so no need to send stop message here.
844 }
845 }
846
847 private void initializeClientLocked(ClientRecord clientRecord) {
848 if (DEBUG) {
849 Slog.d(TAG, clientRecord + ": Registered");
850 }
851 }
852
853 private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
854 if (DEBUG) {
855 if (died) {
856 Slog.d(TAG, clientRecord + ": Died!");
857 } else {
858 Slog.d(TAG, clientRecord + ": Unregistered");
859 }
860 }
861 if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
862 clientRecord.mUserRecord.mHandler.sendEmptyMessage(
863 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
864 }
865 clientRecord.dispose();
866 }
867
868 private boolean validatePackageName(int uid, String packageName) {
869 if (packageName != null) {
870 String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
871 if (packageNames != null) {
872 for (String n : packageNames) {
873 if (n.equals(packageName)) {
874 return true;
875 }
876 }
877 }
878 }
879 return false;
880 }
881
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900882 final class MediaRouterServiceBroadcastReceiver extends BroadcastReceiver {
883 @Override
884 public void onReceive(Context context, Intent intent) {
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900885 if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
886 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
887 synchronized (mLock) {
Sungsoo Lim07e40312020-06-11 16:01:31 +0900888 boolean wasA2dpOn = mGlobalBluetoothA2dpOn;
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900889 mActiveBluetoothDevice = btDevice;
890 mGlobalBluetoothA2dpOn = btDevice != null;
Sungsoo Lim07e40312020-06-11 16:01:31 +0900891 if (wasA2dpOn != mGlobalBluetoothA2dpOn) {
892 UserRecord userRecord = mUserRecords.get(mCurrentUserId);
893 if (userRecord != null) {
894 for (ClientRecord cr : userRecord.mClientRecords) {
895 // mSelectedRouteId will be null for BT and phone speaker.
896 if (cr.mSelectedRouteId == null) {
897 try {
898 cr.mClient.onGlobalA2dpChanged(mGlobalBluetoothA2dpOn);
899 } catch (RemoteException e) {
900 // Ignore exception
901 }
902 }
903 }
904 }
905 }
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900906 }
907 }
908 }
909 }
910
Jeff Brown69b07162013-11-07 00:30:16 -0800911 /**
912 * Information about a particular client of the media router.
913 * The contents of this object is guarded by mLock.
914 */
915 final class ClientRecord implements DeathRecipient {
916 public final UserRecord mUserRecord;
917 public final IMediaRouterClient mClient;
Sungsoob3658562017-05-22 17:10:44 +0900918 public final int mUid;
Jeff Brown69b07162013-11-07 00:30:16 -0800919 public final int mPid;
920 public final String mPackageName;
Jeff Brownaf574182013-11-14 18:16:08 -0800921 public final boolean mTrusted;
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000922 public List<String> mControlCategories;
Jeff Brown69b07162013-11-07 00:30:16 -0800923
924 public int mRouteTypes;
925 public boolean mActiveScan;
926 public String mSelectedRouteId;
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900927 public String mGroupId;
Jeff Brown69b07162013-11-07 00:30:16 -0800928
929 public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
Sungsoob3658562017-05-22 17:10:44 +0900930 int uid, int pid, String packageName, boolean trusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800931 mUserRecord = userRecord;
932 mClient = client;
Sungsoob3658562017-05-22 17:10:44 +0900933 mUid = uid;
Jeff Brown69b07162013-11-07 00:30:16 -0800934 mPid = pid;
935 mPackageName = packageName;
Jeff Brownaf574182013-11-14 18:16:08 -0800936 mTrusted = trusted;
Jeff Brown69b07162013-11-07 00:30:16 -0800937 }
938
939 public void dispose() {
940 mClient.asBinder().unlinkToDeath(this, 0);
941 }
942
943 @Override
944 public void binderDied() {
945 clientDied(this);
946 }
947
Jeff Brownaf574182013-11-14 18:16:08 -0800948 MediaRouterClientState getState() {
Sungsood103e562017-03-09 15:35:07 +0900949 return mTrusted ? mUserRecord.mRouterState : null;
Jeff Brownaf574182013-11-14 18:16:08 -0800950 }
951
Jeff Brown69b07162013-11-07 00:30:16 -0800952 public void dump(PrintWriter pw, String prefix) {
953 pw.println(prefix + this);
954
955 final String indent = prefix + " ";
Jeff Brownaf574182013-11-14 18:16:08 -0800956 pw.println(indent + "mTrusted=" + mTrusted);
Jeff Brown69b07162013-11-07 00:30:16 -0800957 pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
958 pw.println(indent + "mActiveScan=" + mActiveScan);
959 pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
960 }
961
962 @Override
963 public String toString() {
964 return "Client " + mPackageName + " (pid " + mPid + ")";
965 }
966 }
967
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900968 final class ClientGroup {
969 public String mSelectedRouteId;
970 public final List<ClientRecord> mClientRecords = new ArrayList<>();
971 }
972
Jeff Brown69b07162013-11-07 00:30:16 -0800973 /**
974 * Information about a particular user.
975 * The contents of this object is guarded by mLock.
976 */
977 final class UserRecord {
978 public final int mUserId;
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000979 public final ArrayList<ClientRecord> mClientRecords = new ArrayList<>();
Jeff Brown69b07162013-11-07 00:30:16 -0800980 public final UserHandler mHandler;
Sungsood103e562017-03-09 15:35:07 +0900981 public MediaRouterClientState mRouterState;
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900982 private final ArrayMap<String, ClientGroup> mClientGroupMap = new ArrayMap<>();
Jeff Brown69b07162013-11-07 00:30:16 -0800983
984 public UserRecord(int userId) {
985 mUserId = userId;
986 mHandler = new UserHandler(MediaRouterService.this, this);
987 }
988
989 public void dump(final PrintWriter pw, String prefix) {
990 pw.println(prefix + this);
991
992 final String indent = prefix + " ";
993 final int clientCount = mClientRecords.size();
994 if (clientCount != 0) {
995 for (int i = 0; i < clientCount; i++) {
996 mClientRecords.get(i).dump(pw, indent);
997 }
998 } else {
999 pw.println(indent + "<no clients>");
1000 }
1001
Jeff Brownaf574182013-11-14 18:16:08 -08001002 pw.println(indent + "State");
Sungsood103e562017-03-09 15:35:07 +09001003 pw.println(indent + "mRouterState=" + mRouterState);
Jeff Brownaf574182013-11-14 18:16:08 -08001004
Jeff Brown69b07162013-11-07 00:30:16 -08001005 if (!mHandler.runWithScissors(new Runnable() {
1006 @Override
1007 public void run() {
1008 mHandler.dump(pw, indent);
1009 }
1010 }, 1000)) {
1011 pw.println(indent + "<could not dump handler state>");
1012 }
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +09001013 }
1014
1015 public void addToGroup(String groupId, ClientRecord clientRecord) {
1016 ClientGroup group = mClientGroupMap.get(groupId);
1017 if (group == null) {
1018 group = new ClientGroup();
1019 mClientGroupMap.put(groupId, group);
1020 }
1021 group.mClientRecords.add(clientRecord);
1022 }
1023
1024 public void removeFromGroup(String groupId, ClientRecord clientRecord) {
1025 ClientGroup group = mClientGroupMap.get(groupId);
1026 if (group != null) {
1027 group.mClientRecords.remove(clientRecord);
1028 if (group.mClientRecords.size() == 0) {
1029 mClientGroupMap.remove(groupId);
1030 }
1031 }
1032 }
Jeff Brown69b07162013-11-07 00:30:16 -08001033
1034 @Override
1035 public String toString() {
1036 return "User " + mUserId;
1037 }
1038 }
1039
1040 /**
1041 * Media router handler
1042 * <p>
1043 * Since remote display providers are designed to be single-threaded by nature,
1044 * this class encapsulates all of the associated functionality and exports state
1045 * to the service as it evolves.
1046 * </p><p>
Jeff Brown69b07162013-11-07 00:30:16 -08001047 * This class is currently hardcoded to work with remote display providers but
1048 * it is intended to be eventually extended to support more general route providers
1049 * similar to the support library media router.
1050 * </p>
1051 */
1052 static final class UserHandler extends Handler
1053 implements RemoteDisplayProviderWatcher.Callback,
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001054 RemoteDisplayProviderProxy.Callback {
Jeff Brown69b07162013-11-07 00:30:16 -08001055 public static final int MSG_START = 1;
1056 public static final int MSG_STOP = 2;
1057 public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
1058 public static final int MSG_SELECT_ROUTE = 4;
1059 public static final int MSG_UNSELECT_ROUTE = 5;
1060 public static final int MSG_REQUEST_SET_VOLUME = 6;
1061 public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
1062 private static final int MSG_UPDATE_CLIENT_STATE = 8;
1063 private static final int MSG_CONNECTION_TIMED_OUT = 9;
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +09001064 private static final int MSG_UPDATE_SELECTED_ROUTE = 10;
Jeff Brown69b07162013-11-07 00:30:16 -08001065
1066 private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001067 private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
1068 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
1069 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4;
1070
1071 // The relative order of these constants is important and expresses progress
1072 // through the process of connecting to a route.
1073 private static final int PHASE_NOT_AVAILABLE = -1;
1074 private static final int PHASE_NOT_CONNECTED = 0;
1075 private static final int PHASE_CONNECTING = 1;
1076 private static final int PHASE_CONNECTED = 2;
Jeff Brown69b07162013-11-07 00:30:16 -08001077
1078 private final MediaRouterService mService;
1079 private final UserRecord mUserRecord;
1080 private final RemoteDisplayProviderWatcher mWatcher;
1081 private final ArrayList<ProviderRecord> mProviderRecords =
1082 new ArrayList<ProviderRecord>();
1083 private final ArrayList<IMediaRouterClient> mTempClients =
1084 new ArrayList<IMediaRouterClient>();
1085
1086 private boolean mRunning;
1087 private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
Sungsood103e562017-03-09 15:35:07 +09001088 private RouteRecord mSelectedRouteRecord;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001089 private int mConnectionPhase = PHASE_NOT_AVAILABLE;
Jeff Brown69b07162013-11-07 00:30:16 -08001090 private int mConnectionTimeoutReason;
1091 private long mConnectionTimeoutStartTime;
1092 private boolean mClientStateUpdateScheduled;
1093
1094 public UserHandler(MediaRouterService service, UserRecord userRecord) {
1095 super(Looper.getMainLooper(), null, true);
1096 mService = service;
1097 mUserRecord = userRecord;
1098 mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
1099 this, mUserRecord.mUserId);
1100 }
1101
1102 @Override
1103 public void handleMessage(Message msg) {
1104 switch (msg.what) {
1105 case MSG_START: {
1106 start();
1107 break;
1108 }
1109 case MSG_STOP: {
1110 stop();
1111 break;
1112 }
1113 case MSG_UPDATE_DISCOVERY_REQUEST: {
1114 updateDiscoveryRequest();
1115 break;
1116 }
1117 case MSG_SELECT_ROUTE: {
1118 selectRoute((String)msg.obj);
1119 break;
1120 }
1121 case MSG_UNSELECT_ROUTE: {
1122 unselectRoute((String)msg.obj);
1123 break;
1124 }
1125 case MSG_REQUEST_SET_VOLUME: {
1126 requestSetVolume((String)msg.obj, msg.arg1);
1127 break;
1128 }
1129 case MSG_REQUEST_UPDATE_VOLUME: {
1130 requestUpdateVolume((String)msg.obj, msg.arg1);
1131 break;
1132 }
1133 case MSG_UPDATE_CLIENT_STATE: {
1134 updateClientState();
1135 break;
1136 }
1137 case MSG_CONNECTION_TIMED_OUT: {
1138 connectionTimedOut();
1139 break;
1140 }
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +09001141 case MSG_UPDATE_SELECTED_ROUTE: {
1142 updateSelectedRoute((String) msg.obj);
1143 break;
1144 }
Jeff Brown69b07162013-11-07 00:30:16 -08001145 }
1146 }
1147
1148 public void dump(PrintWriter pw, String prefix) {
1149 pw.println(prefix + "Handler");
1150
1151 final String indent = prefix + " ";
1152 pw.println(indent + "mRunning=" + mRunning);
1153 pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
Sungsood103e562017-03-09 15:35:07 +09001154 pw.println(indent + "mSelectedRouteRecord=" + mSelectedRouteRecord);
Jeff Brown39ad0e52013-11-11 17:55:08 -08001155 pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
Jeff Brown69b07162013-11-07 00:30:16 -08001156 pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
1157 pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
1158 TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
1159
1160 mWatcher.dump(pw, prefix);
1161
1162 final int providerCount = mProviderRecords.size();
1163 if (providerCount != 0) {
1164 for (int i = 0; i < providerCount; i++) {
1165 mProviderRecords.get(i).dump(pw, prefix);
1166 }
1167 } else {
1168 pw.println(indent + "<no providers>");
1169 }
1170 }
1171
1172 private void start() {
1173 if (!mRunning) {
1174 mRunning = true;
1175 mWatcher.start(); // also starts all providers
1176 }
1177 }
1178
1179 private void stop() {
1180 if (mRunning) {
1181 mRunning = false;
Sungsood103e562017-03-09 15:35:07 +09001182 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001183 mWatcher.stop(); // also stops all providers
1184 }
1185 }
1186
1187 private void updateDiscoveryRequest() {
1188 int routeTypes = 0;
1189 boolean activeScan = false;
1190 synchronized (mService.mLock) {
1191 final int count = mUserRecord.mClientRecords.size();
1192 for (int i = 0; i < count; i++) {
1193 ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
1194 routeTypes |= clientRecord.mRouteTypes;
1195 activeScan |= clientRecord.mActiveScan;
1196 }
1197 }
1198
1199 final int newDiscoveryMode;
Jeff Brownaf574182013-11-14 18:16:08 -08001200 if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
Jeff Brown69b07162013-11-07 00:30:16 -08001201 if (activeScan) {
1202 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
1203 } else {
1204 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
1205 }
1206 } else {
1207 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
1208 }
1209
1210 if (mDiscoveryMode != newDiscoveryMode) {
1211 mDiscoveryMode = newDiscoveryMode;
1212 final int count = mProviderRecords.size();
1213 for (int i = 0; i < count; i++) {
1214 mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
1215 }
1216 }
1217 }
1218
1219 private void selectRoute(String routeId) {
1220 if (routeId != null
Sungsood103e562017-03-09 15:35:07 +09001221 && (mSelectedRouteRecord == null
1222 || !routeId.equals(mSelectedRouteRecord.getUniqueId()))) {
Jeff Brown69b07162013-11-07 00:30:16 -08001223 RouteRecord routeRecord = findRouteRecord(routeId);
1224 if (routeRecord != null) {
Sungsood103e562017-03-09 15:35:07 +09001225 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001226
Sungsood103e562017-03-09 15:35:07 +09001227 Slog.i(TAG, "Selected route:" + routeRecord);
1228 mSelectedRouteRecord = routeRecord;
1229 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001230 routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
1231
1232 scheduleUpdateClientState();
1233 }
1234 }
1235 }
1236
1237 private void unselectRoute(String routeId) {
1238 if (routeId != null
Sungsood103e562017-03-09 15:35:07 +09001239 && mSelectedRouteRecord != null
1240 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1241 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001242 }
1243 }
1244
Sungsood103e562017-03-09 15:35:07 +09001245 private void unselectSelectedRoute() {
1246 if (mSelectedRouteRecord != null) {
1247 Slog.i(TAG, "Unselected route:" + mSelectedRouteRecord);
1248 mSelectedRouteRecord.getProvider().setSelectedDisplay(null);
1249 mSelectedRouteRecord = null;
1250 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001251
1252 scheduleUpdateClientState();
1253 }
1254 }
1255
1256 private void requestSetVolume(String routeId, int volume) {
Sungsood103e562017-03-09 15:35:07 +09001257 if (mSelectedRouteRecord != null
1258 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1259 mSelectedRouteRecord.getProvider().setDisplayVolume(volume);
Jeff Brown69b07162013-11-07 00:30:16 -08001260 }
1261 }
1262
1263 private void requestUpdateVolume(String routeId, int direction) {
Sungsood103e562017-03-09 15:35:07 +09001264 if (mSelectedRouteRecord != null
1265 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1266 mSelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
Jeff Brown69b07162013-11-07 00:30:16 -08001267 }
1268 }
1269
1270 @Override
1271 public void addProvider(RemoteDisplayProviderProxy provider) {
1272 provider.setCallback(this);
1273 provider.setDiscoveryMode(mDiscoveryMode);
1274 provider.setSelectedDisplay(null); // just to be safe
1275
1276 ProviderRecord providerRecord = new ProviderRecord(provider);
1277 mProviderRecords.add(providerRecord);
1278 providerRecord.updateDescriptor(provider.getDisplayState());
1279
1280 scheduleUpdateClientState();
1281 }
1282
1283 @Override
1284 public void removeProvider(RemoteDisplayProviderProxy provider) {
1285 int index = findProviderRecord(provider);
1286 if (index >= 0) {
1287 ProviderRecord providerRecord = mProviderRecords.remove(index);
1288 providerRecord.updateDescriptor(null); // mark routes invalid
1289 provider.setCallback(null);
1290 provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
1291
Sungsood103e562017-03-09 15:35:07 +09001292 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001293 scheduleUpdateClientState();
1294 }
1295 }
1296
1297 @Override
1298 public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
1299 RemoteDisplayState state) {
1300 updateProvider(provider, state);
1301 }
1302
1303 private void updateProvider(RemoteDisplayProviderProxy provider,
1304 RemoteDisplayState state) {
1305 int index = findProviderRecord(provider);
1306 if (index >= 0) {
1307 ProviderRecord providerRecord = mProviderRecords.get(index);
1308 if (providerRecord.updateDescriptor(state)) {
Sungsood103e562017-03-09 15:35:07 +09001309 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001310 scheduleUpdateClientState();
1311 }
1312 }
1313 }
1314
1315 /**
Sungsood103e562017-03-09 15:35:07 +09001316 * This function is called whenever the state of the selected route may have changed.
1317 * It checks the state and updates timeouts or unselects the route as appropriate.
Jeff Brown69b07162013-11-07 00:30:16 -08001318 */
Sungsood103e562017-03-09 15:35:07 +09001319 private void checkSelectedRouteState() {
Jeff Brown69b07162013-11-07 00:30:16 -08001320 // Unschedule timeouts when the route is unselected.
Sungsood103e562017-03-09 15:35:07 +09001321 if (mSelectedRouteRecord == null) {
Jeff Brown39ad0e52013-11-11 17:55:08 -08001322 mConnectionPhase = PHASE_NOT_AVAILABLE;
Jeff Brown69b07162013-11-07 00:30:16 -08001323 updateConnectionTimeout(0);
1324 return;
1325 }
1326
1327 // Ensure that the route is still present and enabled.
Sungsood103e562017-03-09 15:35:07 +09001328 if (!mSelectedRouteRecord.isValid()
1329 || !mSelectedRouteRecord.isEnabled()) {
Jeff Brown69b07162013-11-07 00:30:16 -08001330 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
1331 return;
1332 }
1333
Jeff Brown39ad0e52013-11-11 17:55:08 -08001334 // Make sure we haven't lost our connection.
1335 final int oldPhase = mConnectionPhase;
Sungsood103e562017-03-09 15:35:07 +09001336 mConnectionPhase = getConnectionPhase(mSelectedRouteRecord.getStatus());
Jeff Brown39ad0e52013-11-11 17:55:08 -08001337 if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
1338 updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
1339 return;
1340 }
1341
Jeff Brown69b07162013-11-07 00:30:16 -08001342 // Check the route status.
Jeff Brown39ad0e52013-11-11 17:55:08 -08001343 switch (mConnectionPhase) {
1344 case PHASE_CONNECTED:
1345 if (oldPhase != PHASE_CONNECTED) {
Sungsood103e562017-03-09 15:35:07 +09001346 Slog.i(TAG, "Connected to route: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001347 }
1348 updateConnectionTimeout(0);
1349 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001350 case PHASE_CONNECTING:
1351 if (oldPhase != PHASE_CONNECTING) {
Sungsood103e562017-03-09 15:35:07 +09001352 Slog.i(TAG, "Connecting to route: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001353 }
1354 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
1355 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001356 case PHASE_NOT_CONNECTED:
Jeff Brown69b07162013-11-07 00:30:16 -08001357 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
1358 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001359 case PHASE_NOT_AVAILABLE:
Jeff Brown69b07162013-11-07 00:30:16 -08001360 default:
1361 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
1362 break;
1363 }
1364 }
1365
1366 private void updateConnectionTimeout(int reason) {
1367 if (reason != mConnectionTimeoutReason) {
1368 if (mConnectionTimeoutReason != 0) {
1369 removeMessages(MSG_CONNECTION_TIMED_OUT);
1370 }
1371 mConnectionTimeoutReason = reason;
1372 mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
1373 switch (reason) {
1374 case TIMEOUT_REASON_NOT_AVAILABLE:
Jeff Brown39ad0e52013-11-11 17:55:08 -08001375 case TIMEOUT_REASON_CONNECTION_LOST:
1376 // Route became unavailable or connection lost.
1377 // Unselect it immediately.
Jeff Brown69b07162013-11-07 00:30:16 -08001378 sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
1379 break;
1380 case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
1381 // Waiting for route to start connecting.
1382 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
1383 break;
1384 case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
1385 // Waiting for route to complete connection.
1386 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
1387 break;
1388 }
1389 }
1390 }
1391
1392 private void connectionTimedOut() {
Sungsood103e562017-03-09 15:35:07 +09001393 if (mConnectionTimeoutReason == 0 || mSelectedRouteRecord == null) {
Jeff Brown69b07162013-11-07 00:30:16 -08001394 // Shouldn't get here. There must be a bug somewhere.
1395 Log.wtf(TAG, "Handled connection timeout for no reason.");
1396 return;
1397 }
1398
1399 switch (mConnectionTimeoutReason) {
1400 case TIMEOUT_REASON_NOT_AVAILABLE:
Sungsood103e562017-03-09 15:35:07 +09001401 Slog.i(TAG, "Selected route no longer available: "
1402 + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001403 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001404 case TIMEOUT_REASON_CONNECTION_LOST:
Sungsood103e562017-03-09 15:35:07 +09001405 Slog.i(TAG, "Selected route connection lost: "
1406 + mSelectedRouteRecord);
Jeff Brown39ad0e52013-11-11 17:55:08 -08001407 break;
Jeff Brown69b07162013-11-07 00:30:16 -08001408 case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
Sungsood103e562017-03-09 15:35:07 +09001409 Slog.i(TAG, "Selected route timed out while waiting for "
Jeff Brown69b07162013-11-07 00:30:16 -08001410 + "connection attempt to begin after "
1411 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
Sungsood103e562017-03-09 15:35:07 +09001412 + " ms: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001413 break;
1414 case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
Sungsood103e562017-03-09 15:35:07 +09001415 Slog.i(TAG, "Selected route timed out while connecting after "
Jeff Brown69b07162013-11-07 00:30:16 -08001416 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
Sungsood103e562017-03-09 15:35:07 +09001417 + " ms: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001418 break;
1419 }
1420 mConnectionTimeoutReason = 0;
1421
Sungsood103e562017-03-09 15:35:07 +09001422 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001423 }
1424
1425 private void scheduleUpdateClientState() {
1426 if (!mClientStateUpdateScheduled) {
1427 mClientStateUpdateScheduled = true;
1428 sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
1429 }
1430 }
1431
1432 private void updateClientState() {
1433 mClientStateUpdateScheduled = false;
1434
Jeff Brownaf574182013-11-14 18:16:08 -08001435 // Build a new client state for trusted clients.
Sungsood103e562017-03-09 15:35:07 +09001436 MediaRouterClientState routerState = new MediaRouterClientState();
Jeff Brown69b07162013-11-07 00:30:16 -08001437 final int providerCount = mProviderRecords.size();
1438 for (int i = 0; i < providerCount; i++) {
Sungsood103e562017-03-09 15:35:07 +09001439 mProviderRecords.get(i).appendClientState(routerState);
Jeff Brown69b07162013-11-07 00:30:16 -08001440 }
Jeff Brown69b07162013-11-07 00:30:16 -08001441 try {
1442 synchronized (mService.mLock) {
1443 // Update the UserRecord.
Sungsood103e562017-03-09 15:35:07 +09001444 mUserRecord.mRouterState = routerState;
Jeff Brown69b07162013-11-07 00:30:16 -08001445
1446 // Collect all clients.
1447 final int count = mUserRecord.mClientRecords.size();
1448 for (int i = 0; i < count; i++) {
1449 mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
1450 }
1451 }
1452
1453 // Notify all clients (outside of the lock).
1454 final int count = mTempClients.size();
1455 for (int i = 0; i < count; i++) {
1456 try {
1457 mTempClients.get(i).onStateChanged();
1458 } catch (RemoteException ex) {
Sungsoob3658562017-05-22 17:10:44 +09001459 Slog.w(TAG, "Failed to call onStateChanged. Client probably died.");
Jeff Brown69b07162013-11-07 00:30:16 -08001460 }
1461 }
1462 } finally {
1463 // Clear the list in preparation for the next time.
1464 mTempClients.clear();
1465 }
1466 }
1467
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +09001468 private void updateSelectedRoute(String groupId) {
1469 try {
1470 String selectedRouteId = null;
1471 synchronized (mService.mLock) {
1472 ClientGroup group = mUserRecord.mClientGroupMap.get(groupId);
1473 if (group == null) {
1474 return;
1475 }
1476 selectedRouteId = group.mSelectedRouteId;
1477 final int count = group.mClientRecords.size();
1478 for (int i = 0; i < count; i++) {
1479 ClientRecord clientRecord = group.mClientRecords.get(i);
1480 if (!TextUtils.equals(selectedRouteId, clientRecord.mSelectedRouteId)) {
1481 mTempClients.add(clientRecord.mClient);
1482 }
1483 }
1484 }
1485
1486 final int count = mTempClients.size();
1487 for (int i = 0; i < count; i++) {
1488 try {
1489 mTempClients.get(i).onSelectedRouteChanged(selectedRouteId);
1490 } catch (RemoteException ex) {
1491 Slog.w(TAG, "Failed to call onSelectedRouteChanged. Client probably died.");
1492 }
1493 }
1494 } finally {
1495 mTempClients.clear();
1496 }
1497 }
1498
Jeff Brown69b07162013-11-07 00:30:16 -08001499 private int findProviderRecord(RemoteDisplayProviderProxy provider) {
1500 final int count = mProviderRecords.size();
1501 for (int i = 0; i < count; i++) {
1502 ProviderRecord record = mProviderRecords.get(i);
1503 if (record.getProvider() == provider) {
1504 return i;
1505 }
1506 }
1507 return -1;
1508 }
1509
1510 private RouteRecord findRouteRecord(String uniqueId) {
1511 final int count = mProviderRecords.size();
1512 for (int i = 0; i < count; i++) {
1513 RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
1514 if (record != null) {
1515 return record;
1516 }
1517 }
1518 return null;
1519 }
1520
Jeff Brown39ad0e52013-11-11 17:55:08 -08001521 private static int getConnectionPhase(int status) {
1522 switch (status) {
1523 case MediaRouter.RouteInfo.STATUS_NONE:
1524 case MediaRouter.RouteInfo.STATUS_CONNECTED:
1525 return PHASE_CONNECTED;
1526 case MediaRouter.RouteInfo.STATUS_CONNECTING:
1527 return PHASE_CONNECTING;
1528 case MediaRouter.RouteInfo.STATUS_SCANNING:
1529 case MediaRouter.RouteInfo.STATUS_AVAILABLE:
1530 return PHASE_NOT_CONNECTED;
1531 case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
1532 case MediaRouter.RouteInfo.STATUS_IN_USE:
1533 default:
1534 return PHASE_NOT_AVAILABLE;
1535 }
1536 }
1537
Jeff Brown69b07162013-11-07 00:30:16 -08001538 static final class ProviderRecord {
1539 private final RemoteDisplayProviderProxy mProvider;
1540 private final String mUniquePrefix;
1541 private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
1542 private RemoteDisplayState mDescriptor;
1543
1544 public ProviderRecord(RemoteDisplayProviderProxy provider) {
1545 mProvider = provider;
1546 mUniquePrefix = provider.getFlattenedComponentName() + ":";
1547 }
1548
1549 public RemoteDisplayProviderProxy getProvider() {
1550 return mProvider;
1551 }
1552
1553 public String getUniquePrefix() {
1554 return mUniquePrefix;
1555 }
1556
1557 public boolean updateDescriptor(RemoteDisplayState descriptor) {
1558 boolean changed = false;
1559 if (mDescriptor != descriptor) {
1560 mDescriptor = descriptor;
1561
1562 // Update all existing routes and reorder them to match
1563 // the order of their descriptors.
1564 int targetIndex = 0;
1565 if (descriptor != null) {
1566 if (descriptor.isValid()) {
1567 final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
1568 final int routeCount = routeDescriptors.size();
1569 for (int i = 0; i < routeCount; i++) {
1570 final RemoteDisplayInfo routeDescriptor =
1571 routeDescriptors.get(i);
1572 final String descriptorId = routeDescriptor.id;
1573 final int sourceIndex = findRouteByDescriptorId(descriptorId);
1574 if (sourceIndex < 0) {
1575 // Add the route to the provider.
1576 String uniqueId = assignRouteUniqueId(descriptorId);
1577 RouteRecord route =
1578 new RouteRecord(this, descriptorId, uniqueId);
1579 mRoutes.add(targetIndex++, route);
1580 route.updateDescriptor(routeDescriptor);
1581 changed = true;
1582 } else if (sourceIndex < targetIndex) {
1583 // Ignore route with duplicate id.
1584 Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
1585 + routeDescriptor);
1586 } else {
1587 // Reorder existing route within the list.
1588 RouteRecord route = mRoutes.get(sourceIndex);
1589 Collections.swap(mRoutes, sourceIndex, targetIndex++);
1590 changed |= route.updateDescriptor(routeDescriptor);
1591 }
1592 }
1593 } else {
1594 Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
1595 + mProvider.getFlattenedComponentName());
1596 }
1597 }
1598
1599 // Dispose all remaining routes that do not have matching descriptors.
1600 for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
1601 RouteRecord route = mRoutes.remove(i);
1602 route.updateDescriptor(null); // mark route invalid
1603 changed = true;
1604 }
1605 }
1606 return changed;
1607 }
1608
1609 public void appendClientState(MediaRouterClientState state) {
1610 final int routeCount = mRoutes.size();
1611 for (int i = 0; i < routeCount; i++) {
1612 state.routes.add(mRoutes.get(i).getInfo());
1613 }
1614 }
1615
1616 public RouteRecord findRouteByUniqueId(String uniqueId) {
1617 final int routeCount = mRoutes.size();
1618 for (int i = 0; i < routeCount; i++) {
1619 RouteRecord route = mRoutes.get(i);
1620 if (route.getUniqueId().equals(uniqueId)) {
1621 return route;
1622 }
1623 }
1624 return null;
1625 }
1626
1627 private int findRouteByDescriptorId(String descriptorId) {
1628 final int routeCount = mRoutes.size();
1629 for (int i = 0; i < routeCount; i++) {
1630 RouteRecord route = mRoutes.get(i);
1631 if (route.getDescriptorId().equals(descriptorId)) {
1632 return i;
1633 }
1634 }
1635 return -1;
1636 }
1637
1638 public void dump(PrintWriter pw, String prefix) {
1639 pw.println(prefix + this);
1640
1641 final String indent = prefix + " ";
1642 mProvider.dump(pw, indent);
1643
1644 final int routeCount = mRoutes.size();
1645 if (routeCount != 0) {
1646 for (int i = 0; i < routeCount; i++) {
1647 mRoutes.get(i).dump(pw, indent);
1648 }
1649 } else {
1650 pw.println(indent + "<no routes>");
1651 }
1652 }
1653
1654 @Override
1655 public String toString() {
1656 return "Provider " + mProvider.getFlattenedComponentName();
1657 }
1658
1659 private String assignRouteUniqueId(String descriptorId) {
1660 return mUniquePrefix + descriptorId;
1661 }
1662 }
1663
1664 static final class RouteRecord {
1665 private final ProviderRecord mProviderRecord;
1666 private final String mDescriptorId;
1667 private final MediaRouterClientState.RouteInfo mMutableInfo;
1668 private MediaRouterClientState.RouteInfo mImmutableInfo;
1669 private RemoteDisplayInfo mDescriptor;
1670
1671 public RouteRecord(ProviderRecord providerRecord,
1672 String descriptorId, String uniqueId) {
1673 mProviderRecord = providerRecord;
1674 mDescriptorId = descriptorId;
1675 mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
1676 }
1677
1678 public RemoteDisplayProviderProxy getProvider() {
1679 return mProviderRecord.getProvider();
1680 }
1681
1682 public ProviderRecord getProviderRecord() {
1683 return mProviderRecord;
1684 }
1685
1686 public String getDescriptorId() {
1687 return mDescriptorId;
1688 }
1689
1690 public String getUniqueId() {
1691 return mMutableInfo.id;
1692 }
1693
1694 public MediaRouterClientState.RouteInfo getInfo() {
1695 if (mImmutableInfo == null) {
1696 mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
1697 }
1698 return mImmutableInfo;
1699 }
1700
1701 public boolean isValid() {
1702 return mDescriptor != null;
1703 }
1704
1705 public boolean isEnabled() {
1706 return mMutableInfo.enabled;
1707 }
1708
1709 public int getStatus() {
1710 return mMutableInfo.statusCode;
1711 }
1712
1713 public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
1714 boolean changed = false;
1715 if (mDescriptor != descriptor) {
1716 mDescriptor = descriptor;
1717 if (descriptor != null) {
1718 final String name = computeName(descriptor);
Kenny Roote6585b32013-12-13 12:00:26 -08001719 if (!Objects.equals(mMutableInfo.name, name)) {
Jeff Brown69b07162013-11-07 00:30:16 -08001720 mMutableInfo.name = name;
1721 changed = true;
1722 }
1723 final String description = computeDescription(descriptor);
Kenny Roote6585b32013-12-13 12:00:26 -08001724 if (!Objects.equals(mMutableInfo.description, description)) {
Jeff Brown69b07162013-11-07 00:30:16 -08001725 mMutableInfo.description = description;
1726 changed = true;
1727 }
1728 final int supportedTypes = computeSupportedTypes(descriptor);
1729 if (mMutableInfo.supportedTypes != supportedTypes) {
1730 mMutableInfo.supportedTypes = supportedTypes;
1731 changed = true;
1732 }
1733 final boolean enabled = computeEnabled(descriptor);
1734 if (mMutableInfo.enabled != enabled) {
1735 mMutableInfo.enabled = enabled;
1736 changed = true;
1737 }
1738 final int statusCode = computeStatusCode(descriptor);
1739 if (mMutableInfo.statusCode != statusCode) {
1740 mMutableInfo.statusCode = statusCode;
1741 changed = true;
1742 }
1743 final int playbackType = computePlaybackType(descriptor);
1744 if (mMutableInfo.playbackType != playbackType) {
1745 mMutableInfo.playbackType = playbackType;
1746 changed = true;
1747 }
1748 final int playbackStream = computePlaybackStream(descriptor);
1749 if (mMutableInfo.playbackStream != playbackStream) {
1750 mMutableInfo.playbackStream = playbackStream;
1751 changed = true;
1752 }
1753 final int volume = computeVolume(descriptor);
1754 if (mMutableInfo.volume != volume) {
1755 mMutableInfo.volume = volume;
1756 changed = true;
1757 }
1758 final int volumeMax = computeVolumeMax(descriptor);
1759 if (mMutableInfo.volumeMax != volumeMax) {
1760 mMutableInfo.volumeMax = volumeMax;
1761 changed = true;
1762 }
1763 final int volumeHandling = computeVolumeHandling(descriptor);
1764 if (mMutableInfo.volumeHandling != volumeHandling) {
1765 mMutableInfo.volumeHandling = volumeHandling;
1766 changed = true;
1767 }
1768 final int presentationDisplayId = computePresentationDisplayId(descriptor);
1769 if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
1770 mMutableInfo.presentationDisplayId = presentationDisplayId;
1771 changed = true;
1772 }
1773 }
1774 }
1775 if (changed) {
1776 mImmutableInfo = null;
1777 }
1778 return changed;
1779 }
1780
1781 public void dump(PrintWriter pw, String prefix) {
1782 pw.println(prefix + this);
1783
1784 final String indent = prefix + " ";
1785 pw.println(indent + "mMutableInfo=" + mMutableInfo);
1786 pw.println(indent + "mDescriptorId=" + mDescriptorId);
1787 pw.println(indent + "mDescriptor=" + mDescriptor);
1788 }
1789
1790 @Override
1791 public String toString() {
1792 return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
1793 }
1794
1795 private static String computeName(RemoteDisplayInfo descriptor) {
1796 // Note that isValid() already ensures the name is non-empty.
1797 return descriptor.name;
1798 }
1799
1800 private static String computeDescription(RemoteDisplayInfo descriptor) {
1801 final String description = descriptor.description;
1802 return TextUtils.isEmpty(description) ? null : description;
1803 }
1804
1805 private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
1806 return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
1807 | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
1808 | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
1809 }
1810
1811 private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
1812 switch (descriptor.status) {
1813 case RemoteDisplayInfo.STATUS_CONNECTED:
1814 case RemoteDisplayInfo.STATUS_CONNECTING:
1815 case RemoteDisplayInfo.STATUS_AVAILABLE:
1816 return true;
1817 default:
1818 return false;
1819 }
1820 }
1821
1822 private static int computeStatusCode(RemoteDisplayInfo descriptor) {
1823 switch (descriptor.status) {
1824 case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
1825 return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
1826 case RemoteDisplayInfo.STATUS_AVAILABLE:
1827 return MediaRouter.RouteInfo.STATUS_AVAILABLE;
1828 case RemoteDisplayInfo.STATUS_IN_USE:
1829 return MediaRouter.RouteInfo.STATUS_IN_USE;
1830 case RemoteDisplayInfo.STATUS_CONNECTING:
1831 return MediaRouter.RouteInfo.STATUS_CONNECTING;
1832 case RemoteDisplayInfo.STATUS_CONNECTED:
1833 return MediaRouter.RouteInfo.STATUS_CONNECTED;
1834 default:
1835 return MediaRouter.RouteInfo.STATUS_NONE;
1836 }
1837 }
1838
1839 private static int computePlaybackType(RemoteDisplayInfo descriptor) {
1840 return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
1841 }
1842
1843 private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
1844 return AudioSystem.STREAM_MUSIC;
1845 }
1846
1847 private static int computeVolume(RemoteDisplayInfo descriptor) {
1848 final int volume = descriptor.volume;
1849 final int volumeMax = descriptor.volumeMax;
1850 if (volume < 0) {
1851 return 0;
1852 } else if (volume > volumeMax) {
1853 return volumeMax;
1854 }
1855 return volume;
1856 }
1857
1858 private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
1859 final int volumeMax = descriptor.volumeMax;
1860 return volumeMax > 0 ? volumeMax : 0;
1861 }
1862
1863 private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
1864 final int volumeHandling = descriptor.volumeHandling;
1865 switch (volumeHandling) {
1866 case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
1867 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
1868 case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
1869 default:
1870 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
1871 }
1872 }
1873
1874 private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
1875 // The MediaRouter class validates that the id corresponds to an extant
1876 // presentation display. So all we do here is canonicalize the null case.
1877 final int displayId = descriptor.presentationDisplayId;
1878 return displayId < 0 ? -1 : displayId;
1879 }
1880 }
1881 }
Jeff Brown69b07162013-11-07 00:30:16 -08001882}