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