TvInputManagerService.java revision 3957091ba8f08c02b5e781098cb955a5f697a1ff
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 // Release created sessions. 155 UserState userState = getUserStateLocked(userId); 156 for (SessionState state : userState.sessionStateMap.values()) { 157 if (state.session != null) { 158 try { 159 state.session.release(); 160 } catch (RemoteException e) { 161 Log.e(TAG, "error in release", e); 162 } 163 } 164 } 165 userState.sessionStateMap.clear(); 166 167 // Unregister all callbacks and unbind all services. 168 for (ServiceState serviceState : userState.serviceStateMap.values()) { 169 if (serviceState.callback != null) { 170 try { 171 serviceState.service.unregisterCallback(serviceState.callback); 172 } catch (RemoteException e) { 173 Log.e(TAG, "error in unregisterCallback", e); 174 } 175 } 176 serviceState.clients.clear(); 177 mContext.unbindService(serviceState.connection); 178 } 179 userState.serviceStateMap.clear(); 180 181 mUserStates.remove(userId); 182 } 183 } 184 185 private UserState getUserStateLocked(int userId) { 186 UserState userState = mUserStates.get(userId); 187 if (userState == null) { 188 throw new IllegalStateException("User state not found for user ID " + userId); 189 } 190 return userState; 191 } 192 193 private ServiceState getServiceStateLocked(ComponentName name, int userId) { 194 UserState userState = getUserStateLocked(userId); 195 ServiceState serviceState = userState.serviceStateMap.get(name); 196 if (serviceState == null) { 197 throw new IllegalStateException("Service state not found for " + name + " (userId=" + 198 userId + ")"); 199 } 200 return serviceState; 201 } 202 203 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) { 204 UserState userState = getUserStateLocked(userId); 205 SessionState sessionState = userState.sessionStateMap.get(sessionToken); 206 if (sessionState == null) { 207 throw new IllegalArgumentException("Session state not found for token " + sessionToken); 208 } 209 // Only the application that requested this session or the system can access it. 210 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) { 211 throw new SecurityException("Illegal access to the session with token " + sessionToken 212 + " from uid " + callingUid); 213 } 214 ITvInputSession session = sessionState.session; 215 if (session == null) { 216 throw new IllegalStateException("Session not yet created for token " + sessionToken); 217 } 218 return session; 219 } 220 221 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId, 222 String methodName) { 223 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false, 224 false, methodName, null); 225 } 226 227 private void updateServiceConnectionLocked(ComponentName name, int userId) { 228 UserState userState = getUserStateLocked(userId); 229 ServiceState serviceState = userState.serviceStateMap.get(name); 230 if (serviceState == null) { 231 return; 232 } 233 boolean isStateEmpty = serviceState.clients.size() == 0 234 && serviceState.sessionStateMap.size() == 0; 235 if (serviceState.service == null && !isStateEmpty && userId == mCurrentUserId) { 236 // This means that the service is not yet connected but its state indicates that we 237 // have pending requests. Then, connect the service. 238 if (serviceState.bound) { 239 // We have already bound to the service so we don't try to bind again until after we 240 // unbind later on. 241 return; 242 } 243 if (DEBUG) { 244 Log.i(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId 245 + ")"); 246 } 247 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(name); 248 mContext.bindServiceAsUser(i, serviceState.connection, Context.BIND_AUTO_CREATE, 249 new UserHandle(userId)); 250 serviceState.bound = true; 251 } else if (serviceState.service != null && isStateEmpty) { 252 // This means that the service is already connected but its state indicates that we have 253 // nothing to do with it. Then, disconnect the service. 254 if (DEBUG) { 255 Log.i(TAG, "unbindService(name=" + name.getClassName() + ")"); 256 } 257 mContext.unbindService(serviceState.connection); 258 userState.serviceStateMap.remove(name); 259 } 260 } 261 262 private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken, 263 final SessionState sessionState, final int userId) { 264 if (DEBUG) { 265 Log.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName() 266 + ")"); 267 } 268 // Set up a callback to send the session token. 269 ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() { 270 @Override 271 public void onSessionCreated(ITvInputSession session) { 272 if (DEBUG) { 273 Log.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")"); 274 } 275 synchronized (mLock) { 276 sessionState.session = session; 277 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, 278 sessionToken, sessionState.seq, userId); 279 } 280 } 281 }; 282 283 // Create a session. When failed, send a null token immediately. 284 try { 285 service.createSession(callback); 286 } catch (RemoteException e) { 287 Log.e(TAG, "error in createSession", e); 288 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null, 289 sessionState.seq, userId); 290 } 291 } 292 293 private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name, 294 IBinder sessionToken, int seq, int userId) { 295 try { 296 client.onSessionCreated(name, sessionToken, seq); 297 } catch (RemoteException exception) { 298 Log.e(TAG, "error in onSessionCreated", exception); 299 } 300 301 if (sessionToken == null) { 302 // This means that the session creation failed. We might want to disconnect the service. 303 updateServiceConnectionLocked(name, userId); 304 } 305 } 306 307 private final class BinderService extends ITvInputManager.Stub { 308 @Override 309 public List<TvInputInfo> getTvInputList(int userId) { 310 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 311 Binder.getCallingUid(), userId, "getTvInputList"); 312 final long identity = Binder.clearCallingIdentity(); 313 try { 314 synchronized (mLock) { 315 UserState userState = getUserStateLocked(resolvedUserId); 316 return new ArrayList<TvInputInfo>(userState.inputList); 317 } 318 } finally { 319 Binder.restoreCallingIdentity(identity); 320 } 321 } 322 323 @Override 324 public boolean getAvailability(final ITvInputClient client, final ComponentName name, 325 int userId) { 326 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 327 Binder.getCallingUid(), userId, "getAvailability"); 328 final long identity = Binder.clearCallingIdentity(); 329 try { 330 synchronized (mLock) { 331 UserState userState = getUserStateLocked(resolvedUserId); 332 ServiceState serviceState = userState.serviceStateMap.get(name); 333 if (serviceState != null) { 334 // We already know the status of this input service. Return the cached 335 // status. 336 return serviceState.available; 337 } 338 } 339 } finally { 340 Binder.restoreCallingIdentity(identity); 341 } 342 return false; 343 } 344 345 @Override 346 public void registerCallback(final ITvInputClient client, final ComponentName name, 347 int userId) { 348 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 349 Binder.getCallingUid(), userId, "registerCallback"); 350 final long identity = Binder.clearCallingIdentity(); 351 try { 352 synchronized (mLock) { 353 // Create a new service callback and add it to the callback map of the current 354 // service. 355 UserState userState = getUserStateLocked(resolvedUserId); 356 ServiceState serviceState = userState.serviceStateMap.get(name); 357 if (serviceState == null) { 358 serviceState = new ServiceState(name, resolvedUserId); 359 userState.serviceStateMap.put(name, serviceState); 360 } 361 IBinder iBinder = client.asBinder(); 362 if (!serviceState.clients.contains(iBinder)) { 363 serviceState.clients.add(iBinder); 364 } 365 if (serviceState.service != null) { 366 if (serviceState.callback != null) { 367 // We already handled. 368 return; 369 } 370 serviceState.callback = new ServiceCallback(resolvedUserId); 371 try { 372 serviceState.service.registerCallback(serviceState.callback); 373 } catch (RemoteException e) { 374 Log.e(TAG, "error in registerCallback", e); 375 } 376 } else { 377 updateServiceConnectionLocked(name, resolvedUserId); 378 } 379 } 380 } finally { 381 Binder.restoreCallingIdentity(identity); 382 } 383 } 384 385 @Override 386 public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) { 387 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 388 Binder.getCallingUid(), userId, "unregisterCallback"); 389 final long identity = Binder.clearCallingIdentity(); 390 try { 391 synchronized (mLock) { 392 UserState userState = getUserStateLocked(resolvedUserId); 393 ServiceState serviceState = userState.serviceStateMap.get(name); 394 if (serviceState == null) { 395 return; 396 } 397 398 // Remove this client from the client list and unregister the callback. 399 serviceState.clients.remove(client.asBinder()); 400 if (!serviceState.clients.isEmpty()) { 401 // We have other clients who want to keep the callback. Do this later. 402 return; 403 } 404 if (serviceState.service == null || serviceState.callback == null) { 405 return; 406 } 407 try { 408 serviceState.service.unregisterCallback(serviceState.callback); 409 } catch (RemoteException e) { 410 Log.e(TAG, "error in unregisterCallback", e); 411 } finally { 412 serviceState.callback = null; 413 updateServiceConnectionLocked(name, resolvedUserId); 414 } 415 } 416 } finally { 417 Binder.restoreCallingIdentity(identity); 418 } 419 } 420 421 @Override 422 public void createSession(final ITvInputClient client, final ComponentName name, 423 int seq, int userId) { 424 final int callingUid = Binder.getCallingUid(); 425 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 426 userId, "createSession"); 427 final long identity = Binder.clearCallingIdentity(); 428 try { 429 synchronized (mLock) { 430 // Create a new session token and a session state. 431 IBinder sessionToken = new Binder(); 432 SessionState sessionState = new SessionState(name, client, seq, callingUid); 433 sessionState.session = null; 434 435 // Add them to the global session state map of the current user. 436 UserState userState = getUserStateLocked(resolvedUserId); 437 userState.sessionStateMap.put(sessionToken, sessionState); 438 439 // Also, add them to the session state map of the current service. 440 ServiceState serviceState = userState.serviceStateMap.get(name); 441 if (serviceState == null) { 442 serviceState = new ServiceState(name, resolvedUserId); 443 userState.serviceStateMap.put(name, serviceState); 444 } 445 serviceState.sessionStateMap.put(sessionToken, sessionState); 446 447 if (serviceState.service != null) { 448 createSessionInternalLocked(serviceState.service, sessionToken, 449 sessionState, resolvedUserId); 450 } else { 451 updateServiceConnectionLocked(name, resolvedUserId); 452 } 453 } 454 } finally { 455 Binder.restoreCallingIdentity(identity); 456 } 457 } 458 459 @Override 460 public void releaseSession(IBinder sessionToken, int userId) { 461 final int callingUid = Binder.getCallingUid(); 462 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 463 userId, "releaseSession"); 464 final long identity = Binder.clearCallingIdentity(); 465 try { 466 synchronized (mLock) { 467 // Release the session. 468 try { 469 getSessionLocked(sessionToken, callingUid, resolvedUserId).release(); 470 } catch (RemoteException e) { 471 Log.e(TAG, "error in release", e); 472 } 473 474 // Remove its state from the global session state map of the current user. 475 UserState userState = getUserStateLocked(resolvedUserId); 476 SessionState sessionState = userState.sessionStateMap.remove(sessionToken); 477 478 // Also remove it from the session state map of the current service. 479 ServiceState serviceState = userState.serviceStateMap.get(sessionState.name); 480 if (serviceState != null) { 481 serviceState.sessionStateMap.remove(sessionToken); 482 } 483 484 updateServiceConnectionLocked(sessionState.name, resolvedUserId); 485 } 486 } finally { 487 Binder.restoreCallingIdentity(identity); 488 } 489 } 490 491 @Override 492 public void setSurface(IBinder sessionToken, Surface surface, int userId) { 493 final int callingUid = Binder.getCallingUid(); 494 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 495 userId, "setSurface"); 496 final long identity = Binder.clearCallingIdentity(); 497 try { 498 synchronized (mLock) { 499 try { 500 getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface( 501 surface); 502 } catch (RemoteException e) { 503 Log.e(TAG, "error in setSurface", e); 504 } 505 } 506 } finally { 507 Binder.restoreCallingIdentity(identity); 508 } 509 } 510 511 @Override 512 public void setVolume(IBinder sessionToken, float volume, int userId) { 513 final int callingUid = Binder.getCallingUid(); 514 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 515 userId, "setVolume"); 516 final long identity = Binder.clearCallingIdentity(); 517 try { 518 synchronized (mLock) { 519 try { 520 getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume( 521 volume); 522 } catch (RemoteException e) { 523 Log.e(TAG, "error in setVolume", e); 524 } 525 } 526 } finally { 527 Binder.restoreCallingIdentity(identity); 528 } 529 } 530 531 @Override 532 public void tune(IBinder sessionToken, final Uri channelUri, int userId) { 533 final int callingUid = Binder.getCallingUid(); 534 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 535 userId, "tune"); 536 final long identity = Binder.clearCallingIdentity(); 537 try { 538 synchronized (mLock) { 539 SessionState sessionState = getUserStateLocked(resolvedUserId) 540 .sessionStateMap.get(sessionToken); 541 final String serviceName = sessionState.name.getClassName(); 542 try { 543 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri); 544 } catch (RemoteException e) { 545 Log.e(TAG, "error in tune", e); 546 return; 547 } 548 } 549 } finally { 550 Binder.restoreCallingIdentity(identity); 551 } 552 } 553 } 554 555 private static final class UserState { 556 // A list of all known TV inputs on the system. 557 private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>(); 558 559 // A mapping from the name of a TV input service to its state. 560 private final Map<ComponentName, ServiceState> serviceStateMap = 561 new HashMap<ComponentName, ServiceState>(); 562 563 // A mapping from the token of a TV input session to its state. 564 private final Map<IBinder, SessionState> sessionStateMap = 565 new HashMap<IBinder, SessionState>(); 566 } 567 568 private final class ServiceState { 569 private final List<IBinder> clients = new ArrayList<IBinder>(); 570 private final ArrayMap<IBinder, SessionState> sessionStateMap = new ArrayMap<IBinder, 571 SessionState>(); 572 private final ServiceConnection connection; 573 574 private ITvInputService service; 575 private ServiceCallback callback; 576 private boolean bound; 577 private boolean available; 578 579 private ServiceState(ComponentName name, int userId) { 580 this.connection = new InputServiceConnection(userId); 581 } 582 } 583 584 private static final class SessionState { 585 private final ComponentName name; 586 private final ITvInputClient client; 587 private final int seq; 588 private final int callingUid; 589 590 private ITvInputSession session; 591 592 private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) { 593 this.name = name; 594 this.client = client; 595 this.seq = seq; 596 this.callingUid = callingUid; 597 } 598 } 599 600 private final class InputServiceConnection implements ServiceConnection { 601 private final int mUserId; 602 603 private InputServiceConnection(int userId) { 604 mUserId = userId; 605 } 606 607 @Override 608 public void onServiceConnected(ComponentName name, IBinder service) { 609 if (DEBUG) { 610 Log.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")"); 611 } 612 synchronized (mLock) { 613 ServiceState serviceState = getServiceStateLocked(name, mUserId); 614 serviceState.service = ITvInputService.Stub.asInterface(service); 615 616 // Register a callback, if we need to. 617 if (!serviceState.clients.isEmpty() && serviceState.callback == null) { 618 serviceState.callback = new ServiceCallback(mUserId); 619 try { 620 serviceState.service.registerCallback(serviceState.callback); 621 } catch (RemoteException e) { 622 Log.e(TAG, "error in registerCallback", e); 623 } 624 } 625 626 // And create sessions, if any. 627 for (Map.Entry<IBinder, SessionState> entry : serviceState.sessionStateMap 628 .entrySet()) { 629 createSessionInternalLocked(serviceState.service, entry.getKey(), 630 entry.getValue(), mUserId); 631 } 632 } 633 } 634 635 @Override 636 public void onServiceDisconnected(ComponentName name) { 637 if (DEBUG) { 638 Log.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")"); 639 } 640 } 641 } 642 643 private final class ServiceCallback extends ITvInputServiceCallback.Stub { 644 private final int mUserId; 645 646 ServiceCallback(int userId) { 647 mUserId = userId; 648 } 649 650 @Override 651 public void onAvailabilityChanged(ComponentName name, boolean isAvailable) 652 throws RemoteException { 653 if (DEBUG) { 654 Log.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable=" 655 + isAvailable + ")"); 656 } 657 synchronized (mLock) { 658 ServiceState serviceState = getServiceStateLocked(name, mUserId); 659 serviceState.available = isAvailable; 660 for (IBinder iBinder : serviceState.clients) { 661 ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder); 662 client.onAvailabilityChanged(name, isAvailable); 663 } 664 } 665 } 666 } 667} 668