blob: 7820cd705193fac36ebf5b01f5737391791d07c9 [file] [log] [blame]
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001/*
2 * Copyright 2019 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
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +090019import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
20
Kyunglyul Hyundafac542019-04-29 18:07:21 +090021import android.annotation.NonNull;
22import android.annotation.Nullable;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090023import android.app.ActivityManager;
24import android.content.Context;
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +090025import android.content.Intent;
Kyunglyul Hyundafac542019-04-29 18:07:21 +090026import android.content.pm.PackageManager;
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +090027import android.media.IMediaRouter2Client;
Kyunglyul Hyun3aedf022019-04-15 16:38:19 +090028import android.media.IMediaRouter2Manager;
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +090029import android.media.IMediaRouterClient;
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +090030import android.media.MediaRoute2Info;
Kyunglyul Hyun3aedf022019-04-15 16:38:19 +090031import android.media.MediaRoute2ProviderInfo;
Hyundo Moon4140fee2019-11-08 14:47:50 +090032import android.media.MediaRouter2;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090033import android.os.Binder;
Hyundo Moon4140fee2019-11-08 14:47:50 +090034import android.os.Bundle;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090035import android.os.Handler;
36import android.os.IBinder;
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +090037import android.os.Looper;
Hyundo Moon4140fee2019-11-08 14:47:50 +090038import android.os.Message;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090039import android.os.RemoteException;
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +090040import android.os.UserHandle;
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +090041import android.text.TextUtils;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090042import android.util.ArrayMap;
43import android.util.Log;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090044import android.util.Slog;
45import android.util.SparseArray;
46
Kyunglyul Hyundafac542019-04-29 18:07:21 +090047import com.android.internal.annotations.GuardedBy;
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +090048import com.android.internal.util.function.pooled.PooledLambda;
Kyunglyul Hyundafac542019-04-29 18:07:21 +090049
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090050import java.io.PrintWriter;
51import java.lang.ref.WeakReference;
52import java.util.ArrayList;
Kyunglyul Hyun7af73012019-10-11 20:01:30 +090053import java.util.Collection;
Kyunglyul Hyundafac542019-04-29 18:07:21 +090054import java.util.Collections;
Kyunglyul Hyun7af73012019-10-11 20:01:30 +090055import java.util.HashSet;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090056import java.util.List;
Kyunglyul Hyundafac542019-04-29 18:07:21 +090057import java.util.Objects;
Kyunglyul Hyun7af73012019-10-11 20:01:30 +090058import java.util.Set;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090059
60/**
61 * TODO: Merge this to MediaRouterService once it's finished.
62 */
63class MediaRouter2ServiceImpl {
Hyundo Moon7d3ab332019-10-16 11:09:56 +090064 private static final String TAG = "MR2ServiceImpl";
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090065 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Hyundo Moon4140fee2019-11-08 14:47:50 +090066 private static final long ROUTE_SELECTION_REQUEST_TIMEOUT_MS = 5000L;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090067
68 private final Context mContext;
69 private final Object mLock = new Object();
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090070
Kyunglyul Hyundafac542019-04-29 18:07:21 +090071 @GuardedBy("mLock")
72 private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
73 @GuardedBy("mLock")
74 private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>();
75 @GuardedBy("mLock")
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090076 private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
Kyunglyul Hyundafac542019-04-29 18:07:21 +090077 @GuardedBy("mLock")
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090078 private int mCurrentUserId = -1;
Hyundo Moon4140fee2019-11-08 14:47:50 +090079 @GuardedBy("mLock")
80 private int mSelectRouteRequestSequenceNumber = 0;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +090081
82 MediaRouter2ServiceImpl(Context context) {
83 mContext = context;
84 }
85
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +090086 public void registerClient(@NonNull IMediaRouter2Client client,
87 @NonNull String packageName) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +090088 Objects.requireNonNull(client, "client must not be null");
89
90 final int uid = Binder.getCallingUid();
91 final int pid = Binder.getCallingPid();
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +090092 final int userId = UserHandle.getUserId(uid);
Kyunglyul Hyundafac542019-04-29 18:07:21 +090093 final boolean trusted = mContext.checkCallingOrSelfPermission(
94 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
95 == PackageManager.PERMISSION_GRANTED;
96 final long token = Binder.clearCallingIdentity();
97 try {
98 synchronized (mLock) {
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +090099 registerClient2Locked(client, uid, pid, packageName, userId, trusted);
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900100 }
101 } finally {
102 Binder.restoreCallingIdentity(token);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900103 }
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900104 }
105
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900106 public void unregisterClient(@NonNull IMediaRouter2Client client) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900107 Objects.requireNonNull(client, "client must not be null");
108
109 final long token = Binder.clearCallingIdentity();
110 try {
111 synchronized (mLock) {
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900112 unregisterClient2Locked(client, false);
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900113 }
114 } finally {
115 Binder.restoreCallingIdentity(token);
116 }
117 }
118
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900119 public void registerManager(@NonNull IMediaRouter2Manager manager,
120 @NonNull String packageName) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900121 Objects.requireNonNull(manager, "manager must not be null");
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900122 //TODO: should check permission
123 final boolean trusted = true;
124
125 final int uid = Binder.getCallingUid();
126 final int pid = Binder.getCallingPid();
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900127 final int userId = UserHandle.getUserId(uid);
128
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900129 final long token = Binder.clearCallingIdentity();
130 try {
131 synchronized (mLock) {
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900132 registerManagerLocked(manager, uid, pid, packageName, userId, trusted);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900133 }
134 } finally {
135 Binder.restoreCallingIdentity(token);
136 }
137 }
138
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900139 public void unregisterManager(@NonNull IMediaRouter2Manager manager) {
140 Objects.requireNonNull(manager, "manager must not be null");
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900141
142 final long token = Binder.clearCallingIdentity();
143 try {
144 synchronized (mLock) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900145 unregisterManagerLocked(manager, false);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900146 }
147 } finally {
148 Binder.restoreCallingIdentity(token);
149 }
150 }
151
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900152 public void sendControlRequest(@NonNull IMediaRouter2Client client,
153 @NonNull MediaRoute2Info route, @NonNull Intent request) {
154 Objects.requireNonNull(client, "client must not be null");
155 Objects.requireNonNull(route, "route must not be null");
156 Objects.requireNonNull(request, "request must not be null");
157
158 final long token = Binder.clearCallingIdentity();
159 try {
160 synchronized (mLock) {
161 sendControlRequestLocked(client, route, request);
162 }
163 } finally {
164 Binder.restoreCallingIdentity(token);
165 }
166 }
167
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900168 //TODO: What would happen if a media app used MediaRouter and MediaRouter2 simultaneously?
169 public void setControlCategories(@NonNull IMediaRouterClient client,
170 @Nullable List<String> categories) {
171 Objects.requireNonNull(client, "client must not be null");
172 final long token = Binder.clearCallingIdentity();
173 try {
174 synchronized (mLock) {
175 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
176 setControlCategoriesLocked(clientRecord, categories);
177 }
178 } finally {
179 Binder.restoreCallingIdentity(token);
180 }
181 }
182
183 public void setControlCategories2(@NonNull IMediaRouter2Client client,
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900184 @Nullable List<String> categories) {
185 Objects.requireNonNull(client, "client must not be null");
186
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900187 final long token = Binder.clearCallingIdentity();
188 try {
189 synchronized (mLock) {
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900190 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
191 setControlCategoriesLocked(clientRecord, categories);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900192 }
193 } finally {
194 Binder.restoreCallingIdentity(token);
195 }
196 }
197
Hyundo Moon4140fee2019-11-08 14:47:50 +0900198 public void requestSelectRoute2(@NonNull IMediaRouter2Client client,
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900199 @Nullable MediaRoute2Info route) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900200 final long token = Binder.clearCallingIdentity();
201 try {
202 synchronized (mLock) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900203 requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), route);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900204 }
205 } finally {
206 Binder.restoreCallingIdentity(token);
207 }
208 }
209
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900210 public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
211 Objects.requireNonNull(client, "client must not be null");
212 Objects.requireNonNull(route, "route must not be null");
213
214 final long token = Binder.clearCallingIdentity();
215 try {
216 synchronized (mLock) {
217 requestSetVolumeLocked(client, route, volume);
218 }
219 } finally {
220 Binder.restoreCallingIdentity(token);
221 }
222 }
223
224 public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
225 Objects.requireNonNull(client, "client must not be null");
226 Objects.requireNonNull(route, "route must not be null");
227
228 final long token = Binder.clearCallingIdentity();
229 try {
230 synchronized (mLock) {
231 requestUpdateVolumeLocked(client, route, delta);
232 }
233 } finally {
234 Binder.restoreCallingIdentity(token);
235 }
236 }
237
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900238 public void selectClientRoute2(@NonNull IMediaRouter2Manager manager,
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900239 String packageName, @Nullable MediaRoute2Info route) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900240 final long token = Binder.clearCallingIdentity();
241 try {
242 synchronized (mLock) {
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900243 selectClientRoute2Locked(manager, packageName, route);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900244 }
245 } finally {
246 Binder.restoreCallingIdentity(token);
247 }
248 }
249
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900250 public void requestSetVolume2Manager(IMediaRouter2Manager manager,
251 MediaRoute2Info route, int volume) {
252 Objects.requireNonNull(manager, "manager must not be null");
253 Objects.requireNonNull(route, "route must not be null");
254
255 final long token = Binder.clearCallingIdentity();
256 try {
257 synchronized (mLock) {
258 requestSetVolumeLocked(manager, route, volume);
259 }
260 } finally {
261 Binder.restoreCallingIdentity(token);
262 }
263 }
264
265 public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
266 MediaRoute2Info route, int delta) {
267 Objects.requireNonNull(manager, "manager must not be null");
268 Objects.requireNonNull(route, "route must not be null");
269
270 final long token = Binder.clearCallingIdentity();
271 try {
272 synchronized (mLock) {
273 requestUpdateVolumeLocked(manager, route, delta);
274 }
275 } finally {
276 Binder.restoreCallingIdentity(token);
277 }
278 }
279
280
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900281 public void registerClient(@NonNull IMediaRouterClient client, @NonNull String packageName) {
282 Objects.requireNonNull(client, "client must not be null");
283
284 final int uid = Binder.getCallingUid();
285 final int pid = Binder.getCallingPid();
286 final int userId = UserHandle.getUserId(uid);
287
288 final long token = Binder.clearCallingIdentity();
289 try {
290 synchronized (mLock) {
291 registerClient1Locked(client, packageName, userId);
292 }
293 } finally {
294 Binder.restoreCallingIdentity(token);
295 }
296 }
297
298 public void unregisterClient(@NonNull IMediaRouterClient client) {
299 Objects.requireNonNull(client, "client must not be null");
300
301 final long token = Binder.clearCallingIdentity();
302 try {
303 synchronized (mLock) {
304 unregisterClient1Locked(client);
305 }
306 } finally {
307 Binder.restoreCallingIdentity(token);
308 }
309 }
310
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900311 //TODO: Review this is handling multi-user properly.
312 void switchUser() {
313 synchronized (mLock) {
314 int userId = ActivityManager.getCurrentUser();
315 if (mCurrentUserId != userId) {
316 final int oldUserId = mCurrentUserId;
317 mCurrentUserId = userId; // do this first
318
319 UserRecord oldUser = mUserRecords.get(oldUserId);
320 if (oldUser != null) {
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900321 oldUser.mHandler.sendMessage(
322 obtainMessage(UserHandler::stop, oldUser.mHandler));
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900323 disposeUserIfNeededLocked(oldUser); // since no longer current user
324 }
325
326 UserRecord newUser = mUserRecords.get(userId);
327 if (newUser != null) {
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900328 newUser.mHandler.sendMessage(
329 obtainMessage(UserHandler::start, newUser.mHandler));
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900330 }
331 }
332 }
333 }
334
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900335 void clientDied(Client2Record clientRecord) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900336 synchronized (mLock) {
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900337 unregisterClient2Locked(clientRecord.mClient, true);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900338 }
339 }
340
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900341 void managerDied(ManagerRecord managerRecord) {
342 synchronized (mLock) {
343 unregisterManagerLocked(managerRecord.mManager, true);
344 }
345 }
346
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900347 private void registerClient2Locked(IMediaRouter2Client client,
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900348 int uid, int pid, String packageName, int userId, boolean trusted) {
349 final IBinder binder = client.asBinder();
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900350 if (mAllClientRecords.get(binder) == null) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900351 UserRecord userRecord = mUserRecords.get(userId);
352 if (userRecord == null) {
353 userRecord = new UserRecord(userId);
Hyundo Moon7d3ab332019-10-16 11:09:56 +0900354 mUserRecords.put(userId, userRecord);
355 initializeUserLocked(userRecord);
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900356 }
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900357 Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid,
358 packageName, trusted);
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900359 try {
360 binder.linkToDeath(clientRecord, 0);
361 } catch (RemoteException ex) {
362 throw new RuntimeException("Media router client died prematurely.", ex);
363 }
364
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900365 userRecord.mClientRecords.add(clientRecord);
366 mAllClientRecords.put(binder, clientRecord);
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900367
368 userRecord.mHandler.sendMessage(
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900369 obtainMessage(UserHandler::notifyRoutesToClient, userRecord.mHandler, client));
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900370 }
371 }
372
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900373 private void unregisterClient2Locked(IMediaRouter2Client client, boolean died) {
374 Client2Record clientRecord = (Client2Record) mAllClientRecords.remove(client.asBinder());
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900375 if (clientRecord != null) {
376 UserRecord userRecord = clientRecord.mUserRecord;
377 userRecord.mClientRecords.remove(clientRecord);
378 //TODO: update discovery request
379 clientRecord.dispose();
380 disposeUserIfNeededLocked(userRecord); // since client removed from user
381 }
382 }
383
Hyundo Moon4140fee2019-11-08 14:47:50 +0900384 private void requestSelectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900385 if (clientRecord != null) {
386 MediaRoute2Info oldRoute = clientRecord.mSelectedRoute;
Hyundo Moon4140fee2019-11-08 14:47:50 +0900387 clientRecord.mSelectingRoute = route;
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900388
389 UserHandler handler = clientRecord.mUserRecord.mHandler;
390 //TODO: Handle transfer instead of unselect and select
391 if (oldRoute != null) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900392 handler.sendMessage(obtainMessage(
393 UserHandler::unselectRoute, handler, clientRecord.mPackageName, oldRoute));
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900394 }
395 if (route != null) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900396 final int seq = mSelectRouteRequestSequenceNumber;
397 mSelectRouteRequestSequenceNumber++;
398
399 handler.sendMessage(obtainMessage(
400 UserHandler::requestSelectRoute, handler, clientRecord.mPackageName,
401 route, seq));
402
403 // Remove all previous timeout messages
404 for (int previousSeq : clientRecord.mSelectRouteSequenceNumbers) {
405 clientRecord.mUserRecord.mHandler.removeMessages(previousSeq);
406 }
407 clientRecord.mSelectRouteSequenceNumbers.clear();
408
409 // When the request is not handled in timeout, set the client's route to default.
410 Message timeoutMsg = obtainMessage(UserHandler::handleRouteSelectionTimeout,
411 handler, clientRecord.mPackageName, route);
412 timeoutMsg.what = seq; // Make the message cancelable.
413 handler.sendMessageDelayed(timeoutMsg, ROUTE_SELECTION_REQUEST_TIMEOUT_MS);
414 clientRecord.mSelectRouteSequenceNumbers.add(seq);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900415 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900416 }
417 }
418
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900419 private void setControlCategoriesLocked(ClientRecord clientRecord, List<String> categories) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900420 if (clientRecord != null) {
421 clientRecord.mControlCategories = categories;
422
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900423 clientRecord.mUserRecord.mHandler.sendMessage(
424 obtainMessage(UserHandler::updateClientUsage,
425 clientRecord.mUserRecord.mHandler, clientRecord));
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900426 }
427 }
428
429 private void sendControlRequestLocked(IMediaRouter2Client client, MediaRoute2Info route,
430 Intent request) {
431 final IBinder binder = client.asBinder();
432 ClientRecord clientRecord = mAllClientRecords.get(binder);
433
434 if (clientRecord != null) {
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900435 clientRecord.mUserRecord.mHandler.sendMessage(
436 obtainMessage(UserHandler::sendControlRequest,
437 clientRecord.mUserRecord.mHandler, route, request));
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900438 }
439 }
440
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900441 private void requestSetVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
442 int volume) {
443 final IBinder binder = client.asBinder();
444 ClientRecord clientRecord = mAllClientRecords.get(binder);
445
446 if (clientRecord != null) {
447 clientRecord.mUserRecord.mHandler.sendMessage(
448 obtainMessage(UserHandler::requestSetVolume,
449 clientRecord.mUserRecord.mHandler, route, volume));
450 }
451 }
452
453 private void requestUpdateVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
454 int delta) {
455 final IBinder binder = client.asBinder();
456 ClientRecord clientRecord = mAllClientRecords.get(binder);
457
458 if (clientRecord != null) {
459 clientRecord.mUserRecord.mHandler.sendMessage(
460 obtainMessage(UserHandler::requestUpdateVolume,
461 clientRecord.mUserRecord.mHandler, route, delta));
462 }
463 }
464
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900465 private void registerManagerLocked(IMediaRouter2Manager manager,
466 int uid, int pid, String packageName, int userId, boolean trusted) {
467 final IBinder binder = manager.asBinder();
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900468 ManagerRecord managerRecord = mAllManagerRecords.get(binder);
469 if (managerRecord == null) {
470 boolean newUser = false;
471 UserRecord userRecord = mUserRecords.get(userId);
472 if (userRecord == null) {
473 userRecord = new UserRecord(userId);
474 newUser = true;
475 }
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900476 managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName, trusted);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900477 try {
478 binder.linkToDeath(managerRecord, 0);
479 } catch (RemoteException ex) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900480 throw new RuntimeException("Media router manager died prematurely.", ex);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900481 }
482
483 if (newUser) {
484 mUserRecords.put(userId, userRecord);
485 initializeUserLocked(userRecord);
486 }
487
488 userRecord.mManagerRecords.add(managerRecord);
489 mAllManagerRecords.put(binder, managerRecord);
490
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900491 userRecord.mHandler.sendMessage(
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +0900492 obtainMessage(UserHandler::notifyRoutesToManager,
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900493 userRecord.mHandler, manager));
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900494
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900495 for (ClientRecord clientRecord : userRecord.mClientRecords) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900496 // TODO: Do not use updateClientUsage since it updates all managers.
497 // Instead, Notify only to the manager that is currently being registered.
498
499 // TODO: UserRecord <-> ClientRecord, why do they reference each other?
500 // How about removing mUserRecord from clientRecord?
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900501 clientRecord.mUserRecord.mHandler.sendMessage(
502 obtainMessage(UserHandler::updateClientUsage,
503 clientRecord.mUserRecord.mHandler, clientRecord));
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900504 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900505 }
506 }
507
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900508 private void unregisterManagerLocked(IMediaRouter2Manager manager, boolean died) {
509 ManagerRecord managerRecord = mAllManagerRecords.remove(manager.asBinder());
510 if (managerRecord != null) {
511 UserRecord userRecord = managerRecord.mUserRecord;
512 userRecord.mManagerRecords.remove(managerRecord);
513 managerRecord.dispose();
514 disposeUserIfNeededLocked(userRecord); // since manager removed from user
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900515 }
516 }
517
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900518 private void selectClientRoute2Locked(IMediaRouter2Manager manager,
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900519 String packageName, MediaRoute2Info route) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900520 ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900521 if (managerRecord != null) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900522 ClientRecord clientRecord =
523 managerRecord.mUserRecord.findClientRecordLocked(packageName);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900524 if (clientRecord == null) {
525 Slog.w(TAG, "Ignoring route selection for unknown client.");
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900526 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900527 if (clientRecord != null && managerRecord.mTrusted) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900528 requestSelectRoute2Locked(clientRecord, route);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900529 }
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900530 }
531 }
532
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900533 private void requestSetVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
534 int volume) {
535 final IBinder binder = manager.asBinder();
536 ManagerRecord managerRecord = mAllManagerRecords.get(binder);
537
538 if (managerRecord != null) {
539 managerRecord.mUserRecord.mHandler.sendMessage(
540 obtainMessage(UserHandler::requestSetVolume,
541 managerRecord.mUserRecord.mHandler, route, volume));
542 }
543 }
544
545 private void requestUpdateVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
546 int delta) {
547 final IBinder binder = manager.asBinder();
548 ManagerRecord managerRecord = mAllManagerRecords.get(binder);
549
550 if (managerRecord != null) {
551 managerRecord.mUserRecord.mHandler.sendMessage(
552 obtainMessage(UserHandler::requestUpdateVolume,
553 managerRecord.mUserRecord.mHandler, route, delta));
554 }
555 }
556
557
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900558 private void initializeUserLocked(UserRecord userRecord) {
559 if (DEBUG) {
560 Slog.d(TAG, userRecord + ": Initialized");
561 }
562 if (userRecord.mUserId == mCurrentUserId) {
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900563 userRecord.mHandler.sendMessage(
564 obtainMessage(UserHandler::start, userRecord.mHandler));
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900565 }
566 }
567
568 private void disposeUserIfNeededLocked(UserRecord userRecord) {
569 // If there are no records left and the user is no longer current then go ahead
570 // and purge the user record and all of its associated state. If the user is current
571 // then leave it alone since we might be connected to a route or want to query
572 // the same route information again soon.
573 if (userRecord.mUserId != mCurrentUserId
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900574 && userRecord.mClientRecords.isEmpty()
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900575 && userRecord.mManagerRecords.isEmpty()) {
576 if (DEBUG) {
577 Slog.d(TAG, userRecord + ": Disposed");
578 }
579 mUserRecords.remove(userRecord.mUserId);
580 // Note: User already stopped (by switchUser) so no need to send stop message here.
581 }
582 }
583
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900584 private void registerClient1Locked(IMediaRouterClient client, String packageName,
585 int userId) {
586 final IBinder binder = client.asBinder();
587 if (mAllClientRecords.get(binder) == null) {
588 boolean newUser = false;
589 UserRecord userRecord = mUserRecords.get(userId);
590 if (userRecord == null) {
591 userRecord = new UserRecord(userId);
592 newUser = true;
593 }
594 ClientRecord clientRecord = new Client1Record(userRecord, client, packageName);
595
596 if (newUser) {
597 mUserRecords.put(userId, userRecord);
598 initializeUserLocked(userRecord);
599 }
600
601 userRecord.mClientRecords.add(clientRecord);
602 mAllClientRecords.put(binder, clientRecord);
603 }
604 }
605
606 private void unregisterClient1Locked(IMediaRouterClient client) {
607 ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
608 if (clientRecord != null) {
609 UserRecord userRecord = clientRecord.mUserRecord;
610 userRecord.mClientRecords.remove(clientRecord);
611 disposeUserIfNeededLocked(userRecord);
612 }
613 }
614
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900615 final class UserRecord {
616 public final int mUserId;
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900617 //TODO: make records private for thread-safety
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900618 final ArrayList<ClientRecord> mClientRecords = new ArrayList<>();
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900619 final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
620 final UserHandler mHandler;
621
622 UserRecord(int userId) {
623 mUserId = userId;
624 mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this);
625 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900626
Hyundo Moon4140fee2019-11-08 14:47:50 +0900627 ClientRecord findClientRecordLocked(String packageName) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900628 for (ClientRecord clientRecord : mClientRecords) {
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900629 if (TextUtils.equals(clientRecord.mPackageName, packageName)) {
630 return clientRecord;
631 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900632 }
633 return null;
634 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900635 }
636
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900637 class ClientRecord {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900638 public final UserRecord mUserRecord;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900639 public final String mPackageName;
Hyundo Moon4140fee2019-11-08 14:47:50 +0900640 public final List<Integer> mSelectRouteSequenceNumbers;
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900641 public List<String> mControlCategories;
Hyundo Moon4140fee2019-11-08 14:47:50 +0900642 public MediaRoute2Info mSelectingRoute;
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900643 public MediaRoute2Info mSelectedRoute;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900644
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900645 ClientRecord(UserRecord userRecord, String packageName) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900646 mUserRecord = userRecord;
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900647 mPackageName = packageName;
Hyundo Moon4140fee2019-11-08 14:47:50 +0900648 mSelectRouteSequenceNumbers = new ArrayList<>();
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900649 mControlCategories = Collections.emptyList();
650 }
651 }
652
653 final class Client1Record extends ClientRecord {
654 public final IMediaRouterClient mClient;
655
656 Client1Record(UserRecord userRecord, IMediaRouterClient client,
657 String packageName) {
658 super(userRecord, packageName);
659 mClient = client;
660 }
661 }
662
663 final class Client2Record extends ClientRecord
664 implements IBinder.DeathRecipient {
665 public final IMediaRouter2Client mClient;
666 public final int mUid;
667 public final int mPid;
668 public final boolean mTrusted;
669
670 Client2Record(UserRecord userRecord, IMediaRouter2Client client,
671 int uid, int pid, String packageName, boolean trusted) {
672 super(userRecord, packageName);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900673 mClient = client;
674 mUid = uid;
675 mPid = pid;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900676 mTrusted = trusted;
677 }
678
679 public void dispose() {
680 mClient.asBinder().unlinkToDeath(this, 0);
681 }
682
683 @Override
684 public void binderDied() {
685 clientDied(this);
686 }
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900687 }
688
689 final class ManagerRecord implements IBinder.DeathRecipient {
690 public final UserRecord mUserRecord;
691 public final IMediaRouter2Manager mManager;
692 public final int mUid;
693 public final int mPid;
694 public final String mPackageName;
695 public final boolean mTrusted;
696
697 ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager,
698 int uid, int pid, String packageName, boolean trusted) {
699 mUserRecord = userRecord;
700 mManager = manager;
701 mUid = uid;
702 mPid = pid;
703 mPackageName = packageName;
704 mTrusted = trusted;
705 }
706
707 public void dispose() {
708 mManager.asBinder().unlinkToDeath(this, 0);
709 }
710
711 @Override
712 public void binderDied() {
713 managerDied(this);
714 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900715
716 public void dump(PrintWriter pw, String prefix) {
717 pw.println(prefix + this);
718
719 final String indent = prefix + " ";
720 pw.println(indent + "mTrusted=" + mTrusted);
721 }
722
723 @Override
724 public String toString() {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900725 return "Manager " + mPackageName + " (pid " + mPid + ")";
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900726 }
727 }
728
729 static final class UserHandler extends Handler implements
730 MediaRoute2ProviderWatcher.Callback,
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000731 MediaRoute2Provider.Callback {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900732
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900733 private final WeakReference<MediaRouter2ServiceImpl> mServiceRef;
734 private final UserRecord mUserRecord;
735 private final MediaRoute2ProviderWatcher mWatcher;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900736
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900737 //TODO: Make this thread-safe.
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000738 private final SystemMediaRoute2Provider mSystemProvider;
739 private final ArrayList<MediaRoute2Provider> mMediaProviders =
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900740 new ArrayList<>();
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900741 private final List<MediaRoute2ProviderInfo> mProviderInfos = new ArrayList<>();
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900742
743 private boolean mRunning;
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900744 private boolean mProviderInfosUpdateScheduled;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900745
746 UserHandler(MediaRouter2ServiceImpl service, UserRecord userRecord) {
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900747 super(Looper.getMainLooper(), null, true);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900748 mServiceRef = new WeakReference<>(service);
749 mUserRecord = userRecord;
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000750 mSystemProvider = new SystemMediaRoute2Provider(service.mContext, this);
751 mMediaProviders.add(mSystemProvider);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900752 mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
753 this, mUserRecord.mUserId);
754 }
755
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900756 private void start() {
757 if (!mRunning) {
758 mRunning = true;
759 mWatcher.start();
760 }
761 }
762
763 private void stop() {
764 if (mRunning) {
765 mRunning = false;
766 //TODO: may unselect routes
767 mWatcher.stop(); // also stops all providers
768 }
769 }
770
771 @Override
Kyunglyul Hyun3aedf022019-04-15 16:38:19 +0900772 public void onAddProvider(MediaRoute2ProviderProxy provider) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900773 provider.setCallback(this);
774 mMediaProviders.add(provider);
775 }
776
777 @Override
Kyunglyul Hyun3aedf022019-04-15 16:38:19 +0900778 public void onRemoveProvider(MediaRoute2ProviderProxy provider) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900779 mMediaProviders.remove(provider);
780 }
781
782 @Override
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000783 public void onProviderStateChanged(@NonNull MediaRoute2Provider provider) {
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900784 sendMessage(PooledLambda.obtainMessage(UserHandler::updateProvider, this, provider));
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900785 }
786
Hyundo Moon4140fee2019-11-08 14:47:50 +0900787 // TODO: When introducing MediaRoute2ProviderService#sendControlHints(),
788 // Make this method to be called.
789 public void onRouteSelectionRequestHandled(@NonNull MediaRoute2ProviderProxy provider,
790 String clientPackageName, MediaRoute2Info route, Bundle controlHints, int seq) {
791 sendMessage(PooledLambda.obtainMessage(
792 UserHandler::updateSelectedRoute, this, provider, clientPackageName, route,
793 controlHints, seq));
794 }
795
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000796 private void updateProvider(MediaRoute2Provider provider) {
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900797 int providerIndex = getProviderInfoIndex(provider.getUniqueId());
798 MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo();
799 MediaRoute2ProviderInfo prevInfo =
800 (providerIndex < 0) ? null : mProviderInfos.get(providerIndex);
801
802 if (Objects.equals(prevInfo, providerInfo)) return;
803
804 if (prevInfo == null) {
805 mProviderInfos.add(providerInfo);
806 Collection<MediaRoute2Info> addedRoutes = providerInfo.getRoutes();
807 if (addedRoutes.size() > 0) {
808 sendMessage(PooledLambda.obtainMessage(UserHandler::notifyRoutesAddedToClients,
809 this, getClients(), new ArrayList<>(addedRoutes)));
810 }
811 } else if (providerInfo == null) {
812 mProviderInfos.remove(prevInfo);
813 Collection<MediaRoute2Info> removedRoutes = prevInfo.getRoutes();
814 if (removedRoutes.size() > 0) {
815 sendMessage(PooledLambda.obtainMessage(
816 UserHandler::notifyRoutesRemovedToClients,
817 this, getClients(), new ArrayList<>(removedRoutes)));
818 }
819 } else {
820 mProviderInfos.set(providerIndex, providerInfo);
821 List<MediaRoute2Info> addedRoutes = new ArrayList<>();
822 List<MediaRoute2Info> removedRoutes = new ArrayList<>();
823 List<MediaRoute2Info> changedRoutes = new ArrayList<>();
824
825 final Collection<MediaRoute2Info> currentRoutes = providerInfo.getRoutes();
826 final Set<String> updatedRouteIds = new HashSet<>();
827
828 for (MediaRoute2Info route : currentRoutes) {
829 if (!route.isValid()) {
830 Slog.w(TAG, "Ignoring invalid route : " + route);
831 continue;
832 }
833 MediaRoute2Info prevRoute = prevInfo.getRoute(route.getId());
834
835 if (prevRoute != null) {
836 if (!Objects.equals(prevRoute, route)) {
837 changedRoutes.add(route);
838 }
839 updatedRouteIds.add(route.getId());
840 } else {
841 addedRoutes.add(route);
842 }
843 }
844
845 for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
846 if (!updatedRouteIds.contains(prevRoute.getId())) {
847 removedRoutes.add(prevRoute);
848 }
849 }
850
851 List<IMediaRouter2Client> clients = getClients();
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +0900852 List<IMediaRouter2Manager> managers = getManagers();
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900853 if (addedRoutes.size() > 0) {
854 notifyRoutesAddedToClients(clients, addedRoutes);
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +0900855 notifyRoutesAddedToManagers(managers, addedRoutes);
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900856 }
857 if (removedRoutes.size() > 0) {
858 notifyRoutesRemovedToClients(clients, removedRoutes);
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +0900859 notifyRoutesRemovedToManagers(managers, removedRoutes);
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900860 }
861 if (changedRoutes.size() > 0) {
862 notifyRoutesChangedToClients(clients, changedRoutes);
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +0900863 notifyRoutesChangedToManagers(managers, changedRoutes);
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900864 }
865 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900866 }
867
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900868 private int getProviderInfoIndex(String providerId) {
869 for (int i = 0; i < mProviderInfos.size(); i++) {
870 MediaRoute2ProviderInfo providerInfo = mProviderInfos.get(i);
871 if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) {
872 return i;
873 }
874 }
875 return -1;
876 }
877
Hyundo Moon4140fee2019-11-08 14:47:50 +0900878 private void updateSelectedRoute(MediaRoute2ProviderProxy provider,
879 String clientPackageName, MediaRoute2Info selectedRoute, Bundle controlHints,
880 int seq) {
881 if (selectedRoute == null
882 || !TextUtils.equals(clientPackageName, selectedRoute.getClientPackageName())) {
883 Log.w(TAG, "Ignoring route selection which has non-matching clientPackageName.");
884 return;
885 }
886
887 MediaRouter2ServiceImpl service = mServiceRef.get();
888 if (service == null) {
889 return;
890 }
891
892 ClientRecord clientRecord;
893 synchronized (service.mLock) {
894 clientRecord = mUserRecord.findClientRecordLocked(clientPackageName);
895 }
896 if (!(clientRecord instanceof Client2Record)) {
897 Log.w(TAG, "Ignoring route selection for unknown client.");
898 unselectRoute(clientPackageName, selectedRoute);
899 return;
900 }
901
902 if (clientRecord.mSelectingRoute == null || !TextUtils.equals(
903 clientRecord.mSelectingRoute.getUniqueId(), selectedRoute.getUniqueId())) {
904 Log.w(TAG, "Ignoring invalid updateSelectedRoute call. selectingRoute="
905 + clientRecord.mSelectingRoute + " route=" + selectedRoute);
906 unselectRoute(clientPackageName, selectedRoute);
907 return;
908 }
909 clientRecord.mSelectingRoute = null;
910 clientRecord.mSelectedRoute = selectedRoute;
911
912 notifyRouteSelectedToClient(((Client2Record) clientRecord).mClient,
913 selectedRoute,
914 MediaRouter2.SELECT_REASON_USER_SELECTED,
915 controlHints);
916 updateClientUsage(clientRecord);
917
918 // Remove the fallback route selection message.
919 removeMessages(seq);
920 }
921
922 private void handleRouteSelectionTimeout(String clientPackageName,
923 MediaRoute2Info selectingRoute) {
924 MediaRouter2ServiceImpl service = mServiceRef.get();
925 if (service == null) {
926 return;
927 }
928
929 ClientRecord clientRecord;
930 synchronized (service.mLock) {
931 clientRecord = mUserRecord.findClientRecordLocked(clientPackageName);
932 }
933 if (!(clientRecord instanceof Client2Record)) {
934 Log.w(TAG, "Ignoring fallback route selection for unknown client.");
935 return;
936 }
937
938 if (clientRecord.mSelectingRoute == null || !TextUtils.equals(
939 clientRecord.mSelectingRoute.getUniqueId(), selectingRoute.getUniqueId())) {
940 Log.w(TAG, "Ignoring invalid selectFallbackRoute call. "
941 + "Current selectingRoute=" + clientRecord.mSelectingRoute
942 + " , original selectingRoute=" + selectingRoute);
943 return;
944 }
945
946 clientRecord.mSelectingRoute = null;
947 // TODO: When the default route is introduced, make mSelectedRoute always non-null.
948 MediaRoute2Info fallbackRoute = null;
949 clientRecord.mSelectedRoute = fallbackRoute;
950
951 notifyRouteSelectedToClient(((Client2Record) clientRecord).mClient,
952 fallbackRoute,
953 MediaRouter2.SELECT_REASON_FALLBACK,
954 Bundle.EMPTY /* controlHints */);
955 updateClientUsage(clientRecord);
956 }
957
958 private void requestSelectRoute(String clientPackageName, MediaRoute2Info route, int seq) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900959 if (route != null) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000960 MediaRoute2Provider provider = findProvider(route.getProviderId());
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900961 if (provider == null) {
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900962 Slog.w(TAG, "Ignoring to select route of unknown provider " + route);
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900963 } else {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900964 provider.requestSelectRoute(clientPackageName, route.getId(), seq);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900965 }
966 }
967 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900968
Hyundo Moon4140fee2019-11-08 14:47:50 +0900969 private void unselectRoute(String clientPackageName, MediaRoute2Info route) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900970 if (route != null) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000971 MediaRoute2Provider provider = findProvider(route.getProviderId());
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900972 if (provider == null) {
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900973 Slog.w(TAG, "Ignoring to unselect route of unknown provider " + route);
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900974 } else {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900975 provider.unselectRoute(clientPackageName, route.getId());
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900976 }
977 }
978 }
979
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900980 private void sendControlRequest(MediaRoute2Info route, Intent request) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000981 final MediaRoute2Provider provider = findProvider(route.getProviderId());
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900982 if (provider != null) {
983 provider.sendControlRequest(route, request);
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900984 }
985 }
986
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900987 private void requestSetVolume(MediaRoute2Info route, int volume) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000988 final MediaRoute2Provider provider = findProvider(route.getProviderId());
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900989 if (provider != null) {
990 provider.requestSetVolume(route, volume);
991 }
992 }
993
994 private void requestUpdateVolume(MediaRoute2Info route, int delta) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000995 final MediaRoute2Provider provider = findProvider(route.getProviderId());
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900996 if (provider != null) {
997 provider.requestUpdateVolume(route, delta);
998 }
999 }
1000
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001001 private List<IMediaRouter2Client> getClients() {
1002 final List<IMediaRouter2Client> clients = new ArrayList<>();
1003 MediaRouter2ServiceImpl service = mServiceRef.get();
1004 if (service == null) {
1005 return clients;
1006 }
1007 synchronized (service.mLock) {
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001008 for (ClientRecord clientRecord : mUserRecord.mClientRecords) {
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +09001009 if (clientRecord instanceof Client2Record) {
1010 clients.add(((Client2Record) clientRecord).mClient);
1011 }
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001012 }
1013 }
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001014 return clients;
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001015 }
1016
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +09001017 private List<IMediaRouter2Manager> getManagers() {
1018 final List<IMediaRouter2Manager> managers = new ArrayList<>();
1019 MediaRouter2ServiceImpl service = mServiceRef.get();
1020 if (service == null) {
1021 return managers;
1022 }
1023 synchronized (service.mLock) {
1024 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
1025 managers.add(managerRecord.mManager);
1026 }
1027 }
1028 return managers;
1029 }
1030
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001031 private void notifyRoutesToClient(IMediaRouter2Client client) {
1032 List<MediaRoute2Info> routes = new ArrayList<>();
1033 for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) {
1034 routes.addAll(providerInfo.getRoutes());
1035 }
1036 if (routes.size() == 0) {
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001037 return;
1038 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001039 try {
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001040 client.notifyRoutesAdded(routes);
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001041 } catch (RemoteException ex) {
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001042 Slog.w(TAG, "Failed to notify all routes. Client probably died.", ex);
1043 }
1044 }
1045
Hyundo Moon4140fee2019-11-08 14:47:50 +09001046 private void notifyRouteSelectedToClient(IMediaRouter2Client client,
1047 MediaRoute2Info route, int reason, Bundle controlHints) {
1048 try {
1049 client.notifyRouteSelected(route, reason, controlHints);
1050 } catch (RemoteException ex) {
1051 Slog.w(TAG, "Failed to notify routes selected. Client probably died.", ex);
1052 }
1053 }
1054
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001055 private void notifyRoutesAddedToClients(List<IMediaRouter2Client> clients,
1056 List<MediaRoute2Info> routes) {
1057 for (IMediaRouter2Client client : clients) {
1058 try {
1059 client.notifyRoutesAdded(routes);
1060 } catch (RemoteException ex) {
1061 Slog.w(TAG, "Failed to notify routes added. Client probably died.", ex);
1062 }
1063 }
1064 }
1065
1066 private void notifyRoutesRemovedToClients(List<IMediaRouter2Client> clients,
1067 List<MediaRoute2Info> routes) {
1068 for (IMediaRouter2Client client : clients) {
1069 try {
1070 client.notifyRoutesRemoved(routes);
1071 } catch (RemoteException ex) {
1072 Slog.w(TAG, "Failed to notify routes removed. Client probably died.", ex);
1073 }
1074 }
1075 }
1076
1077 private void notifyRoutesChangedToClients(List<IMediaRouter2Client> clients,
1078 List<MediaRoute2Info> routes) {
1079 for (IMediaRouter2Client client : clients) {
1080 try {
1081 client.notifyRoutesChanged(routes);
1082 } catch (RemoteException ex) {
1083 Slog.w(TAG, "Failed to notify routes changed. Client probably died.", ex);
1084 }
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001085 }
1086 }
1087
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +09001088 private void notifyRoutesToManager(IMediaRouter2Manager manager) {
1089 List<MediaRoute2Info> routes = new ArrayList<>();
1090 for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) {
1091 routes.addAll(providerInfo.getRoutes());
1092 }
1093 if (routes.size() == 0) {
1094 return;
1095 }
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001096 try {
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +09001097 manager.notifyRoutesAdded(routes);
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001098 } catch (RemoteException ex) {
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +09001099 Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
1100 }
1101 }
1102
1103 private void notifyRoutesAddedToManagers(List<IMediaRouter2Manager> managers,
1104 List<MediaRoute2Info> routes) {
1105 for (IMediaRouter2Manager manager : managers) {
1106 try {
1107 manager.notifyRoutesAdded(routes);
1108 } catch (RemoteException ex) {
1109 Slog.w(TAG, "Failed to notify routes added. Manager probably died.", ex);
1110 }
1111 }
1112 }
1113
1114 private void notifyRoutesRemovedToManagers(List<IMediaRouter2Manager> managers,
1115 List<MediaRoute2Info> routes) {
1116 for (IMediaRouter2Manager manager : managers) {
1117 try {
1118 manager.notifyRoutesRemoved(routes);
1119 } catch (RemoteException ex) {
1120 Slog.w(TAG, "Failed to notify routes removed. Manager probably died.", ex);
1121 }
1122 }
1123 }
1124
1125 private void notifyRoutesChangedToManagers(List<IMediaRouter2Manager> managers,
1126 List<MediaRoute2Info> routes) {
1127 for (IMediaRouter2Manager manager : managers) {
1128 try {
1129 manager.notifyRoutesChanged(routes);
1130 } catch (RemoteException ex) {
1131 Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
1132 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001133 }
1134 }
1135
Kyunglyul Hyundafac542019-04-29 18:07:21 +09001136 private void updateClientUsage(ClientRecord clientRecord) {
1137 MediaRouter2ServiceImpl service = mServiceRef.get();
1138 if (service == null) {
1139 return;
1140 }
1141 List<IMediaRouter2Manager> managers = new ArrayList<>();
1142 synchronized (service.mLock) {
Hyundo Moon7d3ab332019-10-16 11:09:56 +09001143 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
1144 managers.add(managerRecord.mManager);
Kyunglyul Hyundafac542019-04-29 18:07:21 +09001145 }
1146 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +09001147 for (IMediaRouter2Manager manager : managers) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +09001148 try {
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +09001149 manager.notifyRouteSelected(clientRecord.mPackageName,
1150 clientRecord.mSelectedRoute);
1151 manager.notifyControlCategoriesChanged(clientRecord.mPackageName,
Kyunglyul Hyundafac542019-04-29 18:07:21 +09001152 clientRecord.mControlCategories);
1153 } catch (RemoteException ex) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +09001154 Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex);
Kyunglyul Hyundafac542019-04-29 18:07:21 +09001155 }
1156 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001157 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +09001158
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +00001159 private MediaRoute2Provider findProvider(String providerId) {
1160 for (MediaRoute2Provider provider : mMediaProviders) {
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001161 if (TextUtils.equals(provider.getUniqueId(), providerId)) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +09001162 return provider;
1163 }
1164 }
1165 return null;
1166 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001167 }
1168}