blob: 9336af427452ece0bb0f8d28a1ea6360d683f9b8 [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;
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +090033import android.media.IMediaRouter2Client;
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;
42import android.os.Binder;
43import android.os.Handler;
44import android.os.IBinder;
45import android.os.Looper;
46import android.os.Message;
47import android.os.RemoteException;
Sungsoob3658562017-05-22 17:10:44 +090048import android.os.ServiceManager;
Jeff Brown69b07162013-11-07 00:30:16 -080049import android.os.SystemClock;
Sungsoob3658562017-05-22 17:10:44 +090050import android.os.UserHandle;
Jeff Brown69b07162013-11-07 00:30:16 -080051import android.text.TextUtils;
52import android.util.ArrayMap;
Sungsoob3658562017-05-22 17:10:44 +090053import android.util.IntArray;
Jeff Brown69b07162013-11-07 00:30:16 -080054import android.util.Log;
55import android.util.Slog;
56import android.util.SparseArray;
57import android.util.TimeUtils;
58
Kyunglyul Hyun1c8188f2019-02-01 10:50:11 +090059import com.android.internal.util.DumpUtils;
60import com.android.server.Watchdog;
61
Jeff Brown69b07162013-11-07 00:30:16 -080062import java.io.FileDescriptor;
63import java.io.PrintWriter;
64import java.util.ArrayList;
65import java.util.Collections;
66import java.util.List;
Kenny Roote6585b32013-12-13 12:00:26 -080067import java.util.Objects;
Jeff Brown69b07162013-11-07 00:30:16 -080068
69/**
70 * Provides a mechanism for discovering media routes and manages media playback
71 * behalf of applications.
72 * <p>
73 * Currently supports discovering remote displays via remote display provider
74 * services that have been registered by applications.
75 * </p>
76 */
77public final class MediaRouterService extends IMediaRouterService.Stub
78 implements Watchdog.Monitor {
79 private static final String TAG = "MediaRouterService";
80 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
81
82 /**
83 * Timeout in milliseconds for a selected route to transition from a
84 * disconnected state to a connecting state. If we don't observe any
85 * progress within this interval, then we will give up and unselect the route.
86 */
87 static final long CONNECTING_TIMEOUT = 5000;
88
89 /**
90 * Timeout in milliseconds for a selected route to transition from a
91 * connecting state to a connected state. If we don't observe any
92 * progress within this interval, then we will give up and unselect the route.
93 */
94 static final long CONNECTED_TIMEOUT = 60000;
95
96 private final Context mContext;
97
98 // State guarded by mLock.
99 private final Object mLock = new Object();
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900100 private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
101 private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>();
Jeff Brown69b07162013-11-07 00:30:16 -0800102 private int mCurrentUserId = -1;
Sungsoob3658562017-05-22 17:10:44 +0900103 private final IAudioService mAudioService;
Sungsoo Lim875e6972017-11-03 02:22:35 +0000104 private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
105 private final Handler mHandler = new Handler();
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900106 private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
107 private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
Jeff Brown69b07162013-11-07 00:30:16 -0800108
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900109 private final BroadcastReceiver mReceiver = new MediaRouterServiceBroadcastReceiver();
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900110 BluetoothDevice mActiveBluetoothDevice;
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900111 int mAudioRouteMainType = AudioRoutesInfo.MAIN_SPEAKER;
112 boolean mGlobalBluetoothA2dpOn = false;
113
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900114 //TODO: remove this when it's finished
115 private final MediaRouter2ServiceImpl mService2;
116
Jeff Brown69b07162013-11-07 00:30:16 -0800117 public MediaRouterService(Context context) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900118 mService2 = new MediaRouter2ServiceImpl(context);
119
Jeff Brown69b07162013-11-07 00:30:16 -0800120 mContext = context;
121 Watchdog.getInstance().addMonitor(this);
Sungsoob3658562017-05-22 17:10:44 +0900122
123 mAudioService = IAudioService.Stub.asInterface(
124 ServiceManager.getService(Context.AUDIO_SERVICE));
Sungsoo Limd4d11872019-04-04 02:19:52 +0900125 mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(context);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000126 mAudioPlayerStateMonitor.registerListener(
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900127 new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000128 static final long WAIT_MS = 500;
129 final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
130 @Override
131 public void run() {
132 restoreBluetoothA2dp();
133 }
134 };
135
Sungsoob3658562017-05-22 17:10:44 +0900136 @Override
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900137 public void onAudioPlayerActiveStateChanged(
138 @NonNull AudioPlaybackConfiguration config, boolean isRemoved) {
139 final boolean active = !isRemoved && config.isActive();
140 final int pii = config.getPlayerInterfaceId();
141 final int uid = config.getClientUid();
142
143 final int idx = mActivePlayerMinPriorityQueue.indexOf(pii);
144 // Keep the latest active player and its uid at the end of the queue.
145 if (idx >= 0) {
146 mActivePlayerMinPriorityQueue.remove(idx);
147 mActivePlayerUidMinPriorityQueue.remove(idx);
148 }
149
Sungsoo Lim875e6972017-11-03 02:22:35 +0000150 int restoreUid = -1;
Sungsoob3658562017-05-22 17:10:44 +0900151 if (active) {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900152 mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId());
153 mActivePlayerUidMinPriorityQueue.add(uid);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000154 restoreUid = uid;
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900155 } else if (mActivePlayerUidMinPriorityQueue.size() > 0) {
156 restoreUid = mActivePlayerUidMinPriorityQueue.get(
157 mActivePlayerUidMinPriorityQueue.size() - 1);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000158 }
159
160 mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
161 if (restoreUid >= 0) {
162 restoreRoute(restoreUid);
163 if (DEBUG) {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900164 Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
165 + ", active=" + active + ", restoreUid=" + restoreUid);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000166 }
167 } else {
168 mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
169 if (DEBUG) {
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900170 Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900171 + ", active=" + active + ", delaying");
Sungsoob3658562017-05-22 17:10:44 +0900172 }
173 }
174 }
Sungsoo Lim875e6972017-11-03 02:22:35 +0000175 }, mHandler);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000176
Sungsoob3658562017-05-22 17:10:44 +0900177 AudioRoutesInfo audioRoutes = null;
178 try {
179 audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
180 @Override
181 public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
Sungsoo Lim76512a32017-08-24 10:25:06 +0900182 synchronized (mLock) {
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900183 if (newRoutes.mainType != mAudioRouteMainType) {
Sungsoo Lim76512a32017-08-24 10:25:06 +0900184 if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
185 | AudioRoutesInfo.MAIN_HEADPHONES
186 | AudioRoutesInfo.MAIN_USB)) == 0) {
187 // headset was plugged out.
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900188 mGlobalBluetoothA2dpOn = (newRoutes.bluetoothName != null
189 || mActiveBluetoothDevice != null);
Sungsoo Lim76512a32017-08-24 10:25:06 +0900190 } else {
191 // headset was plugged in.
192 mGlobalBluetoothA2dpOn = false;
193 }
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900194 mAudioRouteMainType = newRoutes.mainType;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900195 }
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900196 // The new audio routes info could be delivered with several seconds delay.
197 // In order to avoid such delay, Bluetooth device info will be updated
198 // via MediaRouterServiceBroadcastReceiver.
Sungsoo Lim76512a32017-08-24 10:25:06 +0900199 }
Sungsoob3658562017-05-22 17:10:44 +0900200 }
201 });
202 } catch (RemoteException e) {
203 Slog.w(TAG, "RemoteException in the audio service.");
204 }
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900205
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900206 IntentFilter intentFilter = new IntentFilter(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900207 context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
Jeff Brown69b07162013-11-07 00:30:16 -0800208 }
209
210 public void systemRunning() {
211 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
212 mContext.registerReceiver(new BroadcastReceiver() {
213 @Override
214 public void onReceive(Context context, Intent intent) {
215 if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
216 switchUser();
217 }
218 }
219 }, filter);
220
221 switchUser();
222 }
223
224 @Override
225 public void monitor() {
226 synchronized (mLock) { /* check for deadlock */ }
227 }
228
229 // Binder call
230 @Override
231 public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
232 if (client == null) {
233 throw new IllegalArgumentException("client must not be null");
234 }
235
236 final int uid = Binder.getCallingUid();
237 if (!validatePackageName(uid, packageName)) {
238 throw new SecurityException("packageName must match the calling uid");
239 }
240
241 final int pid = Binder.getCallingPid();
242 final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
243 false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName);
Jeff Brownaf574182013-11-14 18:16:08 -0800244 final boolean trusted = mContext.checkCallingOrSelfPermission(
245 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) ==
246 PackageManager.PERMISSION_GRANTED;
Jeff Brown69b07162013-11-07 00:30:16 -0800247 final long token = Binder.clearCallingIdentity();
248 try {
249 synchronized (mLock) {
Sungsoob3658562017-05-22 17:10:44 +0900250 registerClientLocked(client, uid, pid, packageName, resolvedUserId, trusted);
Jeff Brown69b07162013-11-07 00:30:16 -0800251 }
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900252 mService2.registerClient(client, packageName);
Jeff Brown69b07162013-11-07 00:30:16 -0800253 } finally {
254 Binder.restoreCallingIdentity(token);
255 }
256 }
257
258 // Binder call
259 @Override
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900260 public void registerClientGroupId(IMediaRouterClient client, String groupId) {
261 if (client == null) {
262 throw new NullPointerException("client must not be null");
263 }
264 if (mContext.checkCallingOrSelfPermission(
265 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
266 != PackageManager.PERMISSION_GRANTED) {
267 Log.w(TAG, "Ignoring client group request because "
268 + "the client doesn't have the CONFIGURE_WIFI_DISPLAY permission.");
269 return;
270 }
271 final long token = Binder.clearCallingIdentity();
272 try {
273 synchronized (mLock) {
274 registerClientGroupIdLocked(client, groupId);
275 }
276 } finally {
277 Binder.restoreCallingIdentity(token);
278 }
279 }
280
281 // Binder call
282 @Override
Jeff Brown69b07162013-11-07 00:30:16 -0800283 public void unregisterClient(IMediaRouterClient client) {
284 if (client == null) {
285 throw new IllegalArgumentException("client must not be null");
286 }
287
288 final long token = Binder.clearCallingIdentity();
289 try {
290 synchronized (mLock) {
291 unregisterClientLocked(client, false);
292 }
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900293 mService2.unregisterClient(client);
Jeff Brown69b07162013-11-07 00:30:16 -0800294 } finally {
295 Binder.restoreCallingIdentity(token);
296 }
297 }
298
299 // Binder call
300 @Override
301 public MediaRouterClientState getState(IMediaRouterClient client) {
302 if (client == null) {
303 throw new IllegalArgumentException("client must not be null");
304 }
305
306 final long token = Binder.clearCallingIdentity();
307 try {
308 synchronized (mLock) {
309 return getStateLocked(client);
310 }
311 } finally {
312 Binder.restoreCallingIdentity(token);
313 }
314 }
315
316 // Binder call
317 @Override
Sungsoob3658562017-05-22 17:10:44 +0900318 public boolean isPlaybackActive(IMediaRouterClient client) {
319 if (client == null) {
320 throw new IllegalArgumentException("client must not be null");
321 }
322
323 final long token = Binder.clearCallingIdentity();
324 try {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000325 ClientRecord clientRecord;
Sungsoob3658562017-05-22 17:10:44 +0900326 synchronized (mLock) {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000327 clientRecord = mAllClientRecords.get(client.asBinder());
Sungsoob3658562017-05-22 17:10:44 +0900328 }
Sungsoo Lim875e6972017-11-03 02:22:35 +0000329 if (clientRecord != null) {
330 return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
331 }
332 return false;
Sungsoob3658562017-05-22 17:10:44 +0900333 } finally {
334 Binder.restoreCallingIdentity(token);
335 }
336 }
337
338 // Binder call
339 @Override
Jeff Brown69b07162013-11-07 00:30:16 -0800340 public void setDiscoveryRequest(IMediaRouterClient client,
341 int routeTypes, boolean activeScan) {
342 if (client == null) {
343 throw new IllegalArgumentException("client must not be null");
344 }
345
346 final long token = Binder.clearCallingIdentity();
347 try {
348 synchronized (mLock) {
349 setDiscoveryRequestLocked(client, routeTypes, activeScan);
350 }
351 } finally {
352 Binder.restoreCallingIdentity(token);
353 }
354 }
355
356 // Binder call
357 // A null routeId means that the client wants to unselect its current route.
358 // The explicit flag indicates whether the change was explicitly requested by the
359 // user or the application which may cause changes to propagate out to the rest
Sungsood103e562017-03-09 15:35:07 +0900360 // of the system. Should be false when the change is in response to a new
Jeff Brown69b07162013-11-07 00:30:16 -0800361 // selected route or a default selection.
362 @Override
363 public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
364 if (client == null) {
365 throw new IllegalArgumentException("client must not be null");
366 }
367
368 final long token = Binder.clearCallingIdentity();
369 try {
370 synchronized (mLock) {
371 setSelectedRouteLocked(client, routeId, explicit);
372 }
373 } finally {
374 Binder.restoreCallingIdentity(token);
375 }
376 }
377
378 // Binder call
379 @Override
380 public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
381 if (client == null) {
382 throw new IllegalArgumentException("client must not be null");
383 }
384 if (routeId == null) {
385 throw new IllegalArgumentException("routeId must not be null");
386 }
387
388 final long token = Binder.clearCallingIdentity();
389 try {
390 synchronized (mLock) {
391 requestSetVolumeLocked(client, routeId, volume);
392 }
393 } finally {
394 Binder.restoreCallingIdentity(token);
395 }
396 }
397
398 // Binder call
399 @Override
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900400 public void setControlCategories(IMediaRouterClient client, List<String> controlCategories) {
401 mService2.setControlCategories(client, controlCategories);
402 }
403
404 // Binder call
405 @Override
Jeff Brown69b07162013-11-07 00:30:16 -0800406 public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
407 if (client == null) {
408 throw new IllegalArgumentException("client must not be null");
409 }
410 if (routeId == null) {
411 throw new IllegalArgumentException("routeId must not be null");
412 }
413
414 final long token = Binder.clearCallingIdentity();
415 try {
416 synchronized (mLock) {
417 requestUpdateVolumeLocked(client, routeId, direction);
418 }
419 } finally {
420 Binder.restoreCallingIdentity(token);
421 }
422 }
423
424 // Binder call
425 @Override
426 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600427 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Jeff Brown69b07162013-11-07 00:30:16 -0800428
429 pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
430 pw.println();
431 pw.println("Global state");
432 pw.println(" mCurrentUserId=" + mCurrentUserId);
433
434 synchronized (mLock) {
435 final int count = mUserRecords.size();
436 for (int i = 0; i < count; i++) {
437 UserRecord userRecord = mUserRecords.valueAt(i);
438 pw.println();
439 userRecord.dump(pw, "");
440 }
441 }
442 }
443
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000444 // Binder call
445 @Override
Hyundo Moon5736a612019-11-19 15:08:32 +0900446 public List<MediaRoute2Info> getSystemRoutes() {
447 return mService2.getSystemRoutes();
448 }
449
450 // Binder call
451 @Override
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900452 public void registerClient2(IMediaRouter2Client client, String packageName) {
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900453 final int uid = Binder.getCallingUid();
454 if (!validatePackageName(uid, packageName)) {
455 throw new SecurityException("packageName must match the calling uid");
456 }
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900457 mService2.registerClient(client, packageName);
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900458 }
459
460 // Binder call
461 @Override
462 public void unregisterClient2(IMediaRouter2Client client) {
463 mService2.unregisterClient(client);
464 }
465
466 // Binder call
467 @Override
Hyundo Moon4140fee2019-11-08 14:47:50 +0900468 public void requestSelectRoute2(IMediaRouter2Client client, MediaRoute2Info route) {
469 mService2.requestSelectRoute2(client, route);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900470 }
471
472 // Binder call
473 @Override
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900474 public void sendControlRequest(IMediaRouter2Client client, MediaRoute2Info route,
475 Intent request) {
476 mService2.sendControlRequest(client, route, request);
477 }
478
479 // Binder call
480 @Override
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900481 public void registerManager(IMediaRouter2Manager manager, String packageName) {
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000482 final int uid = Binder.getCallingUid();
483 if (!validatePackageName(uid, packageName)) {
484 throw new SecurityException("packageName must match the calling uid");
485 }
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900486 mService2.registerManager(manager, packageName);
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000487 }
488
489 // Binder call
490 @Override
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900491 public void unregisterManager(IMediaRouter2Manager manager) {
492 mService2.unregisterManager(manager);
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000493 }
494
495 // Binder call
496 @Override
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900497 public void selectClientRoute2(IMediaRouter2Manager manager,
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900498 String packageName, MediaRoute2Info route) {
499 mService2.selectClientRoute2(manager, packageName, route);
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000500 }
501
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900502 // Binder call
503 @Override
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900504 public void setControlCategories2(IMediaRouter2Client client, List<String> categories) {
505 mService2.setControlCategories2(client, categories);
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900506 }
507
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900508 // Binder call
509 @Override
510 public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
511 mService2.requestSetVolume2(client, route, volume);
512 }
513
514 // Binder call
515 @Override
516 public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
517 mService2.requestUpdateVolume2(client, route, delta);
518 }
519
520 // Binder call
521 @Override
522 public void requestSetVolume2Manager(IMediaRouter2Manager manager,
523 MediaRoute2Info route, int volume) {
524 mService2.requestSetVolume2Manager(manager, route, volume);
525 }
526
527 // Binder call
528 @Override
529 public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
530 MediaRoute2Info route, int delta) {
531 mService2.requestUpdateVolume2Manager(manager, route, delta);
532 }
533
Sungsoob3658562017-05-22 17:10:44 +0900534 void restoreBluetoothA2dp() {
535 try {
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900536 boolean a2dpOn;
537 BluetoothDevice btDevice;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900538 synchronized (mLock) {
539 a2dpOn = mGlobalBluetoothA2dpOn;
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900540 btDevice = mActiveBluetoothDevice;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900541 }
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900542 // We don't need to change a2dp status when bluetooth is not connected.
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900543 if (btDevice != null) {
Sungsoo Limc3b52952018-06-11 12:55:25 +0900544 if (DEBUG) {
545 Slog.d(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
546 }
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900547 mAudioService.setBluetoothA2dpOn(a2dpOn);
548 }
Sungsoob3658562017-05-22 17:10:44 +0900549 } catch (RemoteException e) {
550 Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
551 }
552 }
553
554 void restoreRoute(int uid) {
555 ClientRecord clientRecord = null;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900556 synchronized (mLock) {
557 UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
558 if (userRecord != null && userRecord.mClientRecords != null) {
559 for (ClientRecord cr : userRecord.mClientRecords) {
560 if (validatePackageName(uid, cr.mPackageName)) {
561 clientRecord = cr;
562 break;
563 }
Sungsoob3658562017-05-22 17:10:44 +0900564 }
565 }
566 }
567 if (clientRecord != null) {
568 try {
569 clientRecord.mClient.onRestoreRoute();
570 } catch (RemoteException e) {
571 Slog.w(TAG, "Failed to call onRestoreRoute. Client probably died.");
572 }
573 } else {
574 restoreBluetoothA2dp();
575 }
576 }
577
Jeff Brown69b07162013-11-07 00:30:16 -0800578 void switchUser() {
579 synchronized (mLock) {
580 int userId = ActivityManager.getCurrentUser();
581 if (mCurrentUserId != userId) {
582 final int oldUserId = mCurrentUserId;
583 mCurrentUserId = userId; // do this first
584
585 UserRecord oldUser = mUserRecords.get(oldUserId);
586 if (oldUser != null) {
587 oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
588 disposeUserIfNeededLocked(oldUser); // since no longer current user
589 }
590
591 UserRecord newUser = mUserRecords.get(userId);
592 if (newUser != null) {
593 newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
594 }
595 }
596 }
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900597 mService2.switchUser();
Jeff Brown69b07162013-11-07 00:30:16 -0800598 }
599
600 void clientDied(ClientRecord clientRecord) {
601 synchronized (mLock) {
602 unregisterClientLocked(clientRecord.mClient, true);
603 }
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900604 mService2.unregisterClient(clientRecord.mClient);
Jeff Brown69b07162013-11-07 00:30:16 -0800605 }
606
607 private void registerClientLocked(IMediaRouterClient client,
Sungsoob3658562017-05-22 17:10:44 +0900608 int uid, int pid, String packageName, int userId, boolean trusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800609 final IBinder binder = client.asBinder();
610 ClientRecord clientRecord = mAllClientRecords.get(binder);
611 if (clientRecord == null) {
612 boolean newUser = false;
613 UserRecord userRecord = mUserRecords.get(userId);
614 if (userRecord == null) {
615 userRecord = new UserRecord(userId);
616 newUser = true;
617 }
Sungsoob3658562017-05-22 17:10:44 +0900618 clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted);
Jeff Brown69b07162013-11-07 00:30:16 -0800619 try {
620 binder.linkToDeath(clientRecord, 0);
621 } catch (RemoteException ex) {
622 throw new RuntimeException("Media router client died prematurely.", ex);
623 }
624
625 if (newUser) {
626 mUserRecords.put(userId, userRecord);
627 initializeUserLocked(userRecord);
628 }
629
630 userRecord.mClientRecords.add(clientRecord);
631 mAllClientRecords.put(binder, clientRecord);
632 initializeClientLocked(clientRecord);
633 }
634 }
635
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900636 private void registerClientGroupIdLocked(IMediaRouterClient client, String groupId) {
637 final IBinder binder = client.asBinder();
638 ClientRecord clientRecord = mAllClientRecords.get(binder);
639 if (clientRecord == null) {
640 Log.w(TAG, "Ignoring group id register request of a unregistered client.");
641 return;
642 }
643 if (TextUtils.equals(clientRecord.mGroupId, groupId)) {
644 return;
645 }
646 UserRecord userRecord = clientRecord.mUserRecord;
647 if (clientRecord.mGroupId != null) {
648 userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord);
649 }
650 clientRecord.mGroupId = groupId;
651 if (groupId != null) {
652 userRecord.addToGroup(groupId, clientRecord);
653 userRecord.mHandler.obtainMessage(UserHandler.MSG_UPDATE_SELECTED_ROUTE, groupId)
654 .sendToTarget();
655 }
656 }
657
Jeff Brown69b07162013-11-07 00:30:16 -0800658 private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
659 ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
660 if (clientRecord != null) {
661 UserRecord userRecord = clientRecord.mUserRecord;
662 userRecord.mClientRecords.remove(clientRecord);
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900663 if (clientRecord.mGroupId != null) {
664 userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord);
665 clientRecord.mGroupId = null;
666 }
Jeff Brown69b07162013-11-07 00:30:16 -0800667 disposeClientLocked(clientRecord, died);
668 disposeUserIfNeededLocked(userRecord); // since client removed from user
669 }
670 }
671
672 private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
673 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
674 if (clientRecord != null) {
Jeff Brownaf574182013-11-14 18:16:08 -0800675 return clientRecord.getState();
Jeff Brown69b07162013-11-07 00:30:16 -0800676 }
677 return null;
678 }
679
680 private void setDiscoveryRequestLocked(IMediaRouterClient client,
681 int routeTypes, boolean activeScan) {
682 final IBinder binder = client.asBinder();
683 ClientRecord clientRecord = mAllClientRecords.get(binder);
684 if (clientRecord != null) {
Jeff Brownaf574182013-11-14 18:16:08 -0800685 // Only let the system discover remote display routes for now.
686 if (!clientRecord.mTrusted) {
687 routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
688 }
689
Jeff Brown69b07162013-11-07 00:30:16 -0800690 if (clientRecord.mRouteTypes != routeTypes
691 || clientRecord.mActiveScan != activeScan) {
692 if (DEBUG) {
693 Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
694 + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
695 }
696 clientRecord.mRouteTypes = routeTypes;
697 clientRecord.mActiveScan = activeScan;
698 clientRecord.mUserRecord.mHandler.sendEmptyMessage(
699 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
700 }
701 }
702 }
703
704 private void setSelectedRouteLocked(IMediaRouterClient client,
705 String routeId, boolean explicit) {
706 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
707 if (clientRecord != null) {
708 final String oldRouteId = clientRecord.mSelectedRouteId;
Kenny Roote6585b32013-12-13 12:00:26 -0800709 if (!Objects.equals(routeId, oldRouteId)) {
Jeff Brown69b07162013-11-07 00:30:16 -0800710 if (DEBUG) {
711 Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
712 + ", oldRouteId=" + oldRouteId
713 + ", explicit=" + explicit);
714 }
715
716 clientRecord.mSelectedRouteId = routeId;
Sungsood103e562017-03-09 15:35:07 +0900717 // Only let the system connect to new global routes for now.
718 // A similar check exists in the display manager for wifi display.
719 if (explicit && clientRecord.mTrusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800720 if (oldRouteId != null) {
721 clientRecord.mUserRecord.mHandler.obtainMessage(
722 UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
723 }
Sungsood103e562017-03-09 15:35:07 +0900724 if (routeId != null) {
Jeff Brown69b07162013-11-07 00:30:16 -0800725 clientRecord.mUserRecord.mHandler.obtainMessage(
726 UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
727 }
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900728 if (clientRecord.mGroupId != null) {
729 ClientGroup group =
730 clientRecord.mUserRecord.mClientGroupMap.get(clientRecord.mGroupId);
731 if (group != null) {
732 group.mSelectedRouteId = routeId;
733 clientRecord.mUserRecord.mHandler.obtainMessage(
734 UserHandler.MSG_UPDATE_SELECTED_ROUTE, clientRecord.mGroupId)
735 .sendToTarget();
736 }
737 }
Jeff Brown69b07162013-11-07 00:30:16 -0800738 }
739 }
740 }
741 }
742
743 private void requestSetVolumeLocked(IMediaRouterClient client,
744 String routeId, int volume) {
745 final IBinder binder = client.asBinder();
746 ClientRecord clientRecord = mAllClientRecords.get(binder);
747 if (clientRecord != null) {
748 clientRecord.mUserRecord.mHandler.obtainMessage(
749 UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
750 }
751 }
752
753 private void requestUpdateVolumeLocked(IMediaRouterClient client,
754 String routeId, int direction) {
755 final IBinder binder = client.asBinder();
756 ClientRecord clientRecord = mAllClientRecords.get(binder);
757 if (clientRecord != null) {
758 clientRecord.mUserRecord.mHandler.obtainMessage(
759 UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
760 }
761 }
762
763 private void initializeUserLocked(UserRecord userRecord) {
764 if (DEBUG) {
765 Slog.d(TAG, userRecord + ": Initialized");
766 }
767 if (userRecord.mUserId == mCurrentUserId) {
768 userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
769 }
770 }
771
772 private void disposeUserIfNeededLocked(UserRecord userRecord) {
773 // If there are no records left and the user is no longer current then go ahead
774 // and purge the user record and all of its associated state. If the user is current
775 // then leave it alone since we might be connected to a route or want to query
776 // the same route information again soon.
777 if (userRecord.mUserId != mCurrentUserId
778 && userRecord.mClientRecords.isEmpty()) {
779 if (DEBUG) {
780 Slog.d(TAG, userRecord + ": Disposed");
781 }
782 mUserRecords.remove(userRecord.mUserId);
783 // Note: User already stopped (by switchUser) so no need to send stop message here.
784 }
785 }
786
787 private void initializeClientLocked(ClientRecord clientRecord) {
788 if (DEBUG) {
789 Slog.d(TAG, clientRecord + ": Registered");
790 }
791 }
792
793 private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
794 if (DEBUG) {
795 if (died) {
796 Slog.d(TAG, clientRecord + ": Died!");
797 } else {
798 Slog.d(TAG, clientRecord + ": Unregistered");
799 }
800 }
801 if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
802 clientRecord.mUserRecord.mHandler.sendEmptyMessage(
803 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
804 }
805 clientRecord.dispose();
806 }
807
808 private boolean validatePackageName(int uid, String packageName) {
809 if (packageName != null) {
810 String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
811 if (packageNames != null) {
812 for (String n : packageNames) {
813 if (n.equals(packageName)) {
814 return true;
815 }
816 }
817 }
818 }
819 return false;
820 }
821
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900822 final class MediaRouterServiceBroadcastReceiver extends BroadcastReceiver {
823 @Override
824 public void onReceive(Context context, Intent intent) {
Sungsoo Limcc4b8a42018-05-24 00:28:27 +0900825 if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
826 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
827 synchronized (mLock) {
828 mActiveBluetoothDevice = btDevice;
829 mGlobalBluetoothA2dpOn = btDevice != null;
Sungsoo Lim7dafa9a2017-12-07 20:14:24 +0900830 }
831 }
832 }
833 }
834
Jeff Brown69b07162013-11-07 00:30:16 -0800835 /**
836 * Information about a particular client of the media router.
837 * The contents of this object is guarded by mLock.
838 */
839 final class ClientRecord implements DeathRecipient {
840 public final UserRecord mUserRecord;
841 public final IMediaRouterClient mClient;
Sungsoob3658562017-05-22 17:10:44 +0900842 public final int mUid;
Jeff Brown69b07162013-11-07 00:30:16 -0800843 public final int mPid;
844 public final String mPackageName;
Jeff Brownaf574182013-11-14 18:16:08 -0800845 public final boolean mTrusted;
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000846 public List<String> mControlCategories;
Jeff Brown69b07162013-11-07 00:30:16 -0800847
848 public int mRouteTypes;
849 public boolean mActiveScan;
850 public String mSelectedRouteId;
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900851 public String mGroupId;
Jeff Brown69b07162013-11-07 00:30:16 -0800852
853 public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
Sungsoob3658562017-05-22 17:10:44 +0900854 int uid, int pid, String packageName, boolean trusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800855 mUserRecord = userRecord;
856 mClient = client;
Sungsoob3658562017-05-22 17:10:44 +0900857 mUid = uid;
Jeff Brown69b07162013-11-07 00:30:16 -0800858 mPid = pid;
859 mPackageName = packageName;
Jeff Brownaf574182013-11-14 18:16:08 -0800860 mTrusted = trusted;
Jeff Brown69b07162013-11-07 00:30:16 -0800861 }
862
863 public void dispose() {
864 mClient.asBinder().unlinkToDeath(this, 0);
865 }
866
867 @Override
868 public void binderDied() {
869 clientDied(this);
870 }
871
Jeff Brownaf574182013-11-14 18:16:08 -0800872 MediaRouterClientState getState() {
Sungsood103e562017-03-09 15:35:07 +0900873 return mTrusted ? mUserRecord.mRouterState : null;
Jeff Brownaf574182013-11-14 18:16:08 -0800874 }
875
Jeff Brown69b07162013-11-07 00:30:16 -0800876 public void dump(PrintWriter pw, String prefix) {
877 pw.println(prefix + this);
878
879 final String indent = prefix + " ";
Jeff Brownaf574182013-11-14 18:16:08 -0800880 pw.println(indent + "mTrusted=" + mTrusted);
Jeff Brown69b07162013-11-07 00:30:16 -0800881 pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
882 pw.println(indent + "mActiveScan=" + mActiveScan);
883 pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
884 }
885
886 @Override
887 public String toString() {
888 return "Client " + mPackageName + " (pid " + mPid + ")";
889 }
890 }
891
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900892 final class ClientGroup {
893 public String mSelectedRouteId;
894 public final List<ClientRecord> mClientRecords = new ArrayList<>();
895 }
896
Jeff Brown69b07162013-11-07 00:30:16 -0800897 /**
898 * Information about a particular user.
899 * The contents of this object is guarded by mLock.
900 */
901 final class UserRecord {
902 public final int mUserId;
Kyunglyul Hyund51666d2019-04-11 04:08:40 +0000903 public final ArrayList<ClientRecord> mClientRecords = new ArrayList<>();
Jeff Brown69b07162013-11-07 00:30:16 -0800904 public final UserHandler mHandler;
Sungsood103e562017-03-09 15:35:07 +0900905 public MediaRouterClientState mRouterState;
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900906 private final ArrayMap<String, ClientGroup> mClientGroupMap = new ArrayMap<>();
Jeff Brown69b07162013-11-07 00:30:16 -0800907
908 public UserRecord(int userId) {
909 mUserId = userId;
910 mHandler = new UserHandler(MediaRouterService.this, this);
911 }
912
913 public void dump(final PrintWriter pw, String prefix) {
914 pw.println(prefix + this);
915
916 final String indent = prefix + " ";
917 final int clientCount = mClientRecords.size();
918 if (clientCount != 0) {
919 for (int i = 0; i < clientCount; i++) {
920 mClientRecords.get(i).dump(pw, indent);
921 }
922 } else {
923 pw.println(indent + "<no clients>");
924 }
925
Jeff Brownaf574182013-11-14 18:16:08 -0800926 pw.println(indent + "State");
Sungsood103e562017-03-09 15:35:07 +0900927 pw.println(indent + "mRouterState=" + mRouterState);
Jeff Brownaf574182013-11-14 18:16:08 -0800928
Jeff Brown69b07162013-11-07 00:30:16 -0800929 if (!mHandler.runWithScissors(new Runnable() {
930 @Override
931 public void run() {
932 mHandler.dump(pw, indent);
933 }
934 }, 1000)) {
935 pw.println(indent + "<could not dump handler state>");
936 }
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900937 }
938
939 public void addToGroup(String groupId, ClientRecord clientRecord) {
940 ClientGroup group = mClientGroupMap.get(groupId);
941 if (group == null) {
942 group = new ClientGroup();
943 mClientGroupMap.put(groupId, group);
944 }
945 group.mClientRecords.add(clientRecord);
946 }
947
948 public void removeFromGroup(String groupId, ClientRecord clientRecord) {
949 ClientGroup group = mClientGroupMap.get(groupId);
950 if (group != null) {
951 group.mClientRecords.remove(clientRecord);
952 if (group.mClientRecords.size() == 0) {
953 mClientGroupMap.remove(groupId);
954 }
955 }
956 }
Jeff Brown69b07162013-11-07 00:30:16 -0800957
958 @Override
959 public String toString() {
960 return "User " + mUserId;
961 }
962 }
963
964 /**
965 * Media router handler
966 * <p>
967 * Since remote display providers are designed to be single-threaded by nature,
968 * this class encapsulates all of the associated functionality and exports state
969 * to the service as it evolves.
970 * </p><p>
Jeff Brown69b07162013-11-07 00:30:16 -0800971 * This class is currently hardcoded to work with remote display providers but
972 * it is intended to be eventually extended to support more general route providers
973 * similar to the support library media router.
974 * </p>
975 */
976 static final class UserHandler extends Handler
977 implements RemoteDisplayProviderWatcher.Callback,
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900978 RemoteDisplayProviderProxy.Callback {
Jeff Brown69b07162013-11-07 00:30:16 -0800979 public static final int MSG_START = 1;
980 public static final int MSG_STOP = 2;
981 public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
982 public static final int MSG_SELECT_ROUTE = 4;
983 public static final int MSG_UNSELECT_ROUTE = 5;
984 public static final int MSG_REQUEST_SET_VOLUME = 6;
985 public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
986 private static final int MSG_UPDATE_CLIENT_STATE = 8;
987 private static final int MSG_CONNECTION_TIMED_OUT = 9;
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +0900988 private static final int MSG_UPDATE_SELECTED_ROUTE = 10;
Jeff Brown69b07162013-11-07 00:30:16 -0800989
990 private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
Jeff Brown39ad0e52013-11-11 17:55:08 -0800991 private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
992 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
993 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4;
994
995 // The relative order of these constants is important and expresses progress
996 // through the process of connecting to a route.
997 private static final int PHASE_NOT_AVAILABLE = -1;
998 private static final int PHASE_NOT_CONNECTED = 0;
999 private static final int PHASE_CONNECTING = 1;
1000 private static final int PHASE_CONNECTED = 2;
Jeff Brown69b07162013-11-07 00:30:16 -08001001
1002 private final MediaRouterService mService;
1003 private final UserRecord mUserRecord;
1004 private final RemoteDisplayProviderWatcher mWatcher;
1005 private final ArrayList<ProviderRecord> mProviderRecords =
1006 new ArrayList<ProviderRecord>();
1007 private final ArrayList<IMediaRouterClient> mTempClients =
1008 new ArrayList<IMediaRouterClient>();
1009
1010 private boolean mRunning;
1011 private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
Sungsood103e562017-03-09 15:35:07 +09001012 private RouteRecord mSelectedRouteRecord;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001013 private int mConnectionPhase = PHASE_NOT_AVAILABLE;
Jeff Brown69b07162013-11-07 00:30:16 -08001014 private int mConnectionTimeoutReason;
1015 private long mConnectionTimeoutStartTime;
1016 private boolean mClientStateUpdateScheduled;
1017
1018 public UserHandler(MediaRouterService service, UserRecord userRecord) {
1019 super(Looper.getMainLooper(), null, true);
1020 mService = service;
1021 mUserRecord = userRecord;
1022 mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
1023 this, mUserRecord.mUserId);
1024 }
1025
1026 @Override
1027 public void handleMessage(Message msg) {
1028 switch (msg.what) {
1029 case MSG_START: {
1030 start();
1031 break;
1032 }
1033 case MSG_STOP: {
1034 stop();
1035 break;
1036 }
1037 case MSG_UPDATE_DISCOVERY_REQUEST: {
1038 updateDiscoveryRequest();
1039 break;
1040 }
1041 case MSG_SELECT_ROUTE: {
1042 selectRoute((String)msg.obj);
1043 break;
1044 }
1045 case MSG_UNSELECT_ROUTE: {
1046 unselectRoute((String)msg.obj);
1047 break;
1048 }
1049 case MSG_REQUEST_SET_VOLUME: {
1050 requestSetVolume((String)msg.obj, msg.arg1);
1051 break;
1052 }
1053 case MSG_REQUEST_UPDATE_VOLUME: {
1054 requestUpdateVolume((String)msg.obj, msg.arg1);
1055 break;
1056 }
1057 case MSG_UPDATE_CLIENT_STATE: {
1058 updateClientState();
1059 break;
1060 }
1061 case MSG_CONNECTION_TIMED_OUT: {
1062 connectionTimedOut();
1063 break;
1064 }
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +09001065 case MSG_UPDATE_SELECTED_ROUTE: {
1066 updateSelectedRoute((String) msg.obj);
1067 break;
1068 }
Jeff Brown69b07162013-11-07 00:30:16 -08001069 }
1070 }
1071
1072 public void dump(PrintWriter pw, String prefix) {
1073 pw.println(prefix + "Handler");
1074
1075 final String indent = prefix + " ";
1076 pw.println(indent + "mRunning=" + mRunning);
1077 pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
Sungsood103e562017-03-09 15:35:07 +09001078 pw.println(indent + "mSelectedRouteRecord=" + mSelectedRouteRecord);
Jeff Brown39ad0e52013-11-11 17:55:08 -08001079 pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
Jeff Brown69b07162013-11-07 00:30:16 -08001080 pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
1081 pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
1082 TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
1083
1084 mWatcher.dump(pw, prefix);
1085
1086 final int providerCount = mProviderRecords.size();
1087 if (providerCount != 0) {
1088 for (int i = 0; i < providerCount; i++) {
1089 mProviderRecords.get(i).dump(pw, prefix);
1090 }
1091 } else {
1092 pw.println(indent + "<no providers>");
1093 }
1094 }
1095
1096 private void start() {
1097 if (!mRunning) {
1098 mRunning = true;
1099 mWatcher.start(); // also starts all providers
1100 }
1101 }
1102
1103 private void stop() {
1104 if (mRunning) {
1105 mRunning = false;
Sungsood103e562017-03-09 15:35:07 +09001106 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001107 mWatcher.stop(); // also stops all providers
1108 }
1109 }
1110
1111 private void updateDiscoveryRequest() {
1112 int routeTypes = 0;
1113 boolean activeScan = false;
1114 synchronized (mService.mLock) {
1115 final int count = mUserRecord.mClientRecords.size();
1116 for (int i = 0; i < count; i++) {
1117 ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
1118 routeTypes |= clientRecord.mRouteTypes;
1119 activeScan |= clientRecord.mActiveScan;
1120 }
1121 }
1122
1123 final int newDiscoveryMode;
Jeff Brownaf574182013-11-14 18:16:08 -08001124 if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
Jeff Brown69b07162013-11-07 00:30:16 -08001125 if (activeScan) {
1126 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
1127 } else {
1128 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
1129 }
1130 } else {
1131 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
1132 }
1133
1134 if (mDiscoveryMode != newDiscoveryMode) {
1135 mDiscoveryMode = newDiscoveryMode;
1136 final int count = mProviderRecords.size();
1137 for (int i = 0; i < count; i++) {
1138 mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
1139 }
1140 }
1141 }
1142
1143 private void selectRoute(String routeId) {
1144 if (routeId != null
Sungsood103e562017-03-09 15:35:07 +09001145 && (mSelectedRouteRecord == null
1146 || !routeId.equals(mSelectedRouteRecord.getUniqueId()))) {
Jeff Brown69b07162013-11-07 00:30:16 -08001147 RouteRecord routeRecord = findRouteRecord(routeId);
1148 if (routeRecord != null) {
Sungsood103e562017-03-09 15:35:07 +09001149 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001150
Sungsood103e562017-03-09 15:35:07 +09001151 Slog.i(TAG, "Selected route:" + routeRecord);
1152 mSelectedRouteRecord = routeRecord;
1153 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001154 routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
1155
1156 scheduleUpdateClientState();
1157 }
1158 }
1159 }
1160
1161 private void unselectRoute(String routeId) {
1162 if (routeId != null
Sungsood103e562017-03-09 15:35:07 +09001163 && mSelectedRouteRecord != null
1164 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1165 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001166 }
1167 }
1168
Sungsood103e562017-03-09 15:35:07 +09001169 private void unselectSelectedRoute() {
1170 if (mSelectedRouteRecord != null) {
1171 Slog.i(TAG, "Unselected route:" + mSelectedRouteRecord);
1172 mSelectedRouteRecord.getProvider().setSelectedDisplay(null);
1173 mSelectedRouteRecord = null;
1174 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001175
1176 scheduleUpdateClientState();
1177 }
1178 }
1179
1180 private void requestSetVolume(String routeId, int volume) {
Sungsood103e562017-03-09 15:35:07 +09001181 if (mSelectedRouteRecord != null
1182 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1183 mSelectedRouteRecord.getProvider().setDisplayVolume(volume);
Jeff Brown69b07162013-11-07 00:30:16 -08001184 }
1185 }
1186
1187 private void requestUpdateVolume(String routeId, int direction) {
Sungsood103e562017-03-09 15:35:07 +09001188 if (mSelectedRouteRecord != null
1189 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1190 mSelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
Jeff Brown69b07162013-11-07 00:30:16 -08001191 }
1192 }
1193
1194 @Override
1195 public void addProvider(RemoteDisplayProviderProxy provider) {
1196 provider.setCallback(this);
1197 provider.setDiscoveryMode(mDiscoveryMode);
1198 provider.setSelectedDisplay(null); // just to be safe
1199
1200 ProviderRecord providerRecord = new ProviderRecord(provider);
1201 mProviderRecords.add(providerRecord);
1202 providerRecord.updateDescriptor(provider.getDisplayState());
1203
1204 scheduleUpdateClientState();
1205 }
1206
1207 @Override
1208 public void removeProvider(RemoteDisplayProviderProxy provider) {
1209 int index = findProviderRecord(provider);
1210 if (index >= 0) {
1211 ProviderRecord providerRecord = mProviderRecords.remove(index);
1212 providerRecord.updateDescriptor(null); // mark routes invalid
1213 provider.setCallback(null);
1214 provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
1215
Sungsood103e562017-03-09 15:35:07 +09001216 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001217 scheduleUpdateClientState();
1218 }
1219 }
1220
1221 @Override
1222 public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
1223 RemoteDisplayState state) {
1224 updateProvider(provider, state);
1225 }
1226
1227 private void updateProvider(RemoteDisplayProviderProxy provider,
1228 RemoteDisplayState state) {
1229 int index = findProviderRecord(provider);
1230 if (index >= 0) {
1231 ProviderRecord providerRecord = mProviderRecords.get(index);
1232 if (providerRecord.updateDescriptor(state)) {
Sungsood103e562017-03-09 15:35:07 +09001233 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001234 scheduleUpdateClientState();
1235 }
1236 }
1237 }
1238
1239 /**
Sungsood103e562017-03-09 15:35:07 +09001240 * This function is called whenever the state of the selected route may have changed.
1241 * It checks the state and updates timeouts or unselects the route as appropriate.
Jeff Brown69b07162013-11-07 00:30:16 -08001242 */
Sungsood103e562017-03-09 15:35:07 +09001243 private void checkSelectedRouteState() {
Jeff Brown69b07162013-11-07 00:30:16 -08001244 // Unschedule timeouts when the route is unselected.
Sungsood103e562017-03-09 15:35:07 +09001245 if (mSelectedRouteRecord == null) {
Jeff Brown39ad0e52013-11-11 17:55:08 -08001246 mConnectionPhase = PHASE_NOT_AVAILABLE;
Jeff Brown69b07162013-11-07 00:30:16 -08001247 updateConnectionTimeout(0);
1248 return;
1249 }
1250
1251 // Ensure that the route is still present and enabled.
Sungsood103e562017-03-09 15:35:07 +09001252 if (!mSelectedRouteRecord.isValid()
1253 || !mSelectedRouteRecord.isEnabled()) {
Jeff Brown69b07162013-11-07 00:30:16 -08001254 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
1255 return;
1256 }
1257
Jeff Brown39ad0e52013-11-11 17:55:08 -08001258 // Make sure we haven't lost our connection.
1259 final int oldPhase = mConnectionPhase;
Sungsood103e562017-03-09 15:35:07 +09001260 mConnectionPhase = getConnectionPhase(mSelectedRouteRecord.getStatus());
Jeff Brown39ad0e52013-11-11 17:55:08 -08001261 if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
1262 updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
1263 return;
1264 }
1265
Jeff Brown69b07162013-11-07 00:30:16 -08001266 // Check the route status.
Jeff Brown39ad0e52013-11-11 17:55:08 -08001267 switch (mConnectionPhase) {
1268 case PHASE_CONNECTED:
1269 if (oldPhase != PHASE_CONNECTED) {
Sungsood103e562017-03-09 15:35:07 +09001270 Slog.i(TAG, "Connected to route: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001271 }
1272 updateConnectionTimeout(0);
1273 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001274 case PHASE_CONNECTING:
1275 if (oldPhase != PHASE_CONNECTING) {
Sungsood103e562017-03-09 15:35:07 +09001276 Slog.i(TAG, "Connecting to route: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001277 }
1278 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
1279 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001280 case PHASE_NOT_CONNECTED:
Jeff Brown69b07162013-11-07 00:30:16 -08001281 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
1282 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001283 case PHASE_NOT_AVAILABLE:
Jeff Brown69b07162013-11-07 00:30:16 -08001284 default:
1285 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
1286 break;
1287 }
1288 }
1289
1290 private void updateConnectionTimeout(int reason) {
1291 if (reason != mConnectionTimeoutReason) {
1292 if (mConnectionTimeoutReason != 0) {
1293 removeMessages(MSG_CONNECTION_TIMED_OUT);
1294 }
1295 mConnectionTimeoutReason = reason;
1296 mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
1297 switch (reason) {
1298 case TIMEOUT_REASON_NOT_AVAILABLE:
Jeff Brown39ad0e52013-11-11 17:55:08 -08001299 case TIMEOUT_REASON_CONNECTION_LOST:
1300 // Route became unavailable or connection lost.
1301 // Unselect it immediately.
Jeff Brown69b07162013-11-07 00:30:16 -08001302 sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
1303 break;
1304 case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
1305 // Waiting for route to start connecting.
1306 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
1307 break;
1308 case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
1309 // Waiting for route to complete connection.
1310 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
1311 break;
1312 }
1313 }
1314 }
1315
1316 private void connectionTimedOut() {
Sungsood103e562017-03-09 15:35:07 +09001317 if (mConnectionTimeoutReason == 0 || mSelectedRouteRecord == null) {
Jeff Brown69b07162013-11-07 00:30:16 -08001318 // Shouldn't get here. There must be a bug somewhere.
1319 Log.wtf(TAG, "Handled connection timeout for no reason.");
1320 return;
1321 }
1322
1323 switch (mConnectionTimeoutReason) {
1324 case TIMEOUT_REASON_NOT_AVAILABLE:
Sungsood103e562017-03-09 15:35:07 +09001325 Slog.i(TAG, "Selected route no longer available: "
1326 + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001327 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001328 case TIMEOUT_REASON_CONNECTION_LOST:
Sungsood103e562017-03-09 15:35:07 +09001329 Slog.i(TAG, "Selected route connection lost: "
1330 + mSelectedRouteRecord);
Jeff Brown39ad0e52013-11-11 17:55:08 -08001331 break;
Jeff Brown69b07162013-11-07 00:30:16 -08001332 case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
Sungsood103e562017-03-09 15:35:07 +09001333 Slog.i(TAG, "Selected route timed out while waiting for "
Jeff Brown69b07162013-11-07 00:30:16 -08001334 + "connection attempt to begin after "
1335 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
Sungsood103e562017-03-09 15:35:07 +09001336 + " ms: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001337 break;
1338 case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
Sungsood103e562017-03-09 15:35:07 +09001339 Slog.i(TAG, "Selected route timed out while connecting after "
Jeff Brown69b07162013-11-07 00:30:16 -08001340 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
Sungsood103e562017-03-09 15:35:07 +09001341 + " ms: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001342 break;
1343 }
1344 mConnectionTimeoutReason = 0;
1345
Sungsood103e562017-03-09 15:35:07 +09001346 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001347 }
1348
1349 private void scheduleUpdateClientState() {
1350 if (!mClientStateUpdateScheduled) {
1351 mClientStateUpdateScheduled = true;
1352 sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
1353 }
1354 }
1355
1356 private void updateClientState() {
1357 mClientStateUpdateScheduled = false;
1358
Jeff Brownaf574182013-11-14 18:16:08 -08001359 // Build a new client state for trusted clients.
Sungsood103e562017-03-09 15:35:07 +09001360 MediaRouterClientState routerState = new MediaRouterClientState();
Jeff Brown69b07162013-11-07 00:30:16 -08001361 final int providerCount = mProviderRecords.size();
1362 for (int i = 0; i < providerCount; i++) {
Sungsood103e562017-03-09 15:35:07 +09001363 mProviderRecords.get(i).appendClientState(routerState);
Jeff Brown69b07162013-11-07 00:30:16 -08001364 }
Jeff Brown69b07162013-11-07 00:30:16 -08001365 try {
1366 synchronized (mService.mLock) {
1367 // Update the UserRecord.
Sungsood103e562017-03-09 15:35:07 +09001368 mUserRecord.mRouterState = routerState;
Jeff Brown69b07162013-11-07 00:30:16 -08001369
1370 // Collect all clients.
1371 final int count = mUserRecord.mClientRecords.size();
1372 for (int i = 0; i < count; i++) {
1373 mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
1374 }
1375 }
1376
1377 // Notify all clients (outside of the lock).
1378 final int count = mTempClients.size();
1379 for (int i = 0; i < count; i++) {
1380 try {
1381 mTempClients.get(i).onStateChanged();
1382 } catch (RemoteException ex) {
Sungsoob3658562017-05-22 17:10:44 +09001383 Slog.w(TAG, "Failed to call onStateChanged. Client probably died.");
Jeff Brown69b07162013-11-07 00:30:16 -08001384 }
1385 }
1386 } finally {
1387 // Clear the list in preparation for the next time.
1388 mTempClients.clear();
1389 }
1390 }
1391
Kyunglyul Hyuncd18ace2019-05-22 21:32:57 +09001392 private void updateSelectedRoute(String groupId) {
1393 try {
1394 String selectedRouteId = null;
1395 synchronized (mService.mLock) {
1396 ClientGroup group = mUserRecord.mClientGroupMap.get(groupId);
1397 if (group == null) {
1398 return;
1399 }
1400 selectedRouteId = group.mSelectedRouteId;
1401 final int count = group.mClientRecords.size();
1402 for (int i = 0; i < count; i++) {
1403 ClientRecord clientRecord = group.mClientRecords.get(i);
1404 if (!TextUtils.equals(selectedRouteId, clientRecord.mSelectedRouteId)) {
1405 mTempClients.add(clientRecord.mClient);
1406 }
1407 }
1408 }
1409
1410 final int count = mTempClients.size();
1411 for (int i = 0; i < count; i++) {
1412 try {
1413 mTempClients.get(i).onSelectedRouteChanged(selectedRouteId);
1414 } catch (RemoteException ex) {
1415 Slog.w(TAG, "Failed to call onSelectedRouteChanged. Client probably died.");
1416 }
1417 }
1418 } finally {
1419 mTempClients.clear();
1420 }
1421 }
1422
Jeff Brown69b07162013-11-07 00:30:16 -08001423 private int findProviderRecord(RemoteDisplayProviderProxy provider) {
1424 final int count = mProviderRecords.size();
1425 for (int i = 0; i < count; i++) {
1426 ProviderRecord record = mProviderRecords.get(i);
1427 if (record.getProvider() == provider) {
1428 return i;
1429 }
1430 }
1431 return -1;
1432 }
1433
1434 private RouteRecord findRouteRecord(String uniqueId) {
1435 final int count = mProviderRecords.size();
1436 for (int i = 0; i < count; i++) {
1437 RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
1438 if (record != null) {
1439 return record;
1440 }
1441 }
1442 return null;
1443 }
1444
Jeff Brown39ad0e52013-11-11 17:55:08 -08001445 private static int getConnectionPhase(int status) {
1446 switch (status) {
1447 case MediaRouter.RouteInfo.STATUS_NONE:
1448 case MediaRouter.RouteInfo.STATUS_CONNECTED:
1449 return PHASE_CONNECTED;
1450 case MediaRouter.RouteInfo.STATUS_CONNECTING:
1451 return PHASE_CONNECTING;
1452 case MediaRouter.RouteInfo.STATUS_SCANNING:
1453 case MediaRouter.RouteInfo.STATUS_AVAILABLE:
1454 return PHASE_NOT_CONNECTED;
1455 case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
1456 case MediaRouter.RouteInfo.STATUS_IN_USE:
1457 default:
1458 return PHASE_NOT_AVAILABLE;
1459 }
1460 }
1461
Jeff Brown69b07162013-11-07 00:30:16 -08001462 static final class ProviderRecord {
1463 private final RemoteDisplayProviderProxy mProvider;
1464 private final String mUniquePrefix;
1465 private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
1466 private RemoteDisplayState mDescriptor;
1467
1468 public ProviderRecord(RemoteDisplayProviderProxy provider) {
1469 mProvider = provider;
1470 mUniquePrefix = provider.getFlattenedComponentName() + ":";
1471 }
1472
1473 public RemoteDisplayProviderProxy getProvider() {
1474 return mProvider;
1475 }
1476
1477 public String getUniquePrefix() {
1478 return mUniquePrefix;
1479 }
1480
1481 public boolean updateDescriptor(RemoteDisplayState descriptor) {
1482 boolean changed = false;
1483 if (mDescriptor != descriptor) {
1484 mDescriptor = descriptor;
1485
1486 // Update all existing routes and reorder them to match
1487 // the order of their descriptors.
1488 int targetIndex = 0;
1489 if (descriptor != null) {
1490 if (descriptor.isValid()) {
1491 final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
1492 final int routeCount = routeDescriptors.size();
1493 for (int i = 0; i < routeCount; i++) {
1494 final RemoteDisplayInfo routeDescriptor =
1495 routeDescriptors.get(i);
1496 final String descriptorId = routeDescriptor.id;
1497 final int sourceIndex = findRouteByDescriptorId(descriptorId);
1498 if (sourceIndex < 0) {
1499 // Add the route to the provider.
1500 String uniqueId = assignRouteUniqueId(descriptorId);
1501 RouteRecord route =
1502 new RouteRecord(this, descriptorId, uniqueId);
1503 mRoutes.add(targetIndex++, route);
1504 route.updateDescriptor(routeDescriptor);
1505 changed = true;
1506 } else if (sourceIndex < targetIndex) {
1507 // Ignore route with duplicate id.
1508 Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
1509 + routeDescriptor);
1510 } else {
1511 // Reorder existing route within the list.
1512 RouteRecord route = mRoutes.get(sourceIndex);
1513 Collections.swap(mRoutes, sourceIndex, targetIndex++);
1514 changed |= route.updateDescriptor(routeDescriptor);
1515 }
1516 }
1517 } else {
1518 Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
1519 + mProvider.getFlattenedComponentName());
1520 }
1521 }
1522
1523 // Dispose all remaining routes that do not have matching descriptors.
1524 for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
1525 RouteRecord route = mRoutes.remove(i);
1526 route.updateDescriptor(null); // mark route invalid
1527 changed = true;
1528 }
1529 }
1530 return changed;
1531 }
1532
1533 public void appendClientState(MediaRouterClientState state) {
1534 final int routeCount = mRoutes.size();
1535 for (int i = 0; i < routeCount; i++) {
1536 state.routes.add(mRoutes.get(i).getInfo());
1537 }
1538 }
1539
1540 public RouteRecord findRouteByUniqueId(String uniqueId) {
1541 final int routeCount = mRoutes.size();
1542 for (int i = 0; i < routeCount; i++) {
1543 RouteRecord route = mRoutes.get(i);
1544 if (route.getUniqueId().equals(uniqueId)) {
1545 return route;
1546 }
1547 }
1548 return null;
1549 }
1550
1551 private int findRouteByDescriptorId(String descriptorId) {
1552 final int routeCount = mRoutes.size();
1553 for (int i = 0; i < routeCount; i++) {
1554 RouteRecord route = mRoutes.get(i);
1555 if (route.getDescriptorId().equals(descriptorId)) {
1556 return i;
1557 }
1558 }
1559 return -1;
1560 }
1561
1562 public void dump(PrintWriter pw, String prefix) {
1563 pw.println(prefix + this);
1564
1565 final String indent = prefix + " ";
1566 mProvider.dump(pw, indent);
1567
1568 final int routeCount = mRoutes.size();
1569 if (routeCount != 0) {
1570 for (int i = 0; i < routeCount; i++) {
1571 mRoutes.get(i).dump(pw, indent);
1572 }
1573 } else {
1574 pw.println(indent + "<no routes>");
1575 }
1576 }
1577
1578 @Override
1579 public String toString() {
1580 return "Provider " + mProvider.getFlattenedComponentName();
1581 }
1582
1583 private String assignRouteUniqueId(String descriptorId) {
1584 return mUniquePrefix + descriptorId;
1585 }
1586 }
1587
1588 static final class RouteRecord {
1589 private final ProviderRecord mProviderRecord;
1590 private final String mDescriptorId;
1591 private final MediaRouterClientState.RouteInfo mMutableInfo;
1592 private MediaRouterClientState.RouteInfo mImmutableInfo;
1593 private RemoteDisplayInfo mDescriptor;
1594
1595 public RouteRecord(ProviderRecord providerRecord,
1596 String descriptorId, String uniqueId) {
1597 mProviderRecord = providerRecord;
1598 mDescriptorId = descriptorId;
1599 mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
1600 }
1601
1602 public RemoteDisplayProviderProxy getProvider() {
1603 return mProviderRecord.getProvider();
1604 }
1605
1606 public ProviderRecord getProviderRecord() {
1607 return mProviderRecord;
1608 }
1609
1610 public String getDescriptorId() {
1611 return mDescriptorId;
1612 }
1613
1614 public String getUniqueId() {
1615 return mMutableInfo.id;
1616 }
1617
1618 public MediaRouterClientState.RouteInfo getInfo() {
1619 if (mImmutableInfo == null) {
1620 mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
1621 }
1622 return mImmutableInfo;
1623 }
1624
1625 public boolean isValid() {
1626 return mDescriptor != null;
1627 }
1628
1629 public boolean isEnabled() {
1630 return mMutableInfo.enabled;
1631 }
1632
1633 public int getStatus() {
1634 return mMutableInfo.statusCode;
1635 }
1636
1637 public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
1638 boolean changed = false;
1639 if (mDescriptor != descriptor) {
1640 mDescriptor = descriptor;
1641 if (descriptor != null) {
1642 final String name = computeName(descriptor);
Kenny Roote6585b32013-12-13 12:00:26 -08001643 if (!Objects.equals(mMutableInfo.name, name)) {
Jeff Brown69b07162013-11-07 00:30:16 -08001644 mMutableInfo.name = name;
1645 changed = true;
1646 }
1647 final String description = computeDescription(descriptor);
Kenny Roote6585b32013-12-13 12:00:26 -08001648 if (!Objects.equals(mMutableInfo.description, description)) {
Jeff Brown69b07162013-11-07 00:30:16 -08001649 mMutableInfo.description = description;
1650 changed = true;
1651 }
1652 final int supportedTypes = computeSupportedTypes(descriptor);
1653 if (mMutableInfo.supportedTypes != supportedTypes) {
1654 mMutableInfo.supportedTypes = supportedTypes;
1655 changed = true;
1656 }
1657 final boolean enabled = computeEnabled(descriptor);
1658 if (mMutableInfo.enabled != enabled) {
1659 mMutableInfo.enabled = enabled;
1660 changed = true;
1661 }
1662 final int statusCode = computeStatusCode(descriptor);
1663 if (mMutableInfo.statusCode != statusCode) {
1664 mMutableInfo.statusCode = statusCode;
1665 changed = true;
1666 }
1667 final int playbackType = computePlaybackType(descriptor);
1668 if (mMutableInfo.playbackType != playbackType) {
1669 mMutableInfo.playbackType = playbackType;
1670 changed = true;
1671 }
1672 final int playbackStream = computePlaybackStream(descriptor);
1673 if (mMutableInfo.playbackStream != playbackStream) {
1674 mMutableInfo.playbackStream = playbackStream;
1675 changed = true;
1676 }
1677 final int volume = computeVolume(descriptor);
1678 if (mMutableInfo.volume != volume) {
1679 mMutableInfo.volume = volume;
1680 changed = true;
1681 }
1682 final int volumeMax = computeVolumeMax(descriptor);
1683 if (mMutableInfo.volumeMax != volumeMax) {
1684 mMutableInfo.volumeMax = volumeMax;
1685 changed = true;
1686 }
1687 final int volumeHandling = computeVolumeHandling(descriptor);
1688 if (mMutableInfo.volumeHandling != volumeHandling) {
1689 mMutableInfo.volumeHandling = volumeHandling;
1690 changed = true;
1691 }
1692 final int presentationDisplayId = computePresentationDisplayId(descriptor);
1693 if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
1694 mMutableInfo.presentationDisplayId = presentationDisplayId;
1695 changed = true;
1696 }
1697 }
1698 }
1699 if (changed) {
1700 mImmutableInfo = null;
1701 }
1702 return changed;
1703 }
1704
1705 public void dump(PrintWriter pw, String prefix) {
1706 pw.println(prefix + this);
1707
1708 final String indent = prefix + " ";
1709 pw.println(indent + "mMutableInfo=" + mMutableInfo);
1710 pw.println(indent + "mDescriptorId=" + mDescriptorId);
1711 pw.println(indent + "mDescriptor=" + mDescriptor);
1712 }
1713
1714 @Override
1715 public String toString() {
1716 return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
1717 }
1718
1719 private static String computeName(RemoteDisplayInfo descriptor) {
1720 // Note that isValid() already ensures the name is non-empty.
1721 return descriptor.name;
1722 }
1723
1724 private static String computeDescription(RemoteDisplayInfo descriptor) {
1725 final String description = descriptor.description;
1726 return TextUtils.isEmpty(description) ? null : description;
1727 }
1728
1729 private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
1730 return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
1731 | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
1732 | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
1733 }
1734
1735 private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
1736 switch (descriptor.status) {
1737 case RemoteDisplayInfo.STATUS_CONNECTED:
1738 case RemoteDisplayInfo.STATUS_CONNECTING:
1739 case RemoteDisplayInfo.STATUS_AVAILABLE:
1740 return true;
1741 default:
1742 return false;
1743 }
1744 }
1745
1746 private static int computeStatusCode(RemoteDisplayInfo descriptor) {
1747 switch (descriptor.status) {
1748 case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
1749 return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
1750 case RemoteDisplayInfo.STATUS_AVAILABLE:
1751 return MediaRouter.RouteInfo.STATUS_AVAILABLE;
1752 case RemoteDisplayInfo.STATUS_IN_USE:
1753 return MediaRouter.RouteInfo.STATUS_IN_USE;
1754 case RemoteDisplayInfo.STATUS_CONNECTING:
1755 return MediaRouter.RouteInfo.STATUS_CONNECTING;
1756 case RemoteDisplayInfo.STATUS_CONNECTED:
1757 return MediaRouter.RouteInfo.STATUS_CONNECTED;
1758 default:
1759 return MediaRouter.RouteInfo.STATUS_NONE;
1760 }
1761 }
1762
1763 private static int computePlaybackType(RemoteDisplayInfo descriptor) {
1764 return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
1765 }
1766
1767 private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
1768 return AudioSystem.STREAM_MUSIC;
1769 }
1770
1771 private static int computeVolume(RemoteDisplayInfo descriptor) {
1772 final int volume = descriptor.volume;
1773 final int volumeMax = descriptor.volumeMax;
1774 if (volume < 0) {
1775 return 0;
1776 } else if (volume > volumeMax) {
1777 return volumeMax;
1778 }
1779 return volume;
1780 }
1781
1782 private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
1783 final int volumeMax = descriptor.volumeMax;
1784 return volumeMax > 0 ? volumeMax : 0;
1785 }
1786
1787 private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
1788 final int volumeHandling = descriptor.volumeHandling;
1789 switch (volumeHandling) {
1790 case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
1791 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
1792 case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
1793 default:
1794 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
1795 }
1796 }
1797
1798 private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
1799 // The MediaRouter class validates that the id corresponds to an extant
1800 // presentation display. So all we do here is canonicalize the null case.
1801 final int displayId = descriptor.presentationDisplayId;
1802 return displayId < 0 ? -1 : displayId;
1803 }
1804 }
1805 }
Jeff Brown69b07162013-11-07 00:30:16 -08001806}