blob: 2cf920d1aba45384de1ff58b443542f4568f117d [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
Hyundo Moon5736a612019-11-19 15:08:32 +090086 @NonNull
87 public List<MediaRoute2Info> getSystemRoutes() {
88 final int uid = Binder.getCallingUid();
89 final int userId = UserHandle.getUserId(uid);
90
91 Collection<MediaRoute2Info> systemRoutes;
92 synchronized (mLock) {
93 UserRecord userRecord = mUserRecords.get(userId);
94 if (userRecord == null) {
95 userRecord = new UserRecord(userId);
96 mUserRecords.put(userId, userRecord);
97 initializeUserLocked(userRecord);
98 }
99 systemRoutes = userRecord.mHandler.mSystemProvider.getProviderInfo().getRoutes();
100 }
101 return new ArrayList<>(systemRoutes);
102 }
103
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900104 public void registerClient(@NonNull IMediaRouter2Client client,
105 @NonNull String packageName) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900106 Objects.requireNonNull(client, "client must not be null");
107
108 final int uid = Binder.getCallingUid();
109 final int pid = Binder.getCallingPid();
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900110 final int userId = UserHandle.getUserId(uid);
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900111 final boolean trusted = mContext.checkCallingOrSelfPermission(
112 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
113 == PackageManager.PERMISSION_GRANTED;
114 final long token = Binder.clearCallingIdentity();
115 try {
116 synchronized (mLock) {
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900117 registerClient2Locked(client, uid, pid, packageName, userId, trusted);
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900118 }
119 } finally {
120 Binder.restoreCallingIdentity(token);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900121 }
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900122 }
123
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900124 public void unregisterClient(@NonNull IMediaRouter2Client client) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900125 Objects.requireNonNull(client, "client must not be null");
126
127 final long token = Binder.clearCallingIdentity();
128 try {
129 synchronized (mLock) {
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900130 unregisterClient2Locked(client, false);
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900131 }
132 } finally {
133 Binder.restoreCallingIdentity(token);
134 }
135 }
136
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900137 public void registerManager(@NonNull IMediaRouter2Manager manager,
138 @NonNull String packageName) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900139 Objects.requireNonNull(manager, "manager must not be null");
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900140 //TODO: should check permission
141 final boolean trusted = true;
142
143 final int uid = Binder.getCallingUid();
144 final int pid = Binder.getCallingPid();
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900145 final int userId = UserHandle.getUserId(uid);
146
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900147 final long token = Binder.clearCallingIdentity();
148 try {
149 synchronized (mLock) {
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900150 registerManagerLocked(manager, uid, pid, packageName, userId, trusted);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900151 }
152 } finally {
153 Binder.restoreCallingIdentity(token);
154 }
155 }
156
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900157 public void unregisterManager(@NonNull IMediaRouter2Manager manager) {
158 Objects.requireNonNull(manager, "manager must not be null");
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900159
160 final long token = Binder.clearCallingIdentity();
161 try {
162 synchronized (mLock) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900163 unregisterManagerLocked(manager, false);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900164 }
165 } finally {
166 Binder.restoreCallingIdentity(token);
167 }
168 }
169
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900170 public void sendControlRequest(@NonNull IMediaRouter2Client client,
171 @NonNull MediaRoute2Info route, @NonNull Intent request) {
172 Objects.requireNonNull(client, "client must not be null");
173 Objects.requireNonNull(route, "route must not be null");
174 Objects.requireNonNull(request, "request must not be null");
175
176 final long token = Binder.clearCallingIdentity();
177 try {
178 synchronized (mLock) {
179 sendControlRequestLocked(client, route, request);
180 }
181 } finally {
182 Binder.restoreCallingIdentity(token);
183 }
184 }
185
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900186 //TODO: What would happen if a media app used MediaRouter and MediaRouter2 simultaneously?
187 public void setControlCategories(@NonNull IMediaRouterClient client,
188 @Nullable List<String> categories) {
189 Objects.requireNonNull(client, "client must not be null");
190 final long token = Binder.clearCallingIdentity();
191 try {
192 synchronized (mLock) {
193 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
194 setControlCategoriesLocked(clientRecord, categories);
195 }
196 } finally {
197 Binder.restoreCallingIdentity(token);
198 }
199 }
200
201 public void setControlCategories2(@NonNull IMediaRouter2Client client,
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900202 @Nullable List<String> categories) {
203 Objects.requireNonNull(client, "client must not be null");
204
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900205 final long token = Binder.clearCallingIdentity();
206 try {
207 synchronized (mLock) {
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900208 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
209 setControlCategoriesLocked(clientRecord, categories);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900210 }
211 } finally {
212 Binder.restoreCallingIdentity(token);
213 }
214 }
215
Hyundo Moon4140fee2019-11-08 14:47:50 +0900216 public void requestSelectRoute2(@NonNull IMediaRouter2Client client,
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900217 @Nullable MediaRoute2Info route) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900218 final long token = Binder.clearCallingIdentity();
219 try {
220 synchronized (mLock) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900221 requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), route);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900222 }
223 } finally {
224 Binder.restoreCallingIdentity(token);
225 }
226 }
227
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900228 public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
229 Objects.requireNonNull(client, "client must not be null");
230 Objects.requireNonNull(route, "route must not be null");
231
232 final long token = Binder.clearCallingIdentity();
233 try {
234 synchronized (mLock) {
235 requestSetVolumeLocked(client, route, volume);
236 }
237 } finally {
238 Binder.restoreCallingIdentity(token);
239 }
240 }
241
242 public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
243 Objects.requireNonNull(client, "client must not be null");
244 Objects.requireNonNull(route, "route must not be null");
245
246 final long token = Binder.clearCallingIdentity();
247 try {
248 synchronized (mLock) {
249 requestUpdateVolumeLocked(client, route, delta);
250 }
251 } finally {
252 Binder.restoreCallingIdentity(token);
253 }
254 }
255
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900256 public void selectClientRoute2(@NonNull IMediaRouter2Manager manager,
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900257 String packageName, @Nullable MediaRoute2Info route) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900258 final long token = Binder.clearCallingIdentity();
259 try {
260 synchronized (mLock) {
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900261 selectClientRoute2Locked(manager, packageName, route);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900262 }
263 } finally {
264 Binder.restoreCallingIdentity(token);
265 }
266 }
267
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900268 public void requestSetVolume2Manager(IMediaRouter2Manager manager,
269 MediaRoute2Info route, int volume) {
270 Objects.requireNonNull(manager, "manager must not be null");
271 Objects.requireNonNull(route, "route must not be null");
272
273 final long token = Binder.clearCallingIdentity();
274 try {
275 synchronized (mLock) {
276 requestSetVolumeLocked(manager, route, volume);
277 }
278 } finally {
279 Binder.restoreCallingIdentity(token);
280 }
281 }
282
283 public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
284 MediaRoute2Info route, int delta) {
285 Objects.requireNonNull(manager, "manager must not be null");
286 Objects.requireNonNull(route, "route must not be null");
287
288 final long token = Binder.clearCallingIdentity();
289 try {
290 synchronized (mLock) {
291 requestUpdateVolumeLocked(manager, route, delta);
292 }
293 } finally {
294 Binder.restoreCallingIdentity(token);
295 }
296 }
297
298
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900299 public void registerClient(@NonNull IMediaRouterClient client, @NonNull String packageName) {
300 Objects.requireNonNull(client, "client must not be null");
301
302 final int uid = Binder.getCallingUid();
303 final int pid = Binder.getCallingPid();
304 final int userId = UserHandle.getUserId(uid);
305
306 final long token = Binder.clearCallingIdentity();
307 try {
308 synchronized (mLock) {
309 registerClient1Locked(client, packageName, userId);
310 }
311 } finally {
312 Binder.restoreCallingIdentity(token);
313 }
314 }
315
316 public void unregisterClient(@NonNull IMediaRouterClient client) {
317 Objects.requireNonNull(client, "client must not be null");
318
319 final long token = Binder.clearCallingIdentity();
320 try {
321 synchronized (mLock) {
322 unregisterClient1Locked(client);
323 }
324 } finally {
325 Binder.restoreCallingIdentity(token);
326 }
327 }
328
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900329 //TODO: Review this is handling multi-user properly.
330 void switchUser() {
331 synchronized (mLock) {
332 int userId = ActivityManager.getCurrentUser();
333 if (mCurrentUserId != userId) {
334 final int oldUserId = mCurrentUserId;
335 mCurrentUserId = userId; // do this first
336
337 UserRecord oldUser = mUserRecords.get(oldUserId);
338 if (oldUser != null) {
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900339 oldUser.mHandler.sendMessage(
340 obtainMessage(UserHandler::stop, oldUser.mHandler));
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900341 disposeUserIfNeededLocked(oldUser); // since no longer current user
342 }
343
344 UserRecord newUser = mUserRecords.get(userId);
345 if (newUser != null) {
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900346 newUser.mHandler.sendMessage(
347 obtainMessage(UserHandler::start, newUser.mHandler));
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900348 }
349 }
350 }
351 }
352
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900353 void clientDied(Client2Record clientRecord) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900354 synchronized (mLock) {
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900355 unregisterClient2Locked(clientRecord.mClient, true);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900356 }
357 }
358
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900359 void managerDied(ManagerRecord managerRecord) {
360 synchronized (mLock) {
361 unregisterManagerLocked(managerRecord.mManager, true);
362 }
363 }
364
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900365 private void registerClient2Locked(IMediaRouter2Client client,
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900366 int uid, int pid, String packageName, int userId, boolean trusted) {
367 final IBinder binder = client.asBinder();
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900368 if (mAllClientRecords.get(binder) == null) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900369 UserRecord userRecord = mUserRecords.get(userId);
370 if (userRecord == null) {
371 userRecord = new UserRecord(userId);
Hyundo Moon7d3ab332019-10-16 11:09:56 +0900372 mUserRecords.put(userId, userRecord);
373 initializeUserLocked(userRecord);
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900374 }
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900375 Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid,
376 packageName, trusted);
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900377 try {
378 binder.linkToDeath(clientRecord, 0);
379 } catch (RemoteException ex) {
380 throw new RuntimeException("Media router client died prematurely.", ex);
381 }
382
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900383 userRecord.mClientRecords.add(clientRecord);
384 mAllClientRecords.put(binder, clientRecord);
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900385
386 userRecord.mHandler.sendMessage(
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900387 obtainMessage(UserHandler::notifyRoutesToClient, userRecord.mHandler, client));
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900388 }
389 }
390
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900391 private void unregisterClient2Locked(IMediaRouter2Client client, boolean died) {
392 Client2Record clientRecord = (Client2Record) mAllClientRecords.remove(client.asBinder());
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900393 if (clientRecord != null) {
394 UserRecord userRecord = clientRecord.mUserRecord;
395 userRecord.mClientRecords.remove(clientRecord);
396 //TODO: update discovery request
397 clientRecord.dispose();
398 disposeUserIfNeededLocked(userRecord); // since client removed from user
399 }
400 }
401
Hyundo Moon4140fee2019-11-08 14:47:50 +0900402 private void requestSelectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900403 if (clientRecord != null) {
404 MediaRoute2Info oldRoute = clientRecord.mSelectedRoute;
Hyundo Moon4140fee2019-11-08 14:47:50 +0900405 clientRecord.mSelectingRoute = route;
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900406
407 UserHandler handler = clientRecord.mUserRecord.mHandler;
408 //TODO: Handle transfer instead of unselect and select
409 if (oldRoute != null) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900410 handler.sendMessage(obtainMessage(
411 UserHandler::unselectRoute, handler, clientRecord.mPackageName, oldRoute));
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900412 }
413 if (route != null) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900414 final int seq = mSelectRouteRequestSequenceNumber;
415 mSelectRouteRequestSequenceNumber++;
416
417 handler.sendMessage(obtainMessage(
418 UserHandler::requestSelectRoute, handler, clientRecord.mPackageName,
419 route, seq));
420
421 // Remove all previous timeout messages
422 for (int previousSeq : clientRecord.mSelectRouteSequenceNumbers) {
423 clientRecord.mUserRecord.mHandler.removeMessages(previousSeq);
424 }
425 clientRecord.mSelectRouteSequenceNumbers.clear();
426
427 // When the request is not handled in timeout, set the client's route to default.
428 Message timeoutMsg = obtainMessage(UserHandler::handleRouteSelectionTimeout,
429 handler, clientRecord.mPackageName, route);
430 timeoutMsg.what = seq; // Make the message cancelable.
431 handler.sendMessageDelayed(timeoutMsg, ROUTE_SELECTION_REQUEST_TIMEOUT_MS);
432 clientRecord.mSelectRouteSequenceNumbers.add(seq);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900433 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900434 }
435 }
436
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900437 private void setControlCategoriesLocked(ClientRecord clientRecord, List<String> categories) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900438 if (clientRecord != null) {
439 clientRecord.mControlCategories = categories;
440
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900441 clientRecord.mUserRecord.mHandler.sendMessage(
442 obtainMessage(UserHandler::updateClientUsage,
443 clientRecord.mUserRecord.mHandler, clientRecord));
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900444 }
445 }
446
447 private void sendControlRequestLocked(IMediaRouter2Client client, MediaRoute2Info route,
448 Intent request) {
449 final IBinder binder = client.asBinder();
450 ClientRecord clientRecord = mAllClientRecords.get(binder);
451
452 if (clientRecord != null) {
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900453 clientRecord.mUserRecord.mHandler.sendMessage(
454 obtainMessage(UserHandler::sendControlRequest,
455 clientRecord.mUserRecord.mHandler, route, request));
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900456 }
457 }
458
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900459 private void requestSetVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
460 int volume) {
461 final IBinder binder = client.asBinder();
462 ClientRecord clientRecord = mAllClientRecords.get(binder);
463
464 if (clientRecord != null) {
465 clientRecord.mUserRecord.mHandler.sendMessage(
466 obtainMessage(UserHandler::requestSetVolume,
467 clientRecord.mUserRecord.mHandler, route, volume));
468 }
469 }
470
471 private void requestUpdateVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
472 int delta) {
473 final IBinder binder = client.asBinder();
474 ClientRecord clientRecord = mAllClientRecords.get(binder);
475
476 if (clientRecord != null) {
477 clientRecord.mUserRecord.mHandler.sendMessage(
478 obtainMessage(UserHandler::requestUpdateVolume,
479 clientRecord.mUserRecord.mHandler, route, delta));
480 }
481 }
482
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900483 private void registerManagerLocked(IMediaRouter2Manager manager,
484 int uid, int pid, String packageName, int userId, boolean trusted) {
485 final IBinder binder = manager.asBinder();
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900486 ManagerRecord managerRecord = mAllManagerRecords.get(binder);
487 if (managerRecord == null) {
488 boolean newUser = false;
489 UserRecord userRecord = mUserRecords.get(userId);
490 if (userRecord == null) {
491 userRecord = new UserRecord(userId);
492 newUser = true;
493 }
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900494 managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName, trusted);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900495 try {
496 binder.linkToDeath(managerRecord, 0);
497 } catch (RemoteException ex) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900498 throw new RuntimeException("Media router manager died prematurely.", ex);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900499 }
500
501 if (newUser) {
502 mUserRecords.put(userId, userRecord);
503 initializeUserLocked(userRecord);
504 }
505
506 userRecord.mManagerRecords.add(managerRecord);
507 mAllManagerRecords.put(binder, managerRecord);
508
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900509 userRecord.mHandler.sendMessage(
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +0900510 obtainMessage(UserHandler::notifyRoutesToManager,
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900511 userRecord.mHandler, manager));
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900512
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900513 for (ClientRecord clientRecord : userRecord.mClientRecords) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900514 // TODO: Do not use updateClientUsage since it updates all managers.
515 // Instead, Notify only to the manager that is currently being registered.
516
517 // TODO: UserRecord <-> ClientRecord, why do they reference each other?
518 // How about removing mUserRecord from clientRecord?
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900519 clientRecord.mUserRecord.mHandler.sendMessage(
520 obtainMessage(UserHandler::updateClientUsage,
521 clientRecord.mUserRecord.mHandler, clientRecord));
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900522 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900523 }
524 }
525
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900526 private void unregisterManagerLocked(IMediaRouter2Manager manager, boolean died) {
527 ManagerRecord managerRecord = mAllManagerRecords.remove(manager.asBinder());
528 if (managerRecord != null) {
529 UserRecord userRecord = managerRecord.mUserRecord;
530 userRecord.mManagerRecords.remove(managerRecord);
531 managerRecord.dispose();
532 disposeUserIfNeededLocked(userRecord); // since manager removed from user
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900533 }
534 }
535
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900536 private void selectClientRoute2Locked(IMediaRouter2Manager manager,
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900537 String packageName, MediaRoute2Info route) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900538 ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900539 if (managerRecord != null) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900540 ClientRecord clientRecord =
541 managerRecord.mUserRecord.findClientRecordLocked(packageName);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900542 if (clientRecord == null) {
543 Slog.w(TAG, "Ignoring route selection for unknown client.");
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900544 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900545 if (clientRecord != null && managerRecord.mTrusted) {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900546 requestSelectRoute2Locked(clientRecord, route);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900547 }
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900548 }
549 }
550
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +0900551 private void requestSetVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
552 int volume) {
553 final IBinder binder = manager.asBinder();
554 ManagerRecord managerRecord = mAllManagerRecords.get(binder);
555
556 if (managerRecord != null) {
557 managerRecord.mUserRecord.mHandler.sendMessage(
558 obtainMessage(UserHandler::requestSetVolume,
559 managerRecord.mUserRecord.mHandler, route, volume));
560 }
561 }
562
563 private void requestUpdateVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
564 int delta) {
565 final IBinder binder = manager.asBinder();
566 ManagerRecord managerRecord = mAllManagerRecords.get(binder);
567
568 if (managerRecord != null) {
569 managerRecord.mUserRecord.mHandler.sendMessage(
570 obtainMessage(UserHandler::requestUpdateVolume,
571 managerRecord.mUserRecord.mHandler, route, delta));
572 }
573 }
574
575
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900576 private void initializeUserLocked(UserRecord userRecord) {
577 if (DEBUG) {
578 Slog.d(TAG, userRecord + ": Initialized");
579 }
580 if (userRecord.mUserId == mCurrentUserId) {
Kyunglyul Hyun30e2c652019-07-11 16:25:31 +0900581 userRecord.mHandler.sendMessage(
582 obtainMessage(UserHandler::start, userRecord.mHandler));
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900583 }
584 }
585
586 private void disposeUserIfNeededLocked(UserRecord userRecord) {
587 // If there are no records left and the user is no longer current then go ahead
588 // and purge the user record and all of its associated state. If the user is current
589 // then leave it alone since we might be connected to a route or want to query
590 // the same route information again soon.
591 if (userRecord.mUserId != mCurrentUserId
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900592 && userRecord.mClientRecords.isEmpty()
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900593 && userRecord.mManagerRecords.isEmpty()) {
594 if (DEBUG) {
595 Slog.d(TAG, userRecord + ": Disposed");
596 }
597 mUserRecords.remove(userRecord.mUserId);
598 // Note: User already stopped (by switchUser) so no need to send stop message here.
599 }
600 }
601
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900602 private void registerClient1Locked(IMediaRouterClient client, String packageName,
603 int userId) {
604 final IBinder binder = client.asBinder();
605 if (mAllClientRecords.get(binder) == null) {
606 boolean newUser = false;
607 UserRecord userRecord = mUserRecords.get(userId);
608 if (userRecord == null) {
609 userRecord = new UserRecord(userId);
610 newUser = true;
611 }
612 ClientRecord clientRecord = new Client1Record(userRecord, client, packageName);
613
614 if (newUser) {
615 mUserRecords.put(userId, userRecord);
616 initializeUserLocked(userRecord);
617 }
618
619 userRecord.mClientRecords.add(clientRecord);
620 mAllClientRecords.put(binder, clientRecord);
621 }
622 }
623
624 private void unregisterClient1Locked(IMediaRouterClient client) {
625 ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
626 if (clientRecord != null) {
627 UserRecord userRecord = clientRecord.mUserRecord;
628 userRecord.mClientRecords.remove(clientRecord);
629 disposeUserIfNeededLocked(userRecord);
630 }
631 }
632
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900633 final class UserRecord {
634 public final int mUserId;
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900635 //TODO: make records private for thread-safety
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900636 final ArrayList<ClientRecord> mClientRecords = new ArrayList<>();
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900637 final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
638 final UserHandler mHandler;
639
640 UserRecord(int userId) {
641 mUserId = userId;
642 mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this);
643 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900644
Hyundo Moon4140fee2019-11-08 14:47:50 +0900645 ClientRecord findClientRecordLocked(String packageName) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900646 for (ClientRecord clientRecord : mClientRecords) {
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900647 if (TextUtils.equals(clientRecord.mPackageName, packageName)) {
648 return clientRecord;
649 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900650 }
651 return null;
652 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900653 }
654
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900655 class ClientRecord {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900656 public final UserRecord mUserRecord;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900657 public final String mPackageName;
Hyundo Moon4140fee2019-11-08 14:47:50 +0900658 public final List<Integer> mSelectRouteSequenceNumbers;
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900659 public List<String> mControlCategories;
Hyundo Moon4140fee2019-11-08 14:47:50 +0900660 public MediaRoute2Info mSelectingRoute;
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900661 public MediaRoute2Info mSelectedRoute;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900662
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900663 ClientRecord(UserRecord userRecord, String packageName) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900664 mUserRecord = userRecord;
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900665 mPackageName = packageName;
Hyundo Moon4140fee2019-11-08 14:47:50 +0900666 mSelectRouteSequenceNumbers = new ArrayList<>();
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +0900667 mControlCategories = Collections.emptyList();
668 }
669 }
670
671 final class Client1Record extends ClientRecord {
672 public final IMediaRouterClient mClient;
673
674 Client1Record(UserRecord userRecord, IMediaRouterClient client,
675 String packageName) {
676 super(userRecord, packageName);
677 mClient = client;
678 }
679 }
680
681 final class Client2Record extends ClientRecord
682 implements IBinder.DeathRecipient {
683 public final IMediaRouter2Client mClient;
684 public final int mUid;
685 public final int mPid;
686 public final boolean mTrusted;
687
688 Client2Record(UserRecord userRecord, IMediaRouter2Client client,
689 int uid, int pid, String packageName, boolean trusted) {
690 super(userRecord, packageName);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900691 mClient = client;
692 mUid = uid;
693 mPid = pid;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900694 mTrusted = trusted;
695 }
696
697 public void dispose() {
698 mClient.asBinder().unlinkToDeath(this, 0);
699 }
700
701 @Override
702 public void binderDied() {
703 clientDied(this);
704 }
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900705 }
706
707 final class ManagerRecord implements IBinder.DeathRecipient {
708 public final UserRecord mUserRecord;
709 public final IMediaRouter2Manager mManager;
710 public final int mUid;
711 public final int mPid;
712 public final String mPackageName;
713 public final boolean mTrusted;
714
715 ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager,
716 int uid, int pid, String packageName, boolean trusted) {
717 mUserRecord = userRecord;
718 mManager = manager;
719 mUid = uid;
720 mPid = pid;
721 mPackageName = packageName;
722 mTrusted = trusted;
723 }
724
725 public void dispose() {
726 mManager.asBinder().unlinkToDeath(this, 0);
727 }
728
729 @Override
730 public void binderDied() {
731 managerDied(this);
732 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900733
734 public void dump(PrintWriter pw, String prefix) {
735 pw.println(prefix + this);
736
737 final String indent = prefix + " ";
738 pw.println(indent + "mTrusted=" + mTrusted);
739 }
740
741 @Override
742 public String toString() {
Kyunglyul Hyundafac542019-04-29 18:07:21 +0900743 return "Manager " + mPackageName + " (pid " + mPid + ")";
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900744 }
745 }
746
747 static final class UserHandler extends Handler implements
748 MediaRoute2ProviderWatcher.Callback,
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000749 MediaRoute2Provider.Callback {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900750
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900751 private final WeakReference<MediaRouter2ServiceImpl> mServiceRef;
752 private final UserRecord mUserRecord;
753 private final MediaRoute2ProviderWatcher mWatcher;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900754
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900755 //TODO: Make this thread-safe.
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000756 private final SystemMediaRoute2Provider mSystemProvider;
757 private final ArrayList<MediaRoute2Provider> mMediaProviders =
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900758 new ArrayList<>();
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900759 private final List<MediaRoute2ProviderInfo> mProviderInfos = new ArrayList<>();
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900760
761 private boolean mRunning;
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900762 private boolean mProviderInfosUpdateScheduled;
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900763
764 UserHandler(MediaRouter2ServiceImpl service, UserRecord userRecord) {
Kyunglyul Hyun6c5d7dc2019-04-24 15:57:07 +0900765 super(Looper.getMainLooper(), null, true);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900766 mServiceRef = new WeakReference<>(service);
767 mUserRecord = userRecord;
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000768 mSystemProvider = new SystemMediaRoute2Provider(service.mContext, this);
769 mMediaProviders.add(mSystemProvider);
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900770 mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
771 this, mUserRecord.mUserId);
772 }
773
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900774 private void start() {
775 if (!mRunning) {
776 mRunning = true;
777 mWatcher.start();
778 }
779 }
780
781 private void stop() {
782 if (mRunning) {
783 mRunning = false;
784 //TODO: may unselect routes
785 mWatcher.stop(); // also stops all providers
786 }
787 }
788
789 @Override
Kyunglyul Hyun3aedf022019-04-15 16:38:19 +0900790 public void onAddProvider(MediaRoute2ProviderProxy provider) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900791 provider.setCallback(this);
792 mMediaProviders.add(provider);
793 }
794
795 @Override
Kyunglyul Hyun3aedf022019-04-15 16:38:19 +0900796 public void onRemoveProvider(MediaRoute2ProviderProxy provider) {
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900797 mMediaProviders.remove(provider);
798 }
799
800 @Override
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000801 public void onProviderStateChanged(@NonNull MediaRoute2Provider provider) {
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900802 sendMessage(PooledLambda.obtainMessage(UserHandler::updateProvider, this, provider));
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900803 }
804
Hyundo Moon4140fee2019-11-08 14:47:50 +0900805 // TODO: When introducing MediaRoute2ProviderService#sendControlHints(),
806 // Make this method to be called.
807 public void onRouteSelectionRequestHandled(@NonNull MediaRoute2ProviderProxy provider,
808 String clientPackageName, MediaRoute2Info route, Bundle controlHints, int seq) {
809 sendMessage(PooledLambda.obtainMessage(
810 UserHandler::updateSelectedRoute, this, provider, clientPackageName, route,
811 controlHints, seq));
812 }
813
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000814 private void updateProvider(MediaRoute2Provider provider) {
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900815 int providerIndex = getProviderInfoIndex(provider.getUniqueId());
816 MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo();
817 MediaRoute2ProviderInfo prevInfo =
818 (providerIndex < 0) ? null : mProviderInfos.get(providerIndex);
819
820 if (Objects.equals(prevInfo, providerInfo)) return;
821
822 if (prevInfo == null) {
823 mProviderInfos.add(providerInfo);
824 Collection<MediaRoute2Info> addedRoutes = providerInfo.getRoutes();
825 if (addedRoutes.size() > 0) {
826 sendMessage(PooledLambda.obtainMessage(UserHandler::notifyRoutesAddedToClients,
827 this, getClients(), new ArrayList<>(addedRoutes)));
828 }
829 } else if (providerInfo == null) {
830 mProviderInfos.remove(prevInfo);
831 Collection<MediaRoute2Info> removedRoutes = prevInfo.getRoutes();
832 if (removedRoutes.size() > 0) {
833 sendMessage(PooledLambda.obtainMessage(
834 UserHandler::notifyRoutesRemovedToClients,
835 this, getClients(), new ArrayList<>(removedRoutes)));
836 }
837 } else {
838 mProviderInfos.set(providerIndex, providerInfo);
839 List<MediaRoute2Info> addedRoutes = new ArrayList<>();
840 List<MediaRoute2Info> removedRoutes = new ArrayList<>();
841 List<MediaRoute2Info> changedRoutes = new ArrayList<>();
842
843 final Collection<MediaRoute2Info> currentRoutes = providerInfo.getRoutes();
844 final Set<String> updatedRouteIds = new HashSet<>();
845
846 for (MediaRoute2Info route : currentRoutes) {
847 if (!route.isValid()) {
848 Slog.w(TAG, "Ignoring invalid route : " + route);
849 continue;
850 }
851 MediaRoute2Info prevRoute = prevInfo.getRoute(route.getId());
852
853 if (prevRoute != null) {
854 if (!Objects.equals(prevRoute, route)) {
855 changedRoutes.add(route);
856 }
857 updatedRouteIds.add(route.getId());
858 } else {
859 addedRoutes.add(route);
860 }
861 }
862
863 for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
864 if (!updatedRouteIds.contains(prevRoute.getId())) {
865 removedRoutes.add(prevRoute);
866 }
867 }
868
869 List<IMediaRouter2Client> clients = getClients();
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +0900870 List<IMediaRouter2Manager> managers = getManagers();
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900871 if (addedRoutes.size() > 0) {
872 notifyRoutesAddedToClients(clients, addedRoutes);
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +0900873 notifyRoutesAddedToManagers(managers, addedRoutes);
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900874 }
875 if (removedRoutes.size() > 0) {
876 notifyRoutesRemovedToClients(clients, removedRoutes);
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +0900877 notifyRoutesRemovedToManagers(managers, removedRoutes);
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900878 }
879 if (changedRoutes.size() > 0) {
880 notifyRoutesChangedToClients(clients, changedRoutes);
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +0900881 notifyRoutesChangedToManagers(managers, changedRoutes);
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900882 }
883 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900884 }
885
Kyunglyul Hyun7af73012019-10-11 20:01:30 +0900886 private int getProviderInfoIndex(String providerId) {
887 for (int i = 0; i < mProviderInfos.size(); i++) {
888 MediaRoute2ProviderInfo providerInfo = mProviderInfos.get(i);
889 if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) {
890 return i;
891 }
892 }
893 return -1;
894 }
895
Hyundo Moon4140fee2019-11-08 14:47:50 +0900896 private void updateSelectedRoute(MediaRoute2ProviderProxy provider,
897 String clientPackageName, MediaRoute2Info selectedRoute, Bundle controlHints,
898 int seq) {
899 if (selectedRoute == null
900 || !TextUtils.equals(clientPackageName, selectedRoute.getClientPackageName())) {
901 Log.w(TAG, "Ignoring route selection which has non-matching clientPackageName.");
902 return;
903 }
904
905 MediaRouter2ServiceImpl service = mServiceRef.get();
906 if (service == null) {
907 return;
908 }
909
910 ClientRecord clientRecord;
911 synchronized (service.mLock) {
912 clientRecord = mUserRecord.findClientRecordLocked(clientPackageName);
913 }
914 if (!(clientRecord instanceof Client2Record)) {
915 Log.w(TAG, "Ignoring route selection for unknown client.");
916 unselectRoute(clientPackageName, selectedRoute);
917 return;
918 }
919
920 if (clientRecord.mSelectingRoute == null || !TextUtils.equals(
921 clientRecord.mSelectingRoute.getUniqueId(), selectedRoute.getUniqueId())) {
922 Log.w(TAG, "Ignoring invalid updateSelectedRoute call. selectingRoute="
923 + clientRecord.mSelectingRoute + " route=" + selectedRoute);
924 unselectRoute(clientPackageName, selectedRoute);
925 return;
926 }
927 clientRecord.mSelectingRoute = null;
928 clientRecord.mSelectedRoute = selectedRoute;
929
930 notifyRouteSelectedToClient(((Client2Record) clientRecord).mClient,
931 selectedRoute,
932 MediaRouter2.SELECT_REASON_USER_SELECTED,
933 controlHints);
934 updateClientUsage(clientRecord);
935
936 // Remove the fallback route selection message.
937 removeMessages(seq);
938 }
939
940 private void handleRouteSelectionTimeout(String clientPackageName,
941 MediaRoute2Info selectingRoute) {
942 MediaRouter2ServiceImpl service = mServiceRef.get();
943 if (service == null) {
944 return;
945 }
946
947 ClientRecord clientRecord;
948 synchronized (service.mLock) {
949 clientRecord = mUserRecord.findClientRecordLocked(clientPackageName);
950 }
951 if (!(clientRecord instanceof Client2Record)) {
952 Log.w(TAG, "Ignoring fallback route selection for unknown client.");
953 return;
954 }
955
956 if (clientRecord.mSelectingRoute == null || !TextUtils.equals(
957 clientRecord.mSelectingRoute.getUniqueId(), selectingRoute.getUniqueId())) {
958 Log.w(TAG, "Ignoring invalid selectFallbackRoute call. "
959 + "Current selectingRoute=" + clientRecord.mSelectingRoute
960 + " , original selectingRoute=" + selectingRoute);
961 return;
962 }
963
964 clientRecord.mSelectingRoute = null;
965 // TODO: When the default route is introduced, make mSelectedRoute always non-null.
966 MediaRoute2Info fallbackRoute = null;
967 clientRecord.mSelectedRoute = fallbackRoute;
968
969 notifyRouteSelectedToClient(((Client2Record) clientRecord).mClient,
970 fallbackRoute,
971 MediaRouter2.SELECT_REASON_FALLBACK,
972 Bundle.EMPTY /* controlHints */);
973 updateClientUsage(clientRecord);
974 }
975
976 private void requestSelectRoute(String clientPackageName, MediaRoute2Info route, int seq) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900977 if (route != null) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000978 MediaRoute2Provider provider = findProvider(route.getProviderId());
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900979 if (provider == null) {
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900980 Slog.w(TAG, "Ignoring to select route of unknown provider " + route);
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900981 } else {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900982 provider.requestSelectRoute(clientPackageName, route.getId(), seq);
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900983 }
984 }
985 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900986
Hyundo Moon4140fee2019-11-08 14:47:50 +0900987 private void unselectRoute(String clientPackageName, MediaRoute2Info route) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +0900988 if (route != null) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000989 MediaRoute2Provider provider = findProvider(route.getProviderId());
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900990 if (provider == null) {
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +0900991 Slog.w(TAG, "Ignoring to unselect route of unknown provider " + route);
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +0900992 } else {
Hyundo Moon4140fee2019-11-08 14:47:50 +0900993 provider.unselectRoute(clientPackageName, route.getId());
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +0900994 }
995 }
996 }
997
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +0900998 private void sendControlRequest(MediaRoute2Info route, Intent request) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +0000999 final MediaRoute2Provider provider = findProvider(route.getProviderId());
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +09001000 if (provider != null) {
1001 provider.sendControlRequest(route, request);
Kyunglyul Hyuncaae8dc2019-04-29 15:45:23 +09001002 }
1003 }
1004
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +09001005 private void requestSetVolume(MediaRoute2Info route, int volume) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +00001006 final MediaRoute2Provider provider = findProvider(route.getProviderId());
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +09001007 if (provider != null) {
1008 provider.requestSetVolume(route, volume);
1009 }
1010 }
1011
1012 private void requestUpdateVolume(MediaRoute2Info route, int delta) {
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +00001013 final MediaRoute2Provider provider = findProvider(route.getProviderId());
Kyunglyul Hyun5a8dedc2019-10-10 14:09:44 +09001014 if (provider != null) {
1015 provider.requestUpdateVolume(route, delta);
1016 }
1017 }
1018
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001019 private List<IMediaRouter2Client> getClients() {
1020 final List<IMediaRouter2Client> clients = new ArrayList<>();
1021 MediaRouter2ServiceImpl service = mServiceRef.get();
1022 if (service == null) {
1023 return clients;
1024 }
1025 synchronized (service.mLock) {
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001026 for (ClientRecord clientRecord : mUserRecord.mClientRecords) {
Kyunglyul Hyun2f43fd62019-09-16 18:47:52 +09001027 if (clientRecord instanceof Client2Record) {
1028 clients.add(((Client2Record) clientRecord).mClient);
1029 }
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001030 }
1031 }
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001032 return clients;
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001033 }
1034
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +09001035 private List<IMediaRouter2Manager> getManagers() {
1036 final List<IMediaRouter2Manager> managers = new ArrayList<>();
1037 MediaRouter2ServiceImpl service = mServiceRef.get();
1038 if (service == null) {
1039 return managers;
1040 }
1041 synchronized (service.mLock) {
1042 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
1043 managers.add(managerRecord.mManager);
1044 }
1045 }
1046 return managers;
1047 }
1048
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001049 private void notifyRoutesToClient(IMediaRouter2Client client) {
1050 List<MediaRoute2Info> routes = new ArrayList<>();
1051 for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) {
1052 routes.addAll(providerInfo.getRoutes());
1053 }
1054 if (routes.size() == 0) {
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001055 return;
1056 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001057 try {
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001058 client.notifyRoutesAdded(routes);
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001059 } catch (RemoteException ex) {
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001060 Slog.w(TAG, "Failed to notify all routes. Client probably died.", ex);
1061 }
1062 }
1063
Hyundo Moon4140fee2019-11-08 14:47:50 +09001064 private void notifyRouteSelectedToClient(IMediaRouter2Client client,
1065 MediaRoute2Info route, int reason, Bundle controlHints) {
1066 try {
1067 client.notifyRouteSelected(route, reason, controlHints);
1068 } catch (RemoteException ex) {
1069 Slog.w(TAG, "Failed to notify routes selected. Client probably died.", ex);
1070 }
1071 }
1072
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001073 private void notifyRoutesAddedToClients(List<IMediaRouter2Client> clients,
1074 List<MediaRoute2Info> routes) {
1075 for (IMediaRouter2Client client : clients) {
1076 try {
1077 client.notifyRoutesAdded(routes);
1078 } catch (RemoteException ex) {
1079 Slog.w(TAG, "Failed to notify routes added. Client probably died.", ex);
1080 }
1081 }
1082 }
1083
1084 private void notifyRoutesRemovedToClients(List<IMediaRouter2Client> clients,
1085 List<MediaRoute2Info> routes) {
1086 for (IMediaRouter2Client client : clients) {
1087 try {
1088 client.notifyRoutesRemoved(routes);
1089 } catch (RemoteException ex) {
1090 Slog.w(TAG, "Failed to notify routes removed. Client probably died.", ex);
1091 }
1092 }
1093 }
1094
1095 private void notifyRoutesChangedToClients(List<IMediaRouter2Client> clients,
1096 List<MediaRoute2Info> routes) {
1097 for (IMediaRouter2Client client : clients) {
1098 try {
1099 client.notifyRoutesChanged(routes);
1100 } catch (RemoteException ex) {
1101 Slog.w(TAG, "Failed to notify routes changed. Client probably died.", ex);
1102 }
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001103 }
1104 }
1105
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +09001106 private void notifyRoutesToManager(IMediaRouter2Manager manager) {
1107 List<MediaRoute2Info> routes = new ArrayList<>();
1108 for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) {
1109 routes.addAll(providerInfo.getRoutes());
1110 }
1111 if (routes.size() == 0) {
1112 return;
1113 }
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001114 try {
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +09001115 manager.notifyRoutesAdded(routes);
Kyunglyul Hyun23b3aaa2019-08-06 11:17:16 +09001116 } catch (RemoteException ex) {
Kyunglyul Hyun4ed68752019-11-04 16:05:34 +09001117 Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
1118 }
1119 }
1120
1121 private void notifyRoutesAddedToManagers(List<IMediaRouter2Manager> managers,
1122 List<MediaRoute2Info> routes) {
1123 for (IMediaRouter2Manager manager : managers) {
1124 try {
1125 manager.notifyRoutesAdded(routes);
1126 } catch (RemoteException ex) {
1127 Slog.w(TAG, "Failed to notify routes added. Manager probably died.", ex);
1128 }
1129 }
1130 }
1131
1132 private void notifyRoutesRemovedToManagers(List<IMediaRouter2Manager> managers,
1133 List<MediaRoute2Info> routes) {
1134 for (IMediaRouter2Manager manager : managers) {
1135 try {
1136 manager.notifyRoutesRemoved(routes);
1137 } catch (RemoteException ex) {
1138 Slog.w(TAG, "Failed to notify routes removed. Manager probably died.", ex);
1139 }
1140 }
1141 }
1142
1143 private void notifyRoutesChangedToManagers(List<IMediaRouter2Manager> managers,
1144 List<MediaRoute2Info> routes) {
1145 for (IMediaRouter2Manager manager : managers) {
1146 try {
1147 manager.notifyRoutesChanged(routes);
1148 } catch (RemoteException ex) {
1149 Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
1150 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001151 }
1152 }
1153
Kyunglyul Hyundafac542019-04-29 18:07:21 +09001154 private void updateClientUsage(ClientRecord clientRecord) {
1155 MediaRouter2ServiceImpl service = mServiceRef.get();
1156 if (service == null) {
1157 return;
1158 }
1159 List<IMediaRouter2Manager> managers = new ArrayList<>();
1160 synchronized (service.mLock) {
Hyundo Moon7d3ab332019-10-16 11:09:56 +09001161 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
1162 managers.add(managerRecord.mManager);
Kyunglyul Hyundafac542019-04-29 18:07:21 +09001163 }
1164 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +09001165 for (IMediaRouter2Manager manager : managers) {
Kyunglyul Hyundafac542019-04-29 18:07:21 +09001166 try {
Kyunglyul Hyuna0d47412019-07-01 11:14:24 +09001167 manager.notifyRouteSelected(clientRecord.mPackageName,
1168 clientRecord.mSelectedRoute);
1169 manager.notifyControlCategoriesChanged(clientRecord.mPackageName,
Kyunglyul Hyundafac542019-04-29 18:07:21 +09001170 clientRecord.mControlCategories);
1171 } catch (RemoteException ex) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +09001172 Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex);
Kyunglyul Hyundafac542019-04-29 18:07:21 +09001173 }
1174 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001175 }
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +09001176
Kyunglyul Hyun0332e9a22019-11-20 01:39:25 +00001177 private MediaRoute2Provider findProvider(String providerId) {
1178 for (MediaRoute2Provider provider : mMediaProviders) {
Kyunglyul Hyun7af73012019-10-11 20:01:30 +09001179 if (TextUtils.equals(provider.getUniqueId(), providerId)) {
Kyunglyul Hyun13bc1052019-05-31 17:00:36 +09001180 return provider;
1181 }
1182 }
1183 return null;
1184 }
Kyunglyul Hyun5aa60822019-04-18 16:57:53 +09001185 }
1186}