blob: 0b089fbc612925836015dc7261286b461cda3e38 [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
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060019import com.android.internal.util.DumpUtils;
Jeff Brown69b07162013-11-07 00:30:16 -080020import com.android.server.Watchdog;
21
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090022import android.annotation.NonNull;
Jeff Brown69b07162013-11-07 00:30:16 -080023import android.app.ActivityManager;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.PackageManager;
Sungsoo Lim875e6972017-11-03 02:22:35 +000029import android.media.AudioPlaybackConfiguration;
Sungsoob3658562017-05-22 17:10:44 +090030import android.media.AudioRoutesInfo;
Jeff Brown69b07162013-11-07 00:30:16 -080031import android.media.AudioSystem;
Sungsoob3658562017-05-22 17:10:44 +090032import android.media.IAudioRoutesObserver;
33import android.media.IAudioService;
Jeff Brown69b07162013-11-07 00:30:16 -080034import android.media.IMediaRouterClient;
35import android.media.IMediaRouterService;
36import android.media.MediaRouter;
37import android.media.MediaRouterClientState;
38import android.media.RemoteDisplayState;
39import android.media.RemoteDisplayState.RemoteDisplayInfo;
40import android.os.Binder;
41import android.os.Handler;
42import android.os.IBinder;
43import android.os.Looper;
44import android.os.Message;
45import android.os.RemoteException;
Sungsoob3658562017-05-22 17:10:44 +090046import android.os.ServiceManager;
Jeff Brown69b07162013-11-07 00:30:16 -080047import android.os.SystemClock;
Sungsoob3658562017-05-22 17:10:44 +090048import android.os.UserHandle;
Jeff Brown69b07162013-11-07 00:30:16 -080049import android.text.TextUtils;
50import android.util.ArrayMap;
Sungsoob3658562017-05-22 17:10:44 +090051import android.util.IntArray;
Jeff Brown69b07162013-11-07 00:30:16 -080052import android.util.Log;
53import android.util.Slog;
54import android.util.SparseArray;
55import android.util.TimeUtils;
56
57import java.io.FileDescriptor;
58import java.io.PrintWriter;
59import java.util.ArrayList;
60import java.util.Collections;
61import java.util.List;
Kenny Roote6585b32013-12-13 12:00:26 -080062import java.util.Objects;
Jeff Brown69b07162013-11-07 00:30:16 -080063
64/**
65 * Provides a mechanism for discovering media routes and manages media playback
66 * behalf of applications.
67 * <p>
68 * Currently supports discovering remote displays via remote display provider
69 * services that have been registered by applications.
70 * </p>
71 */
72public final class MediaRouterService extends IMediaRouterService.Stub
73 implements Watchdog.Monitor {
74 private static final String TAG = "MediaRouterService";
75 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
76
77 /**
78 * Timeout in milliseconds for a selected route to transition from a
79 * disconnected state to a connecting state. If we don't observe any
80 * progress within this interval, then we will give up and unselect the route.
81 */
82 static final long CONNECTING_TIMEOUT = 5000;
83
84 /**
85 * Timeout in milliseconds for a selected route to transition from a
86 * connecting state to a connected state. If we don't observe any
87 * progress within this interval, then we will give up and unselect the route.
88 */
89 static final long CONNECTED_TIMEOUT = 60000;
90
91 private final Context mContext;
92
93 // State guarded by mLock.
94 private final Object mLock = new Object();
95 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
96 private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
97 new ArrayMap<IBinder, ClientRecord>();
98 private int mCurrentUserId = -1;
Sungsoo Lim76512a32017-08-24 10:25:06 +090099 private boolean mGlobalBluetoothA2dpOn = false;
Sungsoob3658562017-05-22 17:10:44 +0900100 private final IAudioService mAudioService;
Sungsoo Lim875e6972017-11-03 02:22:35 +0000101 private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
102 private final Handler mHandler = new Handler();
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900103 private final AudioRoutesInfo mAudioRoutesInfo = new AudioRoutesInfo();
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900104 private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
105 private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
Jeff Brown69b07162013-11-07 00:30:16 -0800106
107 public MediaRouterService(Context context) {
108 mContext = context;
109 Watchdog.getInstance().addMonitor(this);
Sungsoob3658562017-05-22 17:10:44 +0900110
111 mAudioService = IAudioService.Stub.asInterface(
112 ServiceManager.getService(Context.AUDIO_SERVICE));
113
Sungsoo Lim875e6972017-11-03 02:22:35 +0000114 mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
115 mAudioPlayerStateMonitor.registerListener(
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900116 new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000117 static final long WAIT_MS = 500;
118 final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
119 @Override
120 public void run() {
121 restoreBluetoothA2dp();
122 }
123 };
124
Sungsoob3658562017-05-22 17:10:44 +0900125 @Override
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900126 public void onAudioPlayerActiveStateChanged(
127 @NonNull AudioPlaybackConfiguration config, boolean isRemoved) {
128 final boolean active = !isRemoved && config.isActive();
129 final int pii = config.getPlayerInterfaceId();
130 final int uid = config.getClientUid();
131
132 final int idx = mActivePlayerMinPriorityQueue.indexOf(pii);
133 // Keep the latest active player and its uid at the end of the queue.
134 if (idx >= 0) {
135 mActivePlayerMinPriorityQueue.remove(idx);
136 mActivePlayerUidMinPriorityQueue.remove(idx);
137 }
138
Sungsoo Lim875e6972017-11-03 02:22:35 +0000139 int restoreUid = -1;
Sungsoob3658562017-05-22 17:10:44 +0900140 if (active) {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900141 mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId());
142 mActivePlayerUidMinPriorityQueue.add(uid);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000143 restoreUid = uid;
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900144 } else if (mActivePlayerUidMinPriorityQueue.size() > 0) {
145 restoreUid = mActivePlayerUidMinPriorityQueue.get(
146 mActivePlayerUidMinPriorityQueue.size() - 1);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000147 }
148
149 mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
150 if (restoreUid >= 0) {
151 restoreRoute(restoreUid);
152 if (DEBUG) {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900153 Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
154 + ", active=" + active + ", restoreUid=" + restoreUid);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000155 }
156 } else {
157 mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
158 if (DEBUG) {
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900159 Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900160 + ", active=" + active + ", delaying");
Sungsoob3658562017-05-22 17:10:44 +0900161 }
162 }
163 }
Sungsoo Lim875e6972017-11-03 02:22:35 +0000164 }, mHandler);
165 mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
166
Sungsoob3658562017-05-22 17:10:44 +0900167 AudioRoutesInfo audioRoutes = null;
168 try {
169 audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
170 @Override
171 public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
Sungsoo Lim76512a32017-08-24 10:25:06 +0900172 synchronized (mLock) {
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900173 if (newRoutes.mainType != mAudioRoutesInfo.mainType) {
Sungsoo Lim76512a32017-08-24 10:25:06 +0900174 if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
175 | AudioRoutesInfo.MAIN_HEADPHONES
176 | AudioRoutesInfo.MAIN_USB)) == 0) {
177 // headset was plugged out.
178 mGlobalBluetoothA2dpOn = newRoutes.bluetoothName != null;
179 } else {
180 // headset was plugged in.
181 mGlobalBluetoothA2dpOn = false;
182 }
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900183 mAudioRoutesInfo.mainType = newRoutes.mainType;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900184 }
185 if (!TextUtils.equals(
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900186 newRoutes.bluetoothName, mAudioRoutesInfo.bluetoothName)) {
Sungsoo Lim76512a32017-08-24 10:25:06 +0900187 if (newRoutes.bluetoothName == null) {
188 // BT was disconnected.
189 mGlobalBluetoothA2dpOn = false;
190 } else {
191 // BT was connected or changed.
192 mGlobalBluetoothA2dpOn = true;
193 }
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900194 mAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900195 }
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900196 // Although a Bluetooth device is connected before a new audio playback is
197 // started, dispatchAudioRoutChanged() can be called after
198 // onAudioPlayerActiveStateChanged(). That causes restoreBluetoothA2dp()
199 // is called before mGlobalBluetoothA2dpOn is updated.
200 // Calling restoreBluetoothA2dp() here could prevent that.
201 restoreBluetoothA2dp();
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 Lim76512a32017-08-24 10:25:06 +0900208 synchronized (mLock) {
209 mGlobalBluetoothA2dpOn = (audioRoutes != null && audioRoutes.bluetoothName != null);
210 }
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
262 public void unregisterClient(IMediaRouterClient client) {
263 if (client == null) {
264 throw new IllegalArgumentException("client must not be null");
265 }
266
267 final long token = Binder.clearCallingIdentity();
268 try {
269 synchronized (mLock) {
270 unregisterClientLocked(client, false);
271 }
272 } finally {
273 Binder.restoreCallingIdentity(token);
274 }
275 }
276
277 // Binder call
278 @Override
279 public MediaRouterClientState getState(IMediaRouterClient client) {
280 if (client == null) {
281 throw new IllegalArgumentException("client must not be null");
282 }
283
284 final long token = Binder.clearCallingIdentity();
285 try {
286 synchronized (mLock) {
287 return getStateLocked(client);
288 }
289 } finally {
290 Binder.restoreCallingIdentity(token);
291 }
292 }
293
294 // Binder call
295 @Override
Sungsoob3658562017-05-22 17:10:44 +0900296 public boolean isPlaybackActive(IMediaRouterClient client) {
297 if (client == null) {
298 throw new IllegalArgumentException("client must not be null");
299 }
300
301 final long token = Binder.clearCallingIdentity();
302 try {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000303 ClientRecord clientRecord;
Sungsoob3658562017-05-22 17:10:44 +0900304 synchronized (mLock) {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000305 clientRecord = mAllClientRecords.get(client.asBinder());
Sungsoob3658562017-05-22 17:10:44 +0900306 }
Sungsoo Lim875e6972017-11-03 02:22:35 +0000307 if (clientRecord != null) {
308 return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
309 }
310 return false;
Sungsoob3658562017-05-22 17:10:44 +0900311 } finally {
312 Binder.restoreCallingIdentity(token);
313 }
314 }
315
316 // Binder call
317 @Override
Jeff Brown69b07162013-11-07 00:30:16 -0800318 public void setDiscoveryRequest(IMediaRouterClient client,
319 int routeTypes, boolean activeScan) {
320 if (client == null) {
321 throw new IllegalArgumentException("client must not be null");
322 }
323
324 final long token = Binder.clearCallingIdentity();
325 try {
326 synchronized (mLock) {
327 setDiscoveryRequestLocked(client, routeTypes, activeScan);
328 }
329 } finally {
330 Binder.restoreCallingIdentity(token);
331 }
332 }
333
334 // Binder call
335 // A null routeId means that the client wants to unselect its current route.
336 // The explicit flag indicates whether the change was explicitly requested by the
337 // user or the application which may cause changes to propagate out to the rest
Sungsood103e562017-03-09 15:35:07 +0900338 // of the system. Should be false when the change is in response to a new
Jeff Brown69b07162013-11-07 00:30:16 -0800339 // selected route or a default selection.
340 @Override
341 public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
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 setSelectedRouteLocked(client, routeId, explicit);
350 }
351 } finally {
352 Binder.restoreCallingIdentity(token);
353 }
354 }
355
356 // Binder call
357 @Override
358 public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
359 if (client == null) {
360 throw new IllegalArgumentException("client must not be null");
361 }
362 if (routeId == null) {
363 throw new IllegalArgumentException("routeId must not be null");
364 }
365
366 final long token = Binder.clearCallingIdentity();
367 try {
368 synchronized (mLock) {
369 requestSetVolumeLocked(client, routeId, volume);
370 }
371 } finally {
372 Binder.restoreCallingIdentity(token);
373 }
374 }
375
376 // Binder call
377 @Override
378 public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
379 if (client == null) {
380 throw new IllegalArgumentException("client must not be null");
381 }
382 if (routeId == null) {
383 throw new IllegalArgumentException("routeId must not be null");
384 }
385
386 final long token = Binder.clearCallingIdentity();
387 try {
388 synchronized (mLock) {
389 requestUpdateVolumeLocked(client, routeId, direction);
390 }
391 } finally {
392 Binder.restoreCallingIdentity(token);
393 }
394 }
395
396 // Binder call
397 @Override
398 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600399 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Jeff Brown69b07162013-11-07 00:30:16 -0800400
401 pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
402 pw.println();
403 pw.println("Global state");
404 pw.println(" mCurrentUserId=" + mCurrentUserId);
405
406 synchronized (mLock) {
407 final int count = mUserRecords.size();
408 for (int i = 0; i < count; i++) {
409 UserRecord userRecord = mUserRecords.valueAt(i);
410 pw.println();
411 userRecord.dump(pw, "");
412 }
413 }
414 }
415
Sungsoob3658562017-05-22 17:10:44 +0900416 void restoreBluetoothA2dp() {
417 try {
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900418 boolean btConnected = false;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900419 boolean a2dpOn = false;
420 synchronized (mLock) {
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900421 btConnected = mAudioRoutesInfo.bluetoothName != null;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900422 a2dpOn = mGlobalBluetoothA2dpOn;
423 }
Sungsoo Lim90f4c8952017-11-21 13:12:24 +0900424 // We don't need to change a2dp status when bluetooth is not connected.
425 if (btConnected) {
426 Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
427 mAudioService.setBluetoothA2dpOn(a2dpOn);
428 }
Sungsoob3658562017-05-22 17:10:44 +0900429 } catch (RemoteException e) {
430 Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
431 }
432 }
433
434 void restoreRoute(int uid) {
435 ClientRecord clientRecord = null;
Sungsoo Lim76512a32017-08-24 10:25:06 +0900436 synchronized (mLock) {
437 UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
438 if (userRecord != null && userRecord.mClientRecords != null) {
439 for (ClientRecord cr : userRecord.mClientRecords) {
440 if (validatePackageName(uid, cr.mPackageName)) {
441 clientRecord = cr;
442 break;
443 }
Sungsoob3658562017-05-22 17:10:44 +0900444 }
445 }
446 }
447 if (clientRecord != null) {
448 try {
449 clientRecord.mClient.onRestoreRoute();
450 } catch (RemoteException e) {
451 Slog.w(TAG, "Failed to call onRestoreRoute. Client probably died.");
452 }
453 } else {
454 restoreBluetoothA2dp();
455 }
456 }
457
Jeff Brown69b07162013-11-07 00:30:16 -0800458 void switchUser() {
459 synchronized (mLock) {
460 int userId = ActivityManager.getCurrentUser();
461 if (mCurrentUserId != userId) {
462 final int oldUserId = mCurrentUserId;
463 mCurrentUserId = userId; // do this first
464
465 UserRecord oldUser = mUserRecords.get(oldUserId);
466 if (oldUser != null) {
467 oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
468 disposeUserIfNeededLocked(oldUser); // since no longer current user
469 }
470
471 UserRecord newUser = mUserRecords.get(userId);
472 if (newUser != null) {
473 newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
474 }
475 }
476 }
477 }
478
479 void clientDied(ClientRecord clientRecord) {
480 synchronized (mLock) {
481 unregisterClientLocked(clientRecord.mClient, true);
482 }
483 }
484
485 private void registerClientLocked(IMediaRouterClient client,
Sungsoob3658562017-05-22 17:10:44 +0900486 int uid, int pid, String packageName, int userId, boolean trusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800487 final IBinder binder = client.asBinder();
488 ClientRecord clientRecord = mAllClientRecords.get(binder);
489 if (clientRecord == null) {
490 boolean newUser = false;
491 UserRecord userRecord = mUserRecords.get(userId);
492 if (userRecord == null) {
493 userRecord = new UserRecord(userId);
494 newUser = true;
495 }
Sungsoob3658562017-05-22 17:10:44 +0900496 clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted);
Jeff Brown69b07162013-11-07 00:30:16 -0800497 try {
498 binder.linkToDeath(clientRecord, 0);
499 } catch (RemoteException ex) {
500 throw new RuntimeException("Media router client died prematurely.", ex);
501 }
502
503 if (newUser) {
504 mUserRecords.put(userId, userRecord);
505 initializeUserLocked(userRecord);
506 }
507
508 userRecord.mClientRecords.add(clientRecord);
509 mAllClientRecords.put(binder, clientRecord);
510 initializeClientLocked(clientRecord);
511 }
512 }
513
514 private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
515 ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
516 if (clientRecord != null) {
517 UserRecord userRecord = clientRecord.mUserRecord;
518 userRecord.mClientRecords.remove(clientRecord);
519 disposeClientLocked(clientRecord, died);
520 disposeUserIfNeededLocked(userRecord); // since client removed from user
521 }
522 }
523
524 private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
525 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
526 if (clientRecord != null) {
Jeff Brownaf574182013-11-14 18:16:08 -0800527 return clientRecord.getState();
Jeff Brown69b07162013-11-07 00:30:16 -0800528 }
529 return null;
530 }
531
532 private void setDiscoveryRequestLocked(IMediaRouterClient client,
533 int routeTypes, boolean activeScan) {
534 final IBinder binder = client.asBinder();
535 ClientRecord clientRecord = mAllClientRecords.get(binder);
536 if (clientRecord != null) {
Jeff Brownaf574182013-11-14 18:16:08 -0800537 // Only let the system discover remote display routes for now.
538 if (!clientRecord.mTrusted) {
539 routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
540 }
541
Jeff Brown69b07162013-11-07 00:30:16 -0800542 if (clientRecord.mRouteTypes != routeTypes
543 || clientRecord.mActiveScan != activeScan) {
544 if (DEBUG) {
545 Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
546 + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
547 }
548 clientRecord.mRouteTypes = routeTypes;
549 clientRecord.mActiveScan = activeScan;
550 clientRecord.mUserRecord.mHandler.sendEmptyMessage(
551 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
552 }
553 }
554 }
555
556 private void setSelectedRouteLocked(IMediaRouterClient client,
557 String routeId, boolean explicit) {
558 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
559 if (clientRecord != null) {
560 final String oldRouteId = clientRecord.mSelectedRouteId;
Kenny Roote6585b32013-12-13 12:00:26 -0800561 if (!Objects.equals(routeId, oldRouteId)) {
Jeff Brown69b07162013-11-07 00:30:16 -0800562 if (DEBUG) {
563 Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
564 + ", oldRouteId=" + oldRouteId
565 + ", explicit=" + explicit);
566 }
567
568 clientRecord.mSelectedRouteId = routeId;
Sungsood103e562017-03-09 15:35:07 +0900569 // Only let the system connect to new global routes for now.
570 // A similar check exists in the display manager for wifi display.
571 if (explicit && clientRecord.mTrusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800572 if (oldRouteId != null) {
573 clientRecord.mUserRecord.mHandler.obtainMessage(
574 UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
575 }
Sungsood103e562017-03-09 15:35:07 +0900576 if (routeId != null) {
Jeff Brown69b07162013-11-07 00:30:16 -0800577 clientRecord.mUserRecord.mHandler.obtainMessage(
578 UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
579 }
580 }
581 }
582 }
583 }
584
585 private void requestSetVolumeLocked(IMediaRouterClient client,
586 String routeId, int volume) {
587 final IBinder binder = client.asBinder();
588 ClientRecord clientRecord = mAllClientRecords.get(binder);
589 if (clientRecord != null) {
590 clientRecord.mUserRecord.mHandler.obtainMessage(
591 UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
592 }
593 }
594
595 private void requestUpdateVolumeLocked(IMediaRouterClient client,
596 String routeId, int direction) {
597 final IBinder binder = client.asBinder();
598 ClientRecord clientRecord = mAllClientRecords.get(binder);
599 if (clientRecord != null) {
600 clientRecord.mUserRecord.mHandler.obtainMessage(
601 UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
602 }
603 }
604
605 private void initializeUserLocked(UserRecord userRecord) {
606 if (DEBUG) {
607 Slog.d(TAG, userRecord + ": Initialized");
608 }
609 if (userRecord.mUserId == mCurrentUserId) {
610 userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
611 }
612 }
613
614 private void disposeUserIfNeededLocked(UserRecord userRecord) {
615 // If there are no records left and the user is no longer current then go ahead
616 // and purge the user record and all of its associated state. If the user is current
617 // then leave it alone since we might be connected to a route or want to query
618 // the same route information again soon.
619 if (userRecord.mUserId != mCurrentUserId
620 && userRecord.mClientRecords.isEmpty()) {
621 if (DEBUG) {
622 Slog.d(TAG, userRecord + ": Disposed");
623 }
624 mUserRecords.remove(userRecord.mUserId);
625 // Note: User already stopped (by switchUser) so no need to send stop message here.
626 }
627 }
628
629 private void initializeClientLocked(ClientRecord clientRecord) {
630 if (DEBUG) {
631 Slog.d(TAG, clientRecord + ": Registered");
632 }
633 }
634
635 private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
636 if (DEBUG) {
637 if (died) {
638 Slog.d(TAG, clientRecord + ": Died!");
639 } else {
640 Slog.d(TAG, clientRecord + ": Unregistered");
641 }
642 }
643 if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
644 clientRecord.mUserRecord.mHandler.sendEmptyMessage(
645 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
646 }
647 clientRecord.dispose();
648 }
649
650 private boolean validatePackageName(int uid, String packageName) {
651 if (packageName != null) {
652 String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
653 if (packageNames != null) {
654 for (String n : packageNames) {
655 if (n.equals(packageName)) {
656 return true;
657 }
658 }
659 }
660 }
661 return false;
662 }
663
664 /**
665 * Information about a particular client of the media router.
666 * The contents of this object is guarded by mLock.
667 */
668 final class ClientRecord implements DeathRecipient {
669 public final UserRecord mUserRecord;
670 public final IMediaRouterClient mClient;
Sungsoob3658562017-05-22 17:10:44 +0900671 public final int mUid;
Jeff Brown69b07162013-11-07 00:30:16 -0800672 public final int mPid;
673 public final String mPackageName;
Jeff Brownaf574182013-11-14 18:16:08 -0800674 public final boolean mTrusted;
Jeff Brown69b07162013-11-07 00:30:16 -0800675
676 public int mRouteTypes;
677 public boolean mActiveScan;
678 public String mSelectedRouteId;
679
680 public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
Sungsoob3658562017-05-22 17:10:44 +0900681 int uid, int pid, String packageName, boolean trusted) {
Jeff Brown69b07162013-11-07 00:30:16 -0800682 mUserRecord = userRecord;
683 mClient = client;
Sungsoob3658562017-05-22 17:10:44 +0900684 mUid = uid;
Jeff Brown69b07162013-11-07 00:30:16 -0800685 mPid = pid;
686 mPackageName = packageName;
Jeff Brownaf574182013-11-14 18:16:08 -0800687 mTrusted = trusted;
Jeff Brown69b07162013-11-07 00:30:16 -0800688 }
689
690 public void dispose() {
691 mClient.asBinder().unlinkToDeath(this, 0);
692 }
693
694 @Override
695 public void binderDied() {
696 clientDied(this);
697 }
698
Jeff Brownaf574182013-11-14 18:16:08 -0800699 MediaRouterClientState getState() {
Sungsood103e562017-03-09 15:35:07 +0900700 return mTrusted ? mUserRecord.mRouterState : null;
Jeff Brownaf574182013-11-14 18:16:08 -0800701 }
702
Jeff Brown69b07162013-11-07 00:30:16 -0800703 public void dump(PrintWriter pw, String prefix) {
704 pw.println(prefix + this);
705
706 final String indent = prefix + " ";
Jeff Brownaf574182013-11-14 18:16:08 -0800707 pw.println(indent + "mTrusted=" + mTrusted);
Jeff Brown69b07162013-11-07 00:30:16 -0800708 pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
709 pw.println(indent + "mActiveScan=" + mActiveScan);
710 pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
711 }
712
713 @Override
714 public String toString() {
715 return "Client " + mPackageName + " (pid " + mPid + ")";
716 }
717 }
718
719 /**
720 * Information about a particular user.
721 * The contents of this object is guarded by mLock.
722 */
723 final class UserRecord {
724 public final int mUserId;
725 public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>();
726 public final UserHandler mHandler;
Sungsood103e562017-03-09 15:35:07 +0900727 public MediaRouterClientState mRouterState;
Jeff Brown69b07162013-11-07 00:30:16 -0800728
729 public UserRecord(int userId) {
730 mUserId = userId;
731 mHandler = new UserHandler(MediaRouterService.this, this);
732 }
733
734 public void dump(final PrintWriter pw, String prefix) {
735 pw.println(prefix + this);
736
737 final String indent = prefix + " ";
738 final int clientCount = mClientRecords.size();
739 if (clientCount != 0) {
740 for (int i = 0; i < clientCount; i++) {
741 mClientRecords.get(i).dump(pw, indent);
742 }
743 } else {
744 pw.println(indent + "<no clients>");
745 }
746
Jeff Brownaf574182013-11-14 18:16:08 -0800747 pw.println(indent + "State");
Sungsood103e562017-03-09 15:35:07 +0900748 pw.println(indent + "mRouterState=" + mRouterState);
Jeff Brownaf574182013-11-14 18:16:08 -0800749
Jeff Brown69b07162013-11-07 00:30:16 -0800750 if (!mHandler.runWithScissors(new Runnable() {
751 @Override
752 public void run() {
753 mHandler.dump(pw, indent);
754 }
755 }, 1000)) {
756 pw.println(indent + "<could not dump handler state>");
757 }
758 }
759
760 @Override
761 public String toString() {
762 return "User " + mUserId;
763 }
764 }
765
766 /**
767 * Media router handler
768 * <p>
769 * Since remote display providers are designed to be single-threaded by nature,
770 * this class encapsulates all of the associated functionality and exports state
771 * to the service as it evolves.
772 * </p><p>
Jeff Brown69b07162013-11-07 00:30:16 -0800773 * This class is currently hardcoded to work with remote display providers but
774 * it is intended to be eventually extended to support more general route providers
775 * similar to the support library media router.
776 * </p>
777 */
778 static final class UserHandler extends Handler
779 implements RemoteDisplayProviderWatcher.Callback,
780 RemoteDisplayProviderProxy.Callback {
781 public static final int MSG_START = 1;
782 public static final int MSG_STOP = 2;
783 public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
784 public static final int MSG_SELECT_ROUTE = 4;
785 public static final int MSG_UNSELECT_ROUTE = 5;
786 public static final int MSG_REQUEST_SET_VOLUME = 6;
787 public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
788 private static final int MSG_UPDATE_CLIENT_STATE = 8;
789 private static final int MSG_CONNECTION_TIMED_OUT = 9;
790
791 private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
Jeff Brown39ad0e52013-11-11 17:55:08 -0800792 private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
793 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
794 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4;
795
796 // The relative order of these constants is important and expresses progress
797 // through the process of connecting to a route.
798 private static final int PHASE_NOT_AVAILABLE = -1;
799 private static final int PHASE_NOT_CONNECTED = 0;
800 private static final int PHASE_CONNECTING = 1;
801 private static final int PHASE_CONNECTED = 2;
Jeff Brown69b07162013-11-07 00:30:16 -0800802
803 private final MediaRouterService mService;
804 private final UserRecord mUserRecord;
805 private final RemoteDisplayProviderWatcher mWatcher;
806 private final ArrayList<ProviderRecord> mProviderRecords =
807 new ArrayList<ProviderRecord>();
808 private final ArrayList<IMediaRouterClient> mTempClients =
809 new ArrayList<IMediaRouterClient>();
810
811 private boolean mRunning;
812 private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
Sungsood103e562017-03-09 15:35:07 +0900813 private RouteRecord mSelectedRouteRecord;
Jeff Brown39ad0e52013-11-11 17:55:08 -0800814 private int mConnectionPhase = PHASE_NOT_AVAILABLE;
Jeff Brown69b07162013-11-07 00:30:16 -0800815 private int mConnectionTimeoutReason;
816 private long mConnectionTimeoutStartTime;
817 private boolean mClientStateUpdateScheduled;
818
819 public UserHandler(MediaRouterService service, UserRecord userRecord) {
820 super(Looper.getMainLooper(), null, true);
821 mService = service;
822 mUserRecord = userRecord;
823 mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
824 this, mUserRecord.mUserId);
825 }
826
827 @Override
828 public void handleMessage(Message msg) {
829 switch (msg.what) {
830 case MSG_START: {
831 start();
832 break;
833 }
834 case MSG_STOP: {
835 stop();
836 break;
837 }
838 case MSG_UPDATE_DISCOVERY_REQUEST: {
839 updateDiscoveryRequest();
840 break;
841 }
842 case MSG_SELECT_ROUTE: {
843 selectRoute((String)msg.obj);
844 break;
845 }
846 case MSG_UNSELECT_ROUTE: {
847 unselectRoute((String)msg.obj);
848 break;
849 }
850 case MSG_REQUEST_SET_VOLUME: {
851 requestSetVolume((String)msg.obj, msg.arg1);
852 break;
853 }
854 case MSG_REQUEST_UPDATE_VOLUME: {
855 requestUpdateVolume((String)msg.obj, msg.arg1);
856 break;
857 }
858 case MSG_UPDATE_CLIENT_STATE: {
859 updateClientState();
860 break;
861 }
862 case MSG_CONNECTION_TIMED_OUT: {
863 connectionTimedOut();
864 break;
865 }
866 }
867 }
868
869 public void dump(PrintWriter pw, String prefix) {
870 pw.println(prefix + "Handler");
871
872 final String indent = prefix + " ";
873 pw.println(indent + "mRunning=" + mRunning);
874 pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
Sungsood103e562017-03-09 15:35:07 +0900875 pw.println(indent + "mSelectedRouteRecord=" + mSelectedRouteRecord);
Jeff Brown39ad0e52013-11-11 17:55:08 -0800876 pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
Jeff Brown69b07162013-11-07 00:30:16 -0800877 pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
878 pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
879 TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
880
881 mWatcher.dump(pw, prefix);
882
883 final int providerCount = mProviderRecords.size();
884 if (providerCount != 0) {
885 for (int i = 0; i < providerCount; i++) {
886 mProviderRecords.get(i).dump(pw, prefix);
887 }
888 } else {
889 pw.println(indent + "<no providers>");
890 }
891 }
892
893 private void start() {
894 if (!mRunning) {
895 mRunning = true;
896 mWatcher.start(); // also starts all providers
897 }
898 }
899
900 private void stop() {
901 if (mRunning) {
902 mRunning = false;
Sungsood103e562017-03-09 15:35:07 +0900903 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -0800904 mWatcher.stop(); // also stops all providers
905 }
906 }
907
908 private void updateDiscoveryRequest() {
909 int routeTypes = 0;
910 boolean activeScan = false;
911 synchronized (mService.mLock) {
912 final int count = mUserRecord.mClientRecords.size();
913 for (int i = 0; i < count; i++) {
914 ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
915 routeTypes |= clientRecord.mRouteTypes;
916 activeScan |= clientRecord.mActiveScan;
917 }
918 }
919
920 final int newDiscoveryMode;
Jeff Brownaf574182013-11-14 18:16:08 -0800921 if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
Jeff Brown69b07162013-11-07 00:30:16 -0800922 if (activeScan) {
923 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
924 } else {
925 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
926 }
927 } else {
928 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
929 }
930
931 if (mDiscoveryMode != newDiscoveryMode) {
932 mDiscoveryMode = newDiscoveryMode;
933 final int count = mProviderRecords.size();
934 for (int i = 0; i < count; i++) {
935 mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
936 }
937 }
938 }
939
940 private void selectRoute(String routeId) {
941 if (routeId != null
Sungsood103e562017-03-09 15:35:07 +0900942 && (mSelectedRouteRecord == null
943 || !routeId.equals(mSelectedRouteRecord.getUniqueId()))) {
Jeff Brown69b07162013-11-07 00:30:16 -0800944 RouteRecord routeRecord = findRouteRecord(routeId);
945 if (routeRecord != null) {
Sungsood103e562017-03-09 15:35:07 +0900946 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -0800947
Sungsood103e562017-03-09 15:35:07 +0900948 Slog.i(TAG, "Selected route:" + routeRecord);
949 mSelectedRouteRecord = routeRecord;
950 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -0800951 routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
952
953 scheduleUpdateClientState();
954 }
955 }
956 }
957
958 private void unselectRoute(String routeId) {
959 if (routeId != null
Sungsood103e562017-03-09 15:35:07 +0900960 && mSelectedRouteRecord != null
961 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
962 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -0800963 }
964 }
965
Sungsood103e562017-03-09 15:35:07 +0900966 private void unselectSelectedRoute() {
967 if (mSelectedRouteRecord != null) {
968 Slog.i(TAG, "Unselected route:" + mSelectedRouteRecord);
969 mSelectedRouteRecord.getProvider().setSelectedDisplay(null);
970 mSelectedRouteRecord = null;
971 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -0800972
973 scheduleUpdateClientState();
974 }
975 }
976
977 private void requestSetVolume(String routeId, int volume) {
Sungsood103e562017-03-09 15:35:07 +0900978 if (mSelectedRouteRecord != null
979 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
980 mSelectedRouteRecord.getProvider().setDisplayVolume(volume);
Jeff Brown69b07162013-11-07 00:30:16 -0800981 }
982 }
983
984 private void requestUpdateVolume(String routeId, int direction) {
Sungsood103e562017-03-09 15:35:07 +0900985 if (mSelectedRouteRecord != null
986 && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
987 mSelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
Jeff Brown69b07162013-11-07 00:30:16 -0800988 }
989 }
990
991 @Override
992 public void addProvider(RemoteDisplayProviderProxy provider) {
993 provider.setCallback(this);
994 provider.setDiscoveryMode(mDiscoveryMode);
995 provider.setSelectedDisplay(null); // just to be safe
996
997 ProviderRecord providerRecord = new ProviderRecord(provider);
998 mProviderRecords.add(providerRecord);
999 providerRecord.updateDescriptor(provider.getDisplayState());
1000
1001 scheduleUpdateClientState();
1002 }
1003
1004 @Override
1005 public void removeProvider(RemoteDisplayProviderProxy provider) {
1006 int index = findProviderRecord(provider);
1007 if (index >= 0) {
1008 ProviderRecord providerRecord = mProviderRecords.remove(index);
1009 providerRecord.updateDescriptor(null); // mark routes invalid
1010 provider.setCallback(null);
1011 provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
1012
Sungsood103e562017-03-09 15:35:07 +09001013 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001014 scheduleUpdateClientState();
1015 }
1016 }
1017
1018 @Override
1019 public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
1020 RemoteDisplayState state) {
1021 updateProvider(provider, state);
1022 }
1023
1024 private void updateProvider(RemoteDisplayProviderProxy provider,
1025 RemoteDisplayState state) {
1026 int index = findProviderRecord(provider);
1027 if (index >= 0) {
1028 ProviderRecord providerRecord = mProviderRecords.get(index);
1029 if (providerRecord.updateDescriptor(state)) {
Sungsood103e562017-03-09 15:35:07 +09001030 checkSelectedRouteState();
Jeff Brown69b07162013-11-07 00:30:16 -08001031 scheduleUpdateClientState();
1032 }
1033 }
1034 }
1035
1036 /**
Sungsood103e562017-03-09 15:35:07 +09001037 * This function is called whenever the state of the selected route may have changed.
1038 * It checks the state and updates timeouts or unselects the route as appropriate.
Jeff Brown69b07162013-11-07 00:30:16 -08001039 */
Sungsood103e562017-03-09 15:35:07 +09001040 private void checkSelectedRouteState() {
Jeff Brown69b07162013-11-07 00:30:16 -08001041 // Unschedule timeouts when the route is unselected.
Sungsood103e562017-03-09 15:35:07 +09001042 if (mSelectedRouteRecord == null) {
Jeff Brown39ad0e52013-11-11 17:55:08 -08001043 mConnectionPhase = PHASE_NOT_AVAILABLE;
Jeff Brown69b07162013-11-07 00:30:16 -08001044 updateConnectionTimeout(0);
1045 return;
1046 }
1047
1048 // Ensure that the route is still present and enabled.
Sungsood103e562017-03-09 15:35:07 +09001049 if (!mSelectedRouteRecord.isValid()
1050 || !mSelectedRouteRecord.isEnabled()) {
Jeff Brown69b07162013-11-07 00:30:16 -08001051 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
1052 return;
1053 }
1054
Jeff Brown39ad0e52013-11-11 17:55:08 -08001055 // Make sure we haven't lost our connection.
1056 final int oldPhase = mConnectionPhase;
Sungsood103e562017-03-09 15:35:07 +09001057 mConnectionPhase = getConnectionPhase(mSelectedRouteRecord.getStatus());
Jeff Brown39ad0e52013-11-11 17:55:08 -08001058 if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
1059 updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
1060 return;
1061 }
1062
Jeff Brown69b07162013-11-07 00:30:16 -08001063 // Check the route status.
Jeff Brown39ad0e52013-11-11 17:55:08 -08001064 switch (mConnectionPhase) {
1065 case PHASE_CONNECTED:
1066 if (oldPhase != PHASE_CONNECTED) {
Sungsood103e562017-03-09 15:35:07 +09001067 Slog.i(TAG, "Connected to route: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001068 }
1069 updateConnectionTimeout(0);
1070 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001071 case PHASE_CONNECTING:
1072 if (oldPhase != PHASE_CONNECTING) {
Sungsood103e562017-03-09 15:35:07 +09001073 Slog.i(TAG, "Connecting to route: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001074 }
1075 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
1076 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001077 case PHASE_NOT_CONNECTED:
Jeff Brown69b07162013-11-07 00:30:16 -08001078 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
1079 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001080 case PHASE_NOT_AVAILABLE:
Jeff Brown69b07162013-11-07 00:30:16 -08001081 default:
1082 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
1083 break;
1084 }
1085 }
1086
1087 private void updateConnectionTimeout(int reason) {
1088 if (reason != mConnectionTimeoutReason) {
1089 if (mConnectionTimeoutReason != 0) {
1090 removeMessages(MSG_CONNECTION_TIMED_OUT);
1091 }
1092 mConnectionTimeoutReason = reason;
1093 mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
1094 switch (reason) {
1095 case TIMEOUT_REASON_NOT_AVAILABLE:
Jeff Brown39ad0e52013-11-11 17:55:08 -08001096 case TIMEOUT_REASON_CONNECTION_LOST:
1097 // Route became unavailable or connection lost.
1098 // Unselect it immediately.
Jeff Brown69b07162013-11-07 00:30:16 -08001099 sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
1100 break;
1101 case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
1102 // Waiting for route to start connecting.
1103 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
1104 break;
1105 case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
1106 // Waiting for route to complete connection.
1107 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
1108 break;
1109 }
1110 }
1111 }
1112
1113 private void connectionTimedOut() {
Sungsood103e562017-03-09 15:35:07 +09001114 if (mConnectionTimeoutReason == 0 || mSelectedRouteRecord == null) {
Jeff Brown69b07162013-11-07 00:30:16 -08001115 // Shouldn't get here. There must be a bug somewhere.
1116 Log.wtf(TAG, "Handled connection timeout for no reason.");
1117 return;
1118 }
1119
1120 switch (mConnectionTimeoutReason) {
1121 case TIMEOUT_REASON_NOT_AVAILABLE:
Sungsood103e562017-03-09 15:35:07 +09001122 Slog.i(TAG, "Selected route no longer available: "
1123 + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001124 break;
Jeff Brown39ad0e52013-11-11 17:55:08 -08001125 case TIMEOUT_REASON_CONNECTION_LOST:
Sungsood103e562017-03-09 15:35:07 +09001126 Slog.i(TAG, "Selected route connection lost: "
1127 + mSelectedRouteRecord);
Jeff Brown39ad0e52013-11-11 17:55:08 -08001128 break;
Jeff Brown69b07162013-11-07 00:30:16 -08001129 case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
Sungsood103e562017-03-09 15:35:07 +09001130 Slog.i(TAG, "Selected route timed out while waiting for "
Jeff Brown69b07162013-11-07 00:30:16 -08001131 + "connection attempt to begin after "
1132 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
Sungsood103e562017-03-09 15:35:07 +09001133 + " ms: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001134 break;
1135 case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
Sungsood103e562017-03-09 15:35:07 +09001136 Slog.i(TAG, "Selected route timed out while connecting after "
Jeff Brown69b07162013-11-07 00:30:16 -08001137 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
Sungsood103e562017-03-09 15:35:07 +09001138 + " ms: " + mSelectedRouteRecord);
Jeff Brown69b07162013-11-07 00:30:16 -08001139 break;
1140 }
1141 mConnectionTimeoutReason = 0;
1142
Sungsood103e562017-03-09 15:35:07 +09001143 unselectSelectedRoute();
Jeff Brown69b07162013-11-07 00:30:16 -08001144 }
1145
1146 private void scheduleUpdateClientState() {
1147 if (!mClientStateUpdateScheduled) {
1148 mClientStateUpdateScheduled = true;
1149 sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
1150 }
1151 }
1152
1153 private void updateClientState() {
1154 mClientStateUpdateScheduled = false;
1155
Jeff Brownaf574182013-11-14 18:16:08 -08001156 // Build a new client state for trusted clients.
Sungsood103e562017-03-09 15:35:07 +09001157 MediaRouterClientState routerState = new MediaRouterClientState();
Jeff Brown69b07162013-11-07 00:30:16 -08001158 final int providerCount = mProviderRecords.size();
1159 for (int i = 0; i < providerCount; i++) {
Sungsood103e562017-03-09 15:35:07 +09001160 mProviderRecords.get(i).appendClientState(routerState);
Jeff Brown69b07162013-11-07 00:30:16 -08001161 }
1162
1163 try {
1164 synchronized (mService.mLock) {
1165 // Update the UserRecord.
Sungsood103e562017-03-09 15:35:07 +09001166 mUserRecord.mRouterState = routerState;
Jeff Brown69b07162013-11-07 00:30:16 -08001167
1168 // Collect all clients.
1169 final int count = mUserRecord.mClientRecords.size();
1170 for (int i = 0; i < count; i++) {
1171 mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
1172 }
1173 }
1174
1175 // Notify all clients (outside of the lock).
1176 final int count = mTempClients.size();
1177 for (int i = 0; i < count; i++) {
1178 try {
1179 mTempClients.get(i).onStateChanged();
1180 } catch (RemoteException ex) {
Sungsoob3658562017-05-22 17:10:44 +09001181 Slog.w(TAG, "Failed to call onStateChanged. Client probably died.");
Jeff Brown69b07162013-11-07 00:30:16 -08001182 }
1183 }
1184 } finally {
1185 // Clear the list in preparation for the next time.
1186 mTempClients.clear();
1187 }
1188 }
1189
1190 private int findProviderRecord(RemoteDisplayProviderProxy provider) {
1191 final int count = mProviderRecords.size();
1192 for (int i = 0; i < count; i++) {
1193 ProviderRecord record = mProviderRecords.get(i);
1194 if (record.getProvider() == provider) {
1195 return i;
1196 }
1197 }
1198 return -1;
1199 }
1200
1201 private RouteRecord findRouteRecord(String uniqueId) {
1202 final int count = mProviderRecords.size();
1203 for (int i = 0; i < count; i++) {
1204 RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
1205 if (record != null) {
1206 return record;
1207 }
1208 }
1209 return null;
1210 }
1211
Jeff Brown39ad0e52013-11-11 17:55:08 -08001212 private static int getConnectionPhase(int status) {
1213 switch (status) {
1214 case MediaRouter.RouteInfo.STATUS_NONE:
1215 case MediaRouter.RouteInfo.STATUS_CONNECTED:
1216 return PHASE_CONNECTED;
1217 case MediaRouter.RouteInfo.STATUS_CONNECTING:
1218 return PHASE_CONNECTING;
1219 case MediaRouter.RouteInfo.STATUS_SCANNING:
1220 case MediaRouter.RouteInfo.STATUS_AVAILABLE:
1221 return PHASE_NOT_CONNECTED;
1222 case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
1223 case MediaRouter.RouteInfo.STATUS_IN_USE:
1224 default:
1225 return PHASE_NOT_AVAILABLE;
1226 }
1227 }
1228
Jeff Brown69b07162013-11-07 00:30:16 -08001229 static final class ProviderRecord {
1230 private final RemoteDisplayProviderProxy mProvider;
1231 private final String mUniquePrefix;
1232 private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
1233 private RemoteDisplayState mDescriptor;
1234
1235 public ProviderRecord(RemoteDisplayProviderProxy provider) {
1236 mProvider = provider;
1237 mUniquePrefix = provider.getFlattenedComponentName() + ":";
1238 }
1239
1240 public RemoteDisplayProviderProxy getProvider() {
1241 return mProvider;
1242 }
1243
1244 public String getUniquePrefix() {
1245 return mUniquePrefix;
1246 }
1247
1248 public boolean updateDescriptor(RemoteDisplayState descriptor) {
1249 boolean changed = false;
1250 if (mDescriptor != descriptor) {
1251 mDescriptor = descriptor;
1252
1253 // Update all existing routes and reorder them to match
1254 // the order of their descriptors.
1255 int targetIndex = 0;
1256 if (descriptor != null) {
1257 if (descriptor.isValid()) {
1258 final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
1259 final int routeCount = routeDescriptors.size();
1260 for (int i = 0; i < routeCount; i++) {
1261 final RemoteDisplayInfo routeDescriptor =
1262 routeDescriptors.get(i);
1263 final String descriptorId = routeDescriptor.id;
1264 final int sourceIndex = findRouteByDescriptorId(descriptorId);
1265 if (sourceIndex < 0) {
1266 // Add the route to the provider.
1267 String uniqueId = assignRouteUniqueId(descriptorId);
1268 RouteRecord route =
1269 new RouteRecord(this, descriptorId, uniqueId);
1270 mRoutes.add(targetIndex++, route);
1271 route.updateDescriptor(routeDescriptor);
1272 changed = true;
1273 } else if (sourceIndex < targetIndex) {
1274 // Ignore route with duplicate id.
1275 Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
1276 + routeDescriptor);
1277 } else {
1278 // Reorder existing route within the list.
1279 RouteRecord route = mRoutes.get(sourceIndex);
1280 Collections.swap(mRoutes, sourceIndex, targetIndex++);
1281 changed |= route.updateDescriptor(routeDescriptor);
1282 }
1283 }
1284 } else {
1285 Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
1286 + mProvider.getFlattenedComponentName());
1287 }
1288 }
1289
1290 // Dispose all remaining routes that do not have matching descriptors.
1291 for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
1292 RouteRecord route = mRoutes.remove(i);
1293 route.updateDescriptor(null); // mark route invalid
1294 changed = true;
1295 }
1296 }
1297 return changed;
1298 }
1299
1300 public void appendClientState(MediaRouterClientState state) {
1301 final int routeCount = mRoutes.size();
1302 for (int i = 0; i < routeCount; i++) {
1303 state.routes.add(mRoutes.get(i).getInfo());
1304 }
1305 }
1306
1307 public RouteRecord findRouteByUniqueId(String uniqueId) {
1308 final int routeCount = mRoutes.size();
1309 for (int i = 0; i < routeCount; i++) {
1310 RouteRecord route = mRoutes.get(i);
1311 if (route.getUniqueId().equals(uniqueId)) {
1312 return route;
1313 }
1314 }
1315 return null;
1316 }
1317
1318 private int findRouteByDescriptorId(String descriptorId) {
1319 final int routeCount = mRoutes.size();
1320 for (int i = 0; i < routeCount; i++) {
1321 RouteRecord route = mRoutes.get(i);
1322 if (route.getDescriptorId().equals(descriptorId)) {
1323 return i;
1324 }
1325 }
1326 return -1;
1327 }
1328
1329 public void dump(PrintWriter pw, String prefix) {
1330 pw.println(prefix + this);
1331
1332 final String indent = prefix + " ";
1333 mProvider.dump(pw, indent);
1334
1335 final int routeCount = mRoutes.size();
1336 if (routeCount != 0) {
1337 for (int i = 0; i < routeCount; i++) {
1338 mRoutes.get(i).dump(pw, indent);
1339 }
1340 } else {
1341 pw.println(indent + "<no routes>");
1342 }
1343 }
1344
1345 @Override
1346 public String toString() {
1347 return "Provider " + mProvider.getFlattenedComponentName();
1348 }
1349
1350 private String assignRouteUniqueId(String descriptorId) {
1351 return mUniquePrefix + descriptorId;
1352 }
1353 }
1354
1355 static final class RouteRecord {
1356 private final ProviderRecord mProviderRecord;
1357 private final String mDescriptorId;
1358 private final MediaRouterClientState.RouteInfo mMutableInfo;
1359 private MediaRouterClientState.RouteInfo mImmutableInfo;
1360 private RemoteDisplayInfo mDescriptor;
1361
1362 public RouteRecord(ProviderRecord providerRecord,
1363 String descriptorId, String uniqueId) {
1364 mProviderRecord = providerRecord;
1365 mDescriptorId = descriptorId;
1366 mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
1367 }
1368
1369 public RemoteDisplayProviderProxy getProvider() {
1370 return mProviderRecord.getProvider();
1371 }
1372
1373 public ProviderRecord getProviderRecord() {
1374 return mProviderRecord;
1375 }
1376
1377 public String getDescriptorId() {
1378 return mDescriptorId;
1379 }
1380
1381 public String getUniqueId() {
1382 return mMutableInfo.id;
1383 }
1384
1385 public MediaRouterClientState.RouteInfo getInfo() {
1386 if (mImmutableInfo == null) {
1387 mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
1388 }
1389 return mImmutableInfo;
1390 }
1391
1392 public boolean isValid() {
1393 return mDescriptor != null;
1394 }
1395
1396 public boolean isEnabled() {
1397 return mMutableInfo.enabled;
1398 }
1399
1400 public int getStatus() {
1401 return mMutableInfo.statusCode;
1402 }
1403
1404 public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
1405 boolean changed = false;
1406 if (mDescriptor != descriptor) {
1407 mDescriptor = descriptor;
1408 if (descriptor != null) {
1409 final String name = computeName(descriptor);
Kenny Roote6585b32013-12-13 12:00:26 -08001410 if (!Objects.equals(mMutableInfo.name, name)) {
Jeff Brown69b07162013-11-07 00:30:16 -08001411 mMutableInfo.name = name;
1412 changed = true;
1413 }
1414 final String description = computeDescription(descriptor);
Kenny Roote6585b32013-12-13 12:00:26 -08001415 if (!Objects.equals(mMutableInfo.description, description)) {
Jeff Brown69b07162013-11-07 00:30:16 -08001416 mMutableInfo.description = description;
1417 changed = true;
1418 }
1419 final int supportedTypes = computeSupportedTypes(descriptor);
1420 if (mMutableInfo.supportedTypes != supportedTypes) {
1421 mMutableInfo.supportedTypes = supportedTypes;
1422 changed = true;
1423 }
1424 final boolean enabled = computeEnabled(descriptor);
1425 if (mMutableInfo.enabled != enabled) {
1426 mMutableInfo.enabled = enabled;
1427 changed = true;
1428 }
1429 final int statusCode = computeStatusCode(descriptor);
1430 if (mMutableInfo.statusCode != statusCode) {
1431 mMutableInfo.statusCode = statusCode;
1432 changed = true;
1433 }
1434 final int playbackType = computePlaybackType(descriptor);
1435 if (mMutableInfo.playbackType != playbackType) {
1436 mMutableInfo.playbackType = playbackType;
1437 changed = true;
1438 }
1439 final int playbackStream = computePlaybackStream(descriptor);
1440 if (mMutableInfo.playbackStream != playbackStream) {
1441 mMutableInfo.playbackStream = playbackStream;
1442 changed = true;
1443 }
1444 final int volume = computeVolume(descriptor);
1445 if (mMutableInfo.volume != volume) {
1446 mMutableInfo.volume = volume;
1447 changed = true;
1448 }
1449 final int volumeMax = computeVolumeMax(descriptor);
1450 if (mMutableInfo.volumeMax != volumeMax) {
1451 mMutableInfo.volumeMax = volumeMax;
1452 changed = true;
1453 }
1454 final int volumeHandling = computeVolumeHandling(descriptor);
1455 if (mMutableInfo.volumeHandling != volumeHandling) {
1456 mMutableInfo.volumeHandling = volumeHandling;
1457 changed = true;
1458 }
1459 final int presentationDisplayId = computePresentationDisplayId(descriptor);
1460 if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
1461 mMutableInfo.presentationDisplayId = presentationDisplayId;
1462 changed = true;
1463 }
1464 }
1465 }
1466 if (changed) {
1467 mImmutableInfo = null;
1468 }
1469 return changed;
1470 }
1471
1472 public void dump(PrintWriter pw, String prefix) {
1473 pw.println(prefix + this);
1474
1475 final String indent = prefix + " ";
1476 pw.println(indent + "mMutableInfo=" + mMutableInfo);
1477 pw.println(indent + "mDescriptorId=" + mDescriptorId);
1478 pw.println(indent + "mDescriptor=" + mDescriptor);
1479 }
1480
1481 @Override
1482 public String toString() {
1483 return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
1484 }
1485
1486 private static String computeName(RemoteDisplayInfo descriptor) {
1487 // Note that isValid() already ensures the name is non-empty.
1488 return descriptor.name;
1489 }
1490
1491 private static String computeDescription(RemoteDisplayInfo descriptor) {
1492 final String description = descriptor.description;
1493 return TextUtils.isEmpty(description) ? null : description;
1494 }
1495
1496 private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
1497 return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
1498 | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
1499 | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
1500 }
1501
1502 private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
1503 switch (descriptor.status) {
1504 case RemoteDisplayInfo.STATUS_CONNECTED:
1505 case RemoteDisplayInfo.STATUS_CONNECTING:
1506 case RemoteDisplayInfo.STATUS_AVAILABLE:
1507 return true;
1508 default:
1509 return false;
1510 }
1511 }
1512
1513 private static int computeStatusCode(RemoteDisplayInfo descriptor) {
1514 switch (descriptor.status) {
1515 case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
1516 return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
1517 case RemoteDisplayInfo.STATUS_AVAILABLE:
1518 return MediaRouter.RouteInfo.STATUS_AVAILABLE;
1519 case RemoteDisplayInfo.STATUS_IN_USE:
1520 return MediaRouter.RouteInfo.STATUS_IN_USE;
1521 case RemoteDisplayInfo.STATUS_CONNECTING:
1522 return MediaRouter.RouteInfo.STATUS_CONNECTING;
1523 case RemoteDisplayInfo.STATUS_CONNECTED:
1524 return MediaRouter.RouteInfo.STATUS_CONNECTED;
1525 default:
1526 return MediaRouter.RouteInfo.STATUS_NONE;
1527 }
1528 }
1529
1530 private static int computePlaybackType(RemoteDisplayInfo descriptor) {
1531 return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
1532 }
1533
1534 private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
1535 return AudioSystem.STREAM_MUSIC;
1536 }
1537
1538 private static int computeVolume(RemoteDisplayInfo descriptor) {
1539 final int volume = descriptor.volume;
1540 final int volumeMax = descriptor.volumeMax;
1541 if (volume < 0) {
1542 return 0;
1543 } else if (volume > volumeMax) {
1544 return volumeMax;
1545 }
1546 return volume;
1547 }
1548
1549 private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
1550 final int volumeMax = descriptor.volumeMax;
1551 return volumeMax > 0 ? volumeMax : 0;
1552 }
1553
1554 private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
1555 final int volumeHandling = descriptor.volumeHandling;
1556 switch (volumeHandling) {
1557 case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
1558 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
1559 case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
1560 default:
1561 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
1562 }
1563 }
1564
1565 private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
1566 // The MediaRouter class validates that the id corresponds to an extant
1567 // presentation display. So all we do here is canonicalize the null case.
1568 final int displayId = descriptor.presentationDisplayId;
1569 return displayId < 0 ? -1 : displayId;
1570 }
1571 }
1572 }
1573}