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