TvInputManagerService.java revision 9a22f0f0a631849d9c622c642d3ab0395f77584b
1/* 2 * Copyright (C) 2014 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.tv; 18 19import android.app.ActivityManager; 20import android.content.BroadcastReceiver; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.ServiceConnection; 26import android.content.pm.PackageManager; 27import android.content.pm.ResolveInfo; 28import android.content.pm.ServiceInfo; 29import android.graphics.Rect; 30import android.net.Uri; 31import android.os.Binder; 32import android.os.IBinder; 33import android.os.Process; 34import android.os.RemoteException; 35import android.os.UserHandle; 36import android.tv.ITvInputClient; 37import android.tv.ITvInputManager; 38import android.tv.ITvInputService; 39import android.tv.ITvInputServiceCallback; 40import android.tv.ITvInputSession; 41import android.tv.ITvInputSessionCallback; 42import android.tv.TvInputInfo; 43import android.tv.TvInputService; 44import android.util.ArrayMap; 45import android.util.Log; 46import android.util.Slog; 47import android.util.SparseArray; 48import android.view.Surface; 49 50import com.android.internal.content.PackageMonitor; 51import com.android.server.SystemService; 52 53import java.util.ArrayList; 54import java.util.HashMap; 55import java.util.List; 56import java.util.Map; 57 58/** This class provides a system service that manages television inputs. */ 59public final class TvInputManagerService extends SystemService { 60 // STOPSHIP: Turn debugging off. 61 private static final boolean DEBUG = true; 62 private static final String TAG = "TvInputManagerService"; 63 64 private final Context mContext; 65 66 // A global lock. 67 private final Object mLock = new Object(); 68 69 // ID of the current user. 70 private int mCurrentUserId = UserHandle.USER_OWNER; 71 72 // A map from user id to UserState. 73 private final SparseArray<UserState> mUserStates = new SparseArray<UserState>(); 74 75 public TvInputManagerService(Context context) { 76 super(context); 77 mContext = context; 78 registerBroadcastReceivers(); 79 synchronized (mLock) { 80 mUserStates.put(mCurrentUserId, new UserState()); 81 buildTvInputListLocked(mCurrentUserId); 82 } 83 } 84 85 @Override 86 public void onStart() { 87 publishBinderService(Context.TV_INPUT_SERVICE, new BinderService()); 88 } 89 90 private void registerBroadcastReceivers() { 91 PackageMonitor monitor = new PackageMonitor() { 92 @Override 93 public void onSomePackagesChanged() { 94 synchronized (mLock) { 95 buildTvInputListLocked(mCurrentUserId); 96 } 97 } 98 }; 99 monitor.register(mContext, null, UserHandle.ALL, true); 100 101 IntentFilter intentFilter = new IntentFilter(); 102 intentFilter.addAction(Intent.ACTION_USER_SWITCHED); 103 intentFilter.addAction(Intent.ACTION_USER_REMOVED); 104 mContext.registerReceiverAsUser(new BroadcastReceiver() { 105 @Override 106 public void onReceive(Context context, Intent intent) { 107 String action = intent.getAction(); 108 if (Intent.ACTION_USER_SWITCHED.equals(action)) { 109 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); 110 } else if (Intent.ACTION_USER_REMOVED.equals(action)) { 111 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); 112 } 113 } 114 }, UserHandle.ALL, intentFilter, null, null); 115 } 116 117 private void buildTvInputListLocked(int userId) { 118 UserState userState = getUserStateLocked(userId); 119 userState.inputList.clear(); 120 121 if (DEBUG) Slog.d(TAG, "buildTvInputList"); 122 PackageManager pm = mContext.getPackageManager(); 123 List<ResolveInfo> services = pm.queryIntentServices( 124 new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_SERVICES); 125 for (ResolveInfo ri : services) { 126 ServiceInfo si = ri.serviceInfo; 127 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) { 128 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission " 129 + android.Manifest.permission.BIND_TV_INPUT); 130 continue; 131 } 132 TvInputInfo info = new TvInputInfo(ri); 133 if (DEBUG) Slog.d(TAG, "add " + info.getId()); 134 userState.inputList.add(info); 135 } 136 } 137 138 private void switchUser(int userId) { 139 synchronized (mLock) { 140 if (mCurrentUserId == userId) { 141 return; 142 } 143 // final int oldUserId = mCurrentUserId; 144 // TODO: Release services and sessions in the old user state, if needed. 145 mCurrentUserId = userId; 146 147 UserState userState = mUserStates.get(userId); 148 if (userState == null) { 149 userState = new UserState(); 150 } 151 mUserStates.put(userId, userState); 152 buildTvInputListLocked(userId); 153 } 154 } 155 156 private void removeUser(int userId) { 157 synchronized (mLock) { 158 UserState userState = mUserStates.get(userId); 159 if (userState == null) { 160 return; 161 } 162 // Release created sessions. 163 for (SessionState state : userState.sessionStateMap.values()) { 164 if (state.session != null) { 165 try { 166 state.session.release(); 167 } catch (RemoteException e) { 168 Slog.e(TAG, "error in release", e); 169 } 170 } 171 } 172 userState.sessionStateMap.clear(); 173 174 // Unregister all callbacks and unbind all services. 175 for (ServiceState serviceState : userState.serviceStateMap.values()) { 176 if (serviceState.callback != null) { 177 try { 178 serviceState.service.unregisterCallback(serviceState.callback); 179 } catch (RemoteException e) { 180 Slog.e(TAG, "error in unregisterCallback", e); 181 } 182 } 183 serviceState.clients.clear(); 184 mContext.unbindService(serviceState.connection); 185 } 186 userState.serviceStateMap.clear(); 187 188 mUserStates.remove(userId); 189 } 190 } 191 192 private UserState getUserStateLocked(int userId) { 193 UserState userState = mUserStates.get(userId); 194 if (userState == null) { 195 throw new IllegalStateException("User state not found for user ID " + userId); 196 } 197 return userState; 198 } 199 200 private ServiceState getServiceStateLocked(ComponentName name, int userId) { 201 UserState userState = getUserStateLocked(userId); 202 ServiceState serviceState = userState.serviceStateMap.get(name); 203 if (serviceState == null) { 204 throw new IllegalStateException("Service state not found for " + name + " (userId=" + 205 userId + ")"); 206 } 207 return serviceState; 208 } 209 210 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) { 211 UserState userState = getUserStateLocked(userId); 212 SessionState sessionState = userState.sessionStateMap.get(sessionToken); 213 if (sessionState == null) { 214 throw new IllegalArgumentException("Session state not found for token " + sessionToken); 215 } 216 // Only the application that requested this session or the system can access it. 217 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) { 218 throw new SecurityException("Illegal access to the session with token " + sessionToken 219 + " from uid " + callingUid); 220 } 221 ITvInputSession session = sessionState.session; 222 if (session == null) { 223 throw new IllegalStateException("Session not yet created for token " + sessionToken); 224 } 225 return session; 226 } 227 228 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId, 229 String methodName) { 230 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false, 231 false, methodName, null); 232 } 233 234 private void updateServiceConnectionLocked(ComponentName name, int userId) { 235 UserState userState = getUserStateLocked(userId); 236 ServiceState serviceState = userState.serviceStateMap.get(name); 237 if (serviceState == null) { 238 return; 239 } 240 boolean isStateEmpty = serviceState.clients.size() == 0 241 && serviceState.sessionStateMap.size() == 0; 242 if (serviceState.service == null && !isStateEmpty && userId == mCurrentUserId) { 243 // This means that the service is not yet connected but its state indicates that we 244 // have pending requests. Then, connect the service. 245 if (serviceState.bound) { 246 // We have already bound to the service so we don't try to bind again until after we 247 // unbind later on. 248 return; 249 } 250 if (DEBUG) { 251 Slog.d(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId 252 + ")"); 253 } 254 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(name); 255 mContext.bindServiceAsUser(i, serviceState.connection, Context.BIND_AUTO_CREATE, 256 new UserHandle(userId)); 257 serviceState.bound = true; 258 } else if (serviceState.service != null && isStateEmpty) { 259 // This means that the service is already connected but its state indicates that we have 260 // nothing to do with it. Then, disconnect the service. 261 if (DEBUG) { 262 Slog.d(TAG, "unbindService(name=" + name.getClassName() + ")"); 263 } 264 mContext.unbindService(serviceState.connection); 265 userState.serviceStateMap.remove(name); 266 } 267 } 268 269 private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken, 270 final SessionState sessionState, final int userId) { 271 if (DEBUG) { 272 Slog.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName() 273 + ")"); 274 } 275 // Set up a callback to send the session token. 276 ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() { 277 @Override 278 public void onSessionCreated(ITvInputSession session) { 279 if (DEBUG) { 280 Slog.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")"); 281 } 282 synchronized (mLock) { 283 sessionState.session = session; 284 if (session == null) { 285 removeSessionStateLocked(sessionToken, userId); 286 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null, 287 sessionState.seq, userId); 288 } else { 289 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, 290 sessionToken, sessionState.seq, userId); 291 } 292 } 293 } 294 }; 295 296 // Create a session. When failed, send a null token immediately. 297 try { 298 service.createSession(callback); 299 } catch (RemoteException e) { 300 Slog.e(TAG, "error in createSession", e); 301 removeSessionStateLocked(sessionToken, userId); 302 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null, 303 sessionState.seq, userId); 304 } 305 } 306 307 private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name, 308 IBinder sessionToken, int seq, int userId) { 309 try { 310 client.onSessionCreated(name, sessionToken, seq); 311 } catch (RemoteException exception) { 312 Slog.e(TAG, "error in onSessionCreated", exception); 313 } 314 315 if (sessionToken == null) { 316 // This means that the session creation failed. We might want to disconnect the service. 317 updateServiceConnectionLocked(name, userId); 318 } 319 } 320 321 private void removeSessionStateLocked(IBinder sessionToken, int userId) { 322 // Remove the session state from the global session state map of the current user. 323 UserState userState = getUserStateLocked(userId); 324 SessionState sessionState = userState.sessionStateMap.remove(sessionToken); 325 326 // Also remove the session state from the session state map of the current service. 327 ServiceState serviceState = userState.serviceStateMap.get(sessionState.name); 328 if (serviceState != null) { 329 serviceState.sessionStateMap.remove(sessionToken); 330 } 331 updateServiceConnectionLocked(sessionState.name, userId); 332 } 333 334 private final class BinderService extends ITvInputManager.Stub { 335 @Override 336 public List<TvInputInfo> getTvInputList(int userId) { 337 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 338 Binder.getCallingUid(), userId, "getTvInputList"); 339 final long identity = Binder.clearCallingIdentity(); 340 try { 341 synchronized (mLock) { 342 UserState userState = getUserStateLocked(resolvedUserId); 343 return new ArrayList<TvInputInfo>(userState.inputList); 344 } 345 } finally { 346 Binder.restoreCallingIdentity(identity); 347 } 348 } 349 350 @Override 351 public boolean getAvailability(final ITvInputClient client, final ComponentName name, 352 int userId) { 353 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 354 Binder.getCallingUid(), userId, "getAvailability"); 355 final long identity = Binder.clearCallingIdentity(); 356 try { 357 synchronized (mLock) { 358 UserState userState = getUserStateLocked(resolvedUserId); 359 ServiceState serviceState = userState.serviceStateMap.get(name); 360 if (serviceState != null) { 361 // We already know the status of this input service. Return the cached 362 // status. 363 return serviceState.available; 364 } 365 } 366 } finally { 367 Binder.restoreCallingIdentity(identity); 368 } 369 return false; 370 } 371 372 @Override 373 public void registerCallback(final ITvInputClient client, final ComponentName name, 374 int userId) { 375 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 376 Binder.getCallingUid(), userId, "registerCallback"); 377 final long identity = Binder.clearCallingIdentity(); 378 try { 379 synchronized (mLock) { 380 // Create a new service callback and add it to the callback map of the current 381 // service. 382 UserState userState = getUserStateLocked(resolvedUserId); 383 ServiceState serviceState = userState.serviceStateMap.get(name); 384 if (serviceState == null) { 385 serviceState = new ServiceState(name, resolvedUserId); 386 userState.serviceStateMap.put(name, serviceState); 387 } 388 IBinder iBinder = client.asBinder(); 389 if (!serviceState.clients.contains(iBinder)) { 390 serviceState.clients.add(iBinder); 391 } 392 if (serviceState.service != null) { 393 if (serviceState.callback != null) { 394 // We already handled. 395 return; 396 } 397 serviceState.callback = new ServiceCallback(resolvedUserId); 398 try { 399 serviceState.service.registerCallback(serviceState.callback); 400 } catch (RemoteException e) { 401 Slog.e(TAG, "error in registerCallback", e); 402 } 403 } else { 404 updateServiceConnectionLocked(name, resolvedUserId); 405 } 406 } 407 } finally { 408 Binder.restoreCallingIdentity(identity); 409 } 410 } 411 412 @Override 413 public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) { 414 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 415 Binder.getCallingUid(), userId, "unregisterCallback"); 416 final long identity = Binder.clearCallingIdentity(); 417 try { 418 synchronized (mLock) { 419 UserState userState = getUserStateLocked(resolvedUserId); 420 ServiceState serviceState = userState.serviceStateMap.get(name); 421 if (serviceState == null) { 422 return; 423 } 424 425 // Remove this client from the client list and unregister the callback. 426 serviceState.clients.remove(client.asBinder()); 427 if (!serviceState.clients.isEmpty()) { 428 // We have other clients who want to keep the callback. Do this later. 429 return; 430 } 431 if (serviceState.service == null || serviceState.callback == null) { 432 return; 433 } 434 try { 435 serviceState.service.unregisterCallback(serviceState.callback); 436 } catch (RemoteException e) { 437 Slog.e(TAG, "error in unregisterCallback", e); 438 } finally { 439 serviceState.callback = null; 440 updateServiceConnectionLocked(name, resolvedUserId); 441 } 442 } 443 } finally { 444 Binder.restoreCallingIdentity(identity); 445 } 446 } 447 448 @Override 449 public void createSession(final ITvInputClient client, final ComponentName name, 450 int seq, int userId) { 451 final int callingUid = Binder.getCallingUid(); 452 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 453 userId, "createSession"); 454 final long identity = Binder.clearCallingIdentity(); 455 try { 456 synchronized (mLock) { 457 // Create a new session token and a session state. 458 IBinder sessionToken = new Binder(); 459 SessionState sessionState = new SessionState(name, client, seq, callingUid); 460 sessionState.session = null; 461 462 // Add them to the global session state map of the current user. 463 UserState userState = getUserStateLocked(resolvedUserId); 464 userState.sessionStateMap.put(sessionToken, sessionState); 465 466 // Also, add them to the session state map of the current service. 467 ServiceState serviceState = userState.serviceStateMap.get(name); 468 if (serviceState == null) { 469 serviceState = new ServiceState(name, resolvedUserId); 470 userState.serviceStateMap.put(name, serviceState); 471 } 472 serviceState.sessionStateMap.put(sessionToken, sessionState); 473 474 if (serviceState.service != null) { 475 createSessionInternalLocked(serviceState.service, sessionToken, 476 sessionState, resolvedUserId); 477 } else { 478 updateServiceConnectionLocked(name, resolvedUserId); 479 } 480 } 481 } finally { 482 Binder.restoreCallingIdentity(identity); 483 } 484 } 485 486 @Override 487 public void releaseSession(IBinder sessionToken, int userId) { 488 final int callingUid = Binder.getCallingUid(); 489 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 490 userId, "releaseSession"); 491 final long identity = Binder.clearCallingIdentity(); 492 try { 493 synchronized (mLock) { 494 // Release the session. 495 try { 496 getSessionLocked(sessionToken, callingUid, resolvedUserId).release(); 497 } catch (RemoteException e) { 498 Slog.e(TAG, "error in release", e); 499 } 500 501 removeSessionStateLocked(sessionToken, resolvedUserId); 502 } 503 } finally { 504 Binder.restoreCallingIdentity(identity); 505 } 506 } 507 508 @Override 509 public void setSurface(IBinder sessionToken, Surface surface, int userId) { 510 final int callingUid = Binder.getCallingUid(); 511 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 512 userId, "setSurface"); 513 final long identity = Binder.clearCallingIdentity(); 514 try { 515 synchronized (mLock) { 516 try { 517 getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface( 518 surface); 519 } catch (RemoteException e) { 520 Slog.e(TAG, "error in setSurface", e); 521 } 522 } 523 } finally { 524 Binder.restoreCallingIdentity(identity); 525 } 526 } 527 528 @Override 529 public void setVolume(IBinder sessionToken, float volume, int userId) { 530 final int callingUid = Binder.getCallingUid(); 531 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 532 userId, "setVolume"); 533 final long identity = Binder.clearCallingIdentity(); 534 try { 535 synchronized (mLock) { 536 try { 537 getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume( 538 volume); 539 } catch (RemoteException e) { 540 Slog.e(TAG, "error in setVolume", e); 541 } 542 } 543 } finally { 544 Binder.restoreCallingIdentity(identity); 545 } 546 } 547 548 @Override 549 public void tune(IBinder sessionToken, final Uri channelUri, int userId) { 550 final int callingUid = Binder.getCallingUid(); 551 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 552 userId, "tune"); 553 final long identity = Binder.clearCallingIdentity(); 554 try { 555 synchronized (mLock) { 556 try { 557 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri); 558 } catch (RemoteException e) { 559 Slog.e(TAG, "error in tune", e); 560 return; 561 } 562 } 563 } finally { 564 Binder.restoreCallingIdentity(identity); 565 } 566 } 567 568 @Override 569 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame, 570 int userId) { 571 final int callingUid = Binder.getCallingUid(); 572 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 573 userId, "createOverlayView"); 574 final long identity = Binder.clearCallingIdentity(); 575 try { 576 synchronized (mLock) { 577 try { 578 getSessionLocked(sessionToken, callingUid, resolvedUserId) 579 .createOverlayView(windowToken, frame); 580 } catch (RemoteException e) { 581 Slog.e(TAG, "error in createOverlayView", e); 582 } 583 } 584 } finally { 585 Binder.restoreCallingIdentity(identity); 586 } 587 } 588 589 @Override 590 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) { 591 final int callingUid = Binder.getCallingUid(); 592 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 593 userId, "relayoutOverlayView"); 594 final long identity = Binder.clearCallingIdentity(); 595 try { 596 synchronized (mLock) { 597 try { 598 getSessionLocked(sessionToken, callingUid, resolvedUserId) 599 .relayoutOverlayView(frame); 600 } catch (RemoteException e) { 601 Slog.e(TAG, "error in relayoutOverlayView", e); 602 } 603 } 604 } finally { 605 Binder.restoreCallingIdentity(identity); 606 } 607 } 608 609 @Override 610 public void removeOverlayView(IBinder sessionToken, int userId) { 611 final int callingUid = Binder.getCallingUid(); 612 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 613 userId, "removeOverlayView"); 614 final long identity = Binder.clearCallingIdentity(); 615 try { 616 synchronized (mLock) { 617 try { 618 getSessionLocked(sessionToken, callingUid, resolvedUserId) 619 .removeOverlayView(); 620 } catch (RemoteException e) { 621 Slog.e(TAG, "error in removeOverlayView", e); 622 } 623 } 624 } finally { 625 Binder.restoreCallingIdentity(identity); 626 } 627 } 628 } 629 630 private static final class UserState { 631 // A list of all known TV inputs on the system. 632 private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>(); 633 634 // A mapping from the name of a TV input service to its state. 635 private final Map<ComponentName, ServiceState> serviceStateMap = 636 new HashMap<ComponentName, ServiceState>(); 637 638 // A mapping from the token of a TV input session to its state. 639 private final Map<IBinder, SessionState> sessionStateMap = 640 new HashMap<IBinder, SessionState>(); 641 } 642 643 private final class ServiceState { 644 private final List<IBinder> clients = new ArrayList<IBinder>(); 645 private final ArrayMap<IBinder, SessionState> sessionStateMap = new ArrayMap<IBinder, 646 SessionState>(); 647 private final ServiceConnection connection; 648 649 private ITvInputService service; 650 private ServiceCallback callback; 651 private boolean bound; 652 private boolean available; 653 654 private ServiceState(ComponentName name, int userId) { 655 this.connection = new InputServiceConnection(userId); 656 } 657 } 658 659 private static final class SessionState { 660 private final ComponentName name; 661 private final ITvInputClient client; 662 private final int seq; 663 private final int callingUid; 664 665 private ITvInputSession session; 666 667 private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) { 668 this.name = name; 669 this.client = client; 670 this.seq = seq; 671 this.callingUid = callingUid; 672 } 673 } 674 675 private final class InputServiceConnection implements ServiceConnection { 676 private final int mUserId; 677 678 private InputServiceConnection(int userId) { 679 mUserId = userId; 680 } 681 682 @Override 683 public void onServiceConnected(ComponentName name, IBinder service) { 684 if (DEBUG) { 685 Slog.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")"); 686 } 687 synchronized (mLock) { 688 ServiceState serviceState = getServiceStateLocked(name, mUserId); 689 serviceState.service = ITvInputService.Stub.asInterface(service); 690 691 // Register a callback, if we need to. 692 if (!serviceState.clients.isEmpty() && serviceState.callback == null) { 693 serviceState.callback = new ServiceCallback(mUserId); 694 try { 695 serviceState.service.registerCallback(serviceState.callback); 696 } catch (RemoteException e) { 697 Slog.e(TAG, "error in registerCallback", e); 698 } 699 } 700 701 // And create sessions, if any. 702 for (Map.Entry<IBinder, SessionState> entry : serviceState.sessionStateMap 703 .entrySet()) { 704 createSessionInternalLocked(serviceState.service, entry.getKey(), 705 entry.getValue(), mUserId); 706 } 707 } 708 } 709 710 @Override 711 public void onServiceDisconnected(ComponentName name) { 712 if (DEBUG) { 713 Slog.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")"); 714 } 715 } 716 } 717 718 private final class ServiceCallback extends ITvInputServiceCallback.Stub { 719 private final int mUserId; 720 721 ServiceCallback(int userId) { 722 mUserId = userId; 723 } 724 725 @Override 726 public void onAvailabilityChanged(ComponentName name, boolean isAvailable) 727 throws RemoteException { 728 if (DEBUG) { 729 Slog.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable=" 730 + isAvailable + ")"); 731 } 732 synchronized (mLock) { 733 ServiceState serviceState = getServiceStateLocked(name, mUserId); 734 serviceState.available = isAvailable; 735 for (IBinder iBinder : serviceState.clients) { 736 ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder); 737 client.onAvailabilityChanged(name, isAvailable); 738 } 739 } 740 } 741 } 742} 743