TvInputManagerService.java revision 7de5e234715a3baa8905afa3dd0c5009af64541f
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.isEmpty() 237 && serviceState.sessionTokens.isEmpty(); 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 int userId) { 267 final SessionState sessionState = 268 getUserStateLocked(userId).sessionStateMap.get(sessionToken); 269 if (DEBUG) { 270 Log.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName() 271 + ")"); 272 } 273 // Set up a callback to send the session token. 274 ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() { 275 @Override 276 public void onSessionCreated(ITvInputSession session) { 277 if (DEBUG) { 278 Log.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")"); 279 } 280 synchronized (mLock) { 281 sessionState.session = session; 282 if (session == null) { 283 removeSessionStateLocked(sessionToken, userId); 284 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null, 285 sessionState.seq, userId); 286 } else { 287 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, 288 sessionToken, sessionState.seq, userId); 289 } 290 } 291 } 292 }; 293 294 // Create a session. When failed, send a null token immediately. 295 try { 296 service.createSession(callback); 297 } catch (RemoteException e) { 298 Log.e(TAG, "error in createSession", e); 299 removeSessionStateLocked(sessionToken, userId); 300 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null, 301 sessionState.seq, userId); 302 } 303 } 304 305 private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name, 306 IBinder sessionToken, int seq, int userId) { 307 try { 308 client.onSessionCreated(name, sessionToken, seq); 309 } catch (RemoteException exception) { 310 Log.e(TAG, "error in onSessionCreated", exception); 311 } 312 313 if (sessionToken == null) { 314 // This means that the session creation failed. We might want to disconnect the service. 315 updateServiceConnectionLocked(name, userId); 316 } 317 } 318 319 private void removeSessionStateLocked(IBinder sessionToken, int userId) { 320 // Remove the session state from the global session state map of the current user. 321 UserState userState = getUserStateLocked(userId); 322 SessionState sessionState = userState.sessionStateMap.remove(sessionToken); 323 324 // Also remove the session token from the session token list of the current service. 325 ServiceState serviceState = userState.serviceStateMap.get(sessionState.name); 326 if (serviceState != null) { 327 serviceState.sessionTokens.remove(sessionToken); 328 } 329 updateServiceConnectionLocked(sessionState.name, userId); 330 } 331 332 private final class BinderService extends ITvInputManager.Stub { 333 @Override 334 public List<TvInputInfo> getTvInputList(int userId) { 335 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 336 Binder.getCallingUid(), userId, "getTvInputList"); 337 final long identity = Binder.clearCallingIdentity(); 338 try { 339 synchronized (mLock) { 340 UserState userState = getUserStateLocked(resolvedUserId); 341 return new ArrayList<TvInputInfo>(userState.inputList); 342 } 343 } finally { 344 Binder.restoreCallingIdentity(identity); 345 } 346 } 347 348 @Override 349 public boolean getAvailability(final ITvInputClient client, final ComponentName name, 350 int userId) { 351 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 352 Binder.getCallingUid(), userId, "getAvailability"); 353 final long identity = Binder.clearCallingIdentity(); 354 try { 355 synchronized (mLock) { 356 UserState userState = getUserStateLocked(resolvedUserId); 357 ServiceState serviceState = userState.serviceStateMap.get(name); 358 if (serviceState != null) { 359 // We already know the status of this input service. Return the cached 360 // status. 361 return serviceState.available; 362 } 363 } 364 } finally { 365 Binder.restoreCallingIdentity(identity); 366 } 367 return false; 368 } 369 370 @Override 371 public void registerCallback(final ITvInputClient client, final ComponentName name, 372 int userId) { 373 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 374 Binder.getCallingUid(), userId, "registerCallback"); 375 final long identity = Binder.clearCallingIdentity(); 376 try { 377 synchronized (mLock) { 378 // Create a new service callback and add it to the callback map of the current 379 // service. 380 UserState userState = getUserStateLocked(resolvedUserId); 381 ServiceState serviceState = userState.serviceStateMap.get(name); 382 if (serviceState == null) { 383 serviceState = new ServiceState(name, resolvedUserId); 384 userState.serviceStateMap.put(name, serviceState); 385 } 386 IBinder iBinder = client.asBinder(); 387 if (!serviceState.clients.contains(iBinder)) { 388 serviceState.clients.add(iBinder); 389 } 390 if (serviceState.service != null) { 391 if (serviceState.callback != null) { 392 // We already handled. 393 return; 394 } 395 serviceState.callback = new ServiceCallback(resolvedUserId); 396 try { 397 serviceState.service.registerCallback(serviceState.callback); 398 } catch (RemoteException e) { 399 Log.e(TAG, "error in registerCallback", e); 400 } 401 } else { 402 updateServiceConnectionLocked(name, resolvedUserId); 403 } 404 } 405 } finally { 406 Binder.restoreCallingIdentity(identity); 407 } 408 } 409 410 @Override 411 public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) { 412 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 413 Binder.getCallingUid(), userId, "unregisterCallback"); 414 final long identity = Binder.clearCallingIdentity(); 415 try { 416 synchronized (mLock) { 417 UserState userState = getUserStateLocked(resolvedUserId); 418 ServiceState serviceState = userState.serviceStateMap.get(name); 419 if (serviceState == null) { 420 return; 421 } 422 423 // Remove this client from the client list and unregister the callback. 424 serviceState.clients.remove(client.asBinder()); 425 if (!serviceState.clients.isEmpty()) { 426 // We have other clients who want to keep the callback. Do this later. 427 return; 428 } 429 if (serviceState.service == null || serviceState.callback == null) { 430 return; 431 } 432 try { 433 serviceState.service.unregisterCallback(serviceState.callback); 434 } catch (RemoteException e) { 435 Log.e(TAG, "error in unregisterCallback", e); 436 } finally { 437 serviceState.callback = null; 438 updateServiceConnectionLocked(name, resolvedUserId); 439 } 440 } 441 } finally { 442 Binder.restoreCallingIdentity(identity); 443 } 444 } 445 446 @Override 447 public void createSession(final ITvInputClient client, final ComponentName name, 448 int seq, int userId) { 449 final int callingUid = Binder.getCallingUid(); 450 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 451 userId, "createSession"); 452 final long identity = Binder.clearCallingIdentity(); 453 try { 454 synchronized (mLock) { 455 // Create a new session token and a session state. 456 IBinder sessionToken = new Binder(); 457 SessionState sessionState = new SessionState(name, client, seq, callingUid); 458 sessionState.session = null; 459 460 // Add them to the global session state map of the current user. 461 UserState userState = getUserStateLocked(resolvedUserId); 462 userState.sessionStateMap.put(sessionToken, sessionState); 463 464 // Also, add them to the session state map of the current service. 465 ServiceState serviceState = userState.serviceStateMap.get(name); 466 if (serviceState == null) { 467 serviceState = new ServiceState(name, resolvedUserId); 468 userState.serviceStateMap.put(name, serviceState); 469 } 470 serviceState.sessionTokens.add(sessionToken); 471 472 if (serviceState.service != null) { 473 createSessionInternalLocked(serviceState.service, sessionToken, 474 resolvedUserId); 475 } else { 476 updateServiceConnectionLocked(name, resolvedUserId); 477 } 478 } 479 } finally { 480 Binder.restoreCallingIdentity(identity); 481 } 482 } 483 484 @Override 485 public void releaseSession(IBinder sessionToken, int userId) { 486 final int callingUid = Binder.getCallingUid(); 487 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 488 userId, "releaseSession"); 489 final long identity = Binder.clearCallingIdentity(); 490 try { 491 synchronized (mLock) { 492 // Release the session. 493 try { 494 getSessionLocked(sessionToken, callingUid, resolvedUserId).release(); 495 } catch (RemoteException e) { 496 Log.e(TAG, "error in release", e); 497 } 498 499 removeSessionStateLocked(sessionToken, resolvedUserId); 500 } 501 } finally { 502 Binder.restoreCallingIdentity(identity); 503 } 504 } 505 506 @Override 507 public void setSurface(IBinder sessionToken, Surface surface, int userId) { 508 final int callingUid = Binder.getCallingUid(); 509 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 510 userId, "setSurface"); 511 final long identity = Binder.clearCallingIdentity(); 512 try { 513 synchronized (mLock) { 514 try { 515 getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface( 516 surface); 517 } catch (RemoteException e) { 518 Log.e(TAG, "error in setSurface", e); 519 } 520 } 521 } finally { 522 Binder.restoreCallingIdentity(identity); 523 } 524 } 525 526 @Override 527 public void setVolume(IBinder sessionToken, float volume, int userId) { 528 final int callingUid = Binder.getCallingUid(); 529 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 530 userId, "setVolume"); 531 final long identity = Binder.clearCallingIdentity(); 532 try { 533 synchronized (mLock) { 534 try { 535 getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume( 536 volume); 537 } catch (RemoteException e) { 538 Log.e(TAG, "error in setVolume", e); 539 } 540 } 541 } finally { 542 Binder.restoreCallingIdentity(identity); 543 } 544 } 545 546 @Override 547 public void tune(IBinder sessionToken, final Uri channelUri, int userId) { 548 final int callingUid = Binder.getCallingUid(); 549 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 550 userId, "tune"); 551 final long identity = Binder.clearCallingIdentity(); 552 try { 553 synchronized (mLock) { 554 SessionState sessionState = getUserStateLocked(resolvedUserId) 555 .sessionStateMap.get(sessionToken); 556 final String serviceName = sessionState.name.getClassName(); 557 try { 558 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri); 559 } catch (RemoteException e) { 560 Log.e(TAG, "error in tune", e); 561 return; 562 } 563 } 564 } finally { 565 Binder.restoreCallingIdentity(identity); 566 } 567 } 568 } 569 570 private static final class UserState { 571 // A list of all known TV inputs on the system. 572 private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>(); 573 574 // A mapping from the name of a TV input service to its state. 575 private final Map<ComponentName, ServiceState> serviceStateMap = 576 new HashMap<ComponentName, ServiceState>(); 577 578 // A mapping from the token of a TV input session to its state. 579 private final Map<IBinder, SessionState> sessionStateMap = 580 new HashMap<IBinder, SessionState>(); 581 } 582 583 private final class ServiceState { 584 private final List<IBinder> clients = new ArrayList<IBinder>(); 585 private final List<IBinder> sessionTokens = new ArrayList<IBinder>(); 586 private final ServiceConnection connection; 587 588 private ITvInputService service; 589 private ServiceCallback callback; 590 private boolean bound; 591 private boolean available; 592 593 private ServiceState(ComponentName name, int userId) { 594 this.connection = new InputServiceConnection(userId); 595 } 596 } 597 598 private static final class SessionState { 599 private final ComponentName name; 600 private final ITvInputClient client; 601 private final int seq; 602 private final int callingUid; 603 604 private ITvInputSession session; 605 606 private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) { 607 this.name = name; 608 this.client = client; 609 this.seq = seq; 610 this.callingUid = callingUid; 611 } 612 } 613 614 private final class InputServiceConnection implements ServiceConnection { 615 private final int mUserId; 616 617 private InputServiceConnection(int userId) { 618 mUserId = userId; 619 } 620 621 @Override 622 public void onServiceConnected(ComponentName name, IBinder service) { 623 if (DEBUG) { 624 Log.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")"); 625 } 626 synchronized (mLock) { 627 ServiceState serviceState = getServiceStateLocked(name, mUserId); 628 serviceState.service = ITvInputService.Stub.asInterface(service); 629 630 // Register a callback, if we need to. 631 if (!serviceState.clients.isEmpty() && serviceState.callback == null) { 632 serviceState.callback = new ServiceCallback(mUserId); 633 try { 634 serviceState.service.registerCallback(serviceState.callback); 635 } catch (RemoteException e) { 636 Log.e(TAG, "error in registerCallback", e); 637 } 638 } 639 640 // And create sessions, if any. 641 for (IBinder sessionToken : serviceState.sessionTokens) { 642 createSessionInternalLocked(serviceState.service, sessionToken, mUserId); 643 } 644 } 645 } 646 647 @Override 648 public void onServiceDisconnected(ComponentName name) { 649 if (DEBUG) { 650 Log.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")"); 651 } 652 } 653 } 654 655 private final class ServiceCallback extends ITvInputServiceCallback.Stub { 656 private final int mUserId; 657 658 ServiceCallback(int userId) { 659 mUserId = userId; 660 } 661 662 @Override 663 public void onAvailabilityChanged(ComponentName name, boolean isAvailable) 664 throws RemoteException { 665 if (DEBUG) { 666 Log.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable=" 667 + isAvailable + ")"); 668 } 669 synchronized (mLock) { 670 ServiceState serviceState = getServiceStateLocked(name, mUserId); 671 serviceState.available = isAvailable; 672 for (IBinder iBinder : serviceState.clients) { 673 ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder); 674 client.onAvailabilityChanged(name, isAvailable); 675 } 676 } 677 } 678 } 679} 680