TvInputManagerService.java revision f836206818ce338db83a3c23c486fb8cab29cb6d
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.ContentResolver; 23import android.content.ContentUris; 24import android.content.ContentValues; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.ServiceConnection; 29import android.content.pm.PackageManager; 30import android.content.pm.ResolveInfo; 31import android.content.pm.ServiceInfo; 32import android.database.Cursor; 33import android.graphics.Rect; 34import android.net.Uri; 35import android.os.Binder; 36import android.os.Handler; 37import android.os.IBinder; 38import android.os.Looper; 39import android.os.Message; 40import android.os.Process; 41import android.os.RemoteException; 42import android.os.UserHandle; 43import android.provider.TvContract; 44import android.tv.ITvInputClient; 45import android.tv.ITvInputManager; 46import android.tv.ITvInputService; 47import android.tv.ITvInputServiceCallback; 48import android.tv.ITvInputSession; 49import android.tv.ITvInputSessionCallback; 50import android.tv.TvInputInfo; 51import android.tv.TvInputService; 52import android.util.Slog; 53import android.util.SparseArray; 54import android.view.InputChannel; 55import android.view.Surface; 56 57import com.android.internal.content.PackageMonitor; 58import com.android.internal.os.SomeArgs; 59import com.android.server.IoThread; 60import com.android.server.SystemService; 61 62import java.util.ArrayList; 63import java.util.HashMap; 64import java.util.List; 65import java.util.Map; 66 67/** This class provides a system service that manages television inputs. */ 68public final class TvInputManagerService extends SystemService { 69 // STOPSHIP: Turn debugging off. 70 private static final boolean DEBUG = true; 71 private static final String TAG = "TvInputManagerService"; 72 73 private final Context mContext; 74 75 private final ContentResolver mContentResolver; 76 77 // A global lock. 78 private final Object mLock = new Object(); 79 80 // ID of the current user. 81 private int mCurrentUserId = UserHandle.USER_OWNER; 82 83 // A map from user id to UserState. 84 private final SparseArray<UserState> mUserStates = new SparseArray<UserState>(); 85 86 private final Handler mLogHandler; 87 88 public TvInputManagerService(Context context) { 89 super(context); 90 91 mContext = context; 92 mContentResolver = context.getContentResolver(); 93 mLogHandler = new LogHandler(IoThread.get().getLooper()); 94 95 registerBroadcastReceivers(); 96 97 synchronized (mLock) { 98 mUserStates.put(mCurrentUserId, new UserState()); 99 buildTvInputListLocked(mCurrentUserId); 100 } 101 } 102 103 @Override 104 public void onStart() { 105 publishBinderService(Context.TV_INPUT_SERVICE, new BinderService()); 106 } 107 108 private void registerBroadcastReceivers() { 109 PackageMonitor monitor = new PackageMonitor() { 110 @Override 111 public void onSomePackagesChanged() { 112 synchronized (mLock) { 113 buildTvInputListLocked(mCurrentUserId); 114 } 115 } 116 }; 117 monitor.register(mContext, null, UserHandle.ALL, true); 118 119 IntentFilter intentFilter = new IntentFilter(); 120 intentFilter.addAction(Intent.ACTION_USER_SWITCHED); 121 intentFilter.addAction(Intent.ACTION_USER_REMOVED); 122 mContext.registerReceiverAsUser(new BroadcastReceiver() { 123 @Override 124 public void onReceive(Context context, Intent intent) { 125 String action = intent.getAction(); 126 if (Intent.ACTION_USER_SWITCHED.equals(action)) { 127 switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); 128 } else if (Intent.ACTION_USER_REMOVED.equals(action)) { 129 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); 130 } 131 } 132 }, UserHandle.ALL, intentFilter, null, null); 133 } 134 135 private void buildTvInputListLocked(int userId) { 136 UserState userState = getUserStateLocked(userId); 137 userState.inputList.clear(); 138 139 if (DEBUG) Slog.d(TAG, "buildTvInputList"); 140 PackageManager pm = mContext.getPackageManager(); 141 List<ResolveInfo> services = pm.queryIntentServices( 142 new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_SERVICES); 143 for (ResolveInfo ri : services) { 144 ServiceInfo si = ri.serviceInfo; 145 if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) { 146 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission " 147 + android.Manifest.permission.BIND_TV_INPUT); 148 continue; 149 } 150 TvInputInfo info = new TvInputInfo(ri); 151 if (DEBUG) Slog.d(TAG, "add " + info.getId()); 152 userState.inputList.add(info); 153 } 154 } 155 156 private void switchUser(int userId) { 157 synchronized (mLock) { 158 if (mCurrentUserId == userId) { 159 return; 160 } 161 // final int oldUserId = mCurrentUserId; 162 // TODO: Release services and sessions in the old user state, if needed. 163 mCurrentUserId = userId; 164 165 UserState userState = mUserStates.get(userId); 166 if (userState == null) { 167 userState = new UserState(); 168 } 169 mUserStates.put(userId, userState); 170 buildTvInputListLocked(userId); 171 } 172 } 173 174 private void removeUser(int userId) { 175 synchronized (mLock) { 176 UserState userState = mUserStates.get(userId); 177 if (userState == null) { 178 return; 179 } 180 // Release created sessions. 181 for (SessionState state : userState.sessionStateMap.values()) { 182 if (state.session != null) { 183 try { 184 state.session.release(); 185 } catch (RemoteException e) { 186 Slog.e(TAG, "error in release", e); 187 } 188 } 189 } 190 userState.sessionStateMap.clear(); 191 192 // Unregister all callbacks and unbind all services. 193 for (ServiceState serviceState : userState.serviceStateMap.values()) { 194 if (serviceState.callback != null) { 195 try { 196 serviceState.service.unregisterCallback(serviceState.callback); 197 } catch (RemoteException e) { 198 Slog.e(TAG, "error in unregisterCallback", e); 199 } 200 } 201 serviceState.clients.clear(); 202 mContext.unbindService(serviceState.connection); 203 } 204 userState.serviceStateMap.clear(); 205 206 mUserStates.remove(userId); 207 } 208 } 209 210 private UserState getUserStateLocked(int userId) { 211 UserState userState = mUserStates.get(userId); 212 if (userState == null) { 213 throw new IllegalStateException("User state not found for user ID " + userId); 214 } 215 return userState; 216 } 217 218 private ServiceState getServiceStateLocked(ComponentName name, int userId) { 219 UserState userState = getUserStateLocked(userId); 220 ServiceState serviceState = userState.serviceStateMap.get(name); 221 if (serviceState == null) { 222 throw new IllegalStateException("Service state not found for " + name + " (userId=" 223 + userId + ")"); 224 } 225 return serviceState; 226 } 227 228 private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) { 229 UserState userState = getUserStateLocked(userId); 230 SessionState sessionState = userState.sessionStateMap.get(sessionToken); 231 if (sessionState == null) { 232 throw new IllegalArgumentException("Session state not found for token " + sessionToken); 233 } 234 // Only the application that requested this session or the system can access it. 235 if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) { 236 throw new SecurityException("Illegal access to the session with token " + sessionToken 237 + " from uid " + callingUid); 238 } 239 ITvInputSession session = sessionState.session; 240 if (session == null) { 241 throw new IllegalStateException("Session not yet created for token " + sessionToken); 242 } 243 return session; 244 } 245 246 private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId, 247 String methodName) { 248 return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false, 249 false, methodName, null); 250 } 251 252 private void updateServiceConnectionLocked(ComponentName name, int userId) { 253 UserState userState = getUserStateLocked(userId); 254 ServiceState serviceState = userState.serviceStateMap.get(name); 255 if (serviceState == null) { 256 return; 257 } 258 boolean isStateEmpty = serviceState.clients.isEmpty() 259 && serviceState.sessionTokens.isEmpty(); 260 if (serviceState.service == null && !isStateEmpty && userId == mCurrentUserId) { 261 // This means that the service is not yet connected but its state indicates that we 262 // have pending requests. Then, connect the service. 263 if (serviceState.bound) { 264 // We have already bound to the service so we don't try to bind again until after we 265 // unbind later on. 266 return; 267 } 268 if (DEBUG) { 269 Slog.d(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId 270 + ")"); 271 } 272 Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(name); 273 mContext.bindServiceAsUser(i, serviceState.connection, Context.BIND_AUTO_CREATE, 274 new UserHandle(userId)); 275 serviceState.bound = true; 276 } else if (serviceState.service != null && isStateEmpty) { 277 // This means that the service is already connected but its state indicates that we have 278 // nothing to do with it. Then, disconnect the service. 279 if (DEBUG) { 280 Slog.d(TAG, "unbindService(name=" + name.getClassName() + ")"); 281 } 282 mContext.unbindService(serviceState.connection); 283 userState.serviceStateMap.remove(name); 284 } 285 } 286 287 private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken, 288 final int userId) { 289 final SessionState sessionState = 290 getUserStateLocked(userId).sessionStateMap.get(sessionToken); 291 if (DEBUG) { 292 Slog.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName() 293 + ")"); 294 } 295 296 final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString()); 297 298 // Set up a callback to send the session token. 299 ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() { 300 @Override 301 public void onSessionCreated(ITvInputSession session) { 302 if (DEBUG) { 303 Slog.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")"); 304 } 305 synchronized (mLock) { 306 sessionState.session = session; 307 if (session == null) { 308 removeSessionStateLocked(sessionToken, userId); 309 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null, 310 null, sessionState.seq, userId); 311 } else { 312 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, 313 sessionToken, channels[0], sessionState.seq, userId); 314 } 315 channels[0].dispose(); 316 } 317 } 318 }; 319 320 // Create a session. When failed, send a null token immediately. 321 try { 322 service.createSession(channels[1], callback); 323 } catch (RemoteException e) { 324 Slog.e(TAG, "error in createSession", e); 325 removeSessionStateLocked(sessionToken, userId); 326 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null, null, 327 sessionState.seq, userId); 328 } 329 channels[1].dispose(); 330 } 331 332 private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name, 333 IBinder sessionToken, InputChannel channel, int seq, int userId) { 334 try { 335 client.onSessionCreated(name, sessionToken, channel, seq); 336 } catch (RemoteException exception) { 337 Slog.e(TAG, "error in onSessionCreated", exception); 338 } 339 340 if (sessionToken == null) { 341 // This means that the session creation failed. We might want to disconnect the service. 342 updateServiceConnectionLocked(name, userId); 343 } 344 } 345 346 private void removeSessionStateLocked(IBinder sessionToken, int userId) { 347 // Remove the session state from the global session state map of the current user. 348 UserState userState = getUserStateLocked(userId); 349 SessionState sessionState = userState.sessionStateMap.remove(sessionToken); 350 351 // Close the open log entry, if any. 352 if (sessionState.logUri != null) { 353 SomeArgs args = SomeArgs.obtain(); 354 args.arg1 = sessionState.logUri; 355 args.arg2 = System.currentTimeMillis(); 356 mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget(); 357 } 358 359 // Also remove the session token from the session token list of the current service. 360 ServiceState serviceState = userState.serviceStateMap.get(sessionState.name); 361 if (serviceState != null) { 362 serviceState.sessionTokens.remove(sessionToken); 363 } 364 updateServiceConnectionLocked(sessionState.name, userId); 365 } 366 367 private final class BinderService extends ITvInputManager.Stub { 368 @Override 369 public List<TvInputInfo> getTvInputList(int userId) { 370 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 371 Binder.getCallingUid(), userId, "getTvInputList"); 372 final long identity = Binder.clearCallingIdentity(); 373 try { 374 synchronized (mLock) { 375 UserState userState = getUserStateLocked(resolvedUserId); 376 return new ArrayList<TvInputInfo>(userState.inputList); 377 } 378 } finally { 379 Binder.restoreCallingIdentity(identity); 380 } 381 } 382 383 @Override 384 public boolean getAvailability(final ITvInputClient client, final ComponentName name, 385 int userId) { 386 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 387 Binder.getCallingUid(), userId, "getAvailability"); 388 final long identity = Binder.clearCallingIdentity(); 389 try { 390 synchronized (mLock) { 391 UserState userState = getUserStateLocked(resolvedUserId); 392 ServiceState serviceState = userState.serviceStateMap.get(name); 393 if (serviceState != null) { 394 // We already know the status of this input service. Return the cached 395 // status. 396 return serviceState.available; 397 } 398 } 399 } finally { 400 Binder.restoreCallingIdentity(identity); 401 } 402 return false; 403 } 404 405 @Override 406 public void registerCallback(final ITvInputClient client, final ComponentName name, 407 int userId) { 408 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 409 Binder.getCallingUid(), userId, "registerCallback"); 410 final long identity = Binder.clearCallingIdentity(); 411 try { 412 synchronized (mLock) { 413 // Create a new service callback and add it to the callback map of the current 414 // service. 415 UserState userState = getUserStateLocked(resolvedUserId); 416 ServiceState serviceState = userState.serviceStateMap.get(name); 417 if (serviceState == null) { 418 serviceState = new ServiceState(resolvedUserId); 419 userState.serviceStateMap.put(name, serviceState); 420 } 421 IBinder iBinder = client.asBinder(); 422 if (!serviceState.clients.contains(iBinder)) { 423 serviceState.clients.add(iBinder); 424 } 425 if (serviceState.service != null) { 426 if (serviceState.callback != null) { 427 // We already handled. 428 return; 429 } 430 serviceState.callback = new ServiceCallback(resolvedUserId); 431 try { 432 serviceState.service.registerCallback(serviceState.callback); 433 } catch (RemoteException e) { 434 Slog.e(TAG, "error in registerCallback", e); 435 } 436 } else { 437 updateServiceConnectionLocked(name, resolvedUserId); 438 } 439 } 440 } finally { 441 Binder.restoreCallingIdentity(identity); 442 } 443 } 444 445 @Override 446 public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) { 447 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 448 Binder.getCallingUid(), userId, "unregisterCallback"); 449 final long identity = Binder.clearCallingIdentity(); 450 try { 451 synchronized (mLock) { 452 UserState userState = getUserStateLocked(resolvedUserId); 453 ServiceState serviceState = userState.serviceStateMap.get(name); 454 if (serviceState == null) { 455 return; 456 } 457 458 // Remove this client from the client list and unregister the callback. 459 serviceState.clients.remove(client.asBinder()); 460 if (!serviceState.clients.isEmpty()) { 461 // We have other clients who want to keep the callback. Do this later. 462 return; 463 } 464 if (serviceState.service == null || serviceState.callback == null) { 465 return; 466 } 467 try { 468 serviceState.service.unregisterCallback(serviceState.callback); 469 } catch (RemoteException e) { 470 Slog.e(TAG, "error in unregisterCallback", e); 471 } finally { 472 serviceState.callback = null; 473 updateServiceConnectionLocked(name, resolvedUserId); 474 } 475 } 476 } finally { 477 Binder.restoreCallingIdentity(identity); 478 } 479 } 480 481 @Override 482 public void createSession(final ITvInputClient client, final ComponentName name, 483 int seq, int userId) { 484 final int callingUid = Binder.getCallingUid(); 485 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 486 userId, "createSession"); 487 final long identity = Binder.clearCallingIdentity(); 488 try { 489 synchronized (mLock) { 490 // Create a new session token and a session state. 491 IBinder sessionToken = new Binder(); 492 SessionState sessionState = new SessionState(name, client, seq, callingUid); 493 sessionState.session = null; 494 495 // Add them to the global session state map of the current user. 496 UserState userState = getUserStateLocked(resolvedUserId); 497 userState.sessionStateMap.put(sessionToken, sessionState); 498 499 // Also, add them to the session state map of the current service. 500 ServiceState serviceState = userState.serviceStateMap.get(name); 501 if (serviceState == null) { 502 serviceState = new ServiceState(resolvedUserId); 503 userState.serviceStateMap.put(name, serviceState); 504 } 505 serviceState.sessionTokens.add(sessionToken); 506 507 if (serviceState.service != null) { 508 createSessionInternalLocked(serviceState.service, sessionToken, 509 resolvedUserId); 510 } else { 511 updateServiceConnectionLocked(name, resolvedUserId); 512 } 513 } 514 } finally { 515 Binder.restoreCallingIdentity(identity); 516 } 517 } 518 519 @Override 520 public void releaseSession(IBinder sessionToken, int userId) { 521 final int callingUid = Binder.getCallingUid(); 522 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 523 userId, "releaseSession"); 524 final long identity = Binder.clearCallingIdentity(); 525 try { 526 synchronized (mLock) { 527 // Release the session. 528 try { 529 getSessionLocked(sessionToken, callingUid, resolvedUserId).release(); 530 } catch (RemoteException e) { 531 Slog.e(TAG, "error in release", e); 532 } 533 534 removeSessionStateLocked(sessionToken, resolvedUserId); 535 } 536 } finally { 537 Binder.restoreCallingIdentity(identity); 538 } 539 } 540 541 @Override 542 public void setSurface(IBinder sessionToken, Surface surface, int userId) { 543 final int callingUid = Binder.getCallingUid(); 544 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 545 userId, "setSurface"); 546 final long identity = Binder.clearCallingIdentity(); 547 try { 548 synchronized (mLock) { 549 try { 550 getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface( 551 surface); 552 } catch (RemoteException e) { 553 Slog.e(TAG, "error in setSurface", e); 554 } 555 } 556 } finally { 557 if (surface != null) { 558 // surface is not used in TvInputManagerService. 559 surface.release(); 560 } 561 Binder.restoreCallingIdentity(identity); 562 } 563 } 564 565 @Override 566 public void setVolume(IBinder sessionToken, float volume, int userId) { 567 final int callingUid = Binder.getCallingUid(); 568 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 569 userId, "setVolume"); 570 final long identity = Binder.clearCallingIdentity(); 571 try { 572 synchronized (mLock) { 573 try { 574 getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume( 575 volume); 576 } catch (RemoteException e) { 577 Slog.e(TAG, "error in setVolume", e); 578 } 579 } 580 } finally { 581 Binder.restoreCallingIdentity(identity); 582 } 583 } 584 585 @Override 586 public void tune(IBinder sessionToken, final Uri channelUri, int userId) { 587 final int callingUid = Binder.getCallingUid(); 588 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 589 userId, "tune"); 590 final long identity = Binder.clearCallingIdentity(); 591 try { 592 synchronized (mLock) { 593 try { 594 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri); 595 596 long currentTime = System.currentTimeMillis(); 597 long channelId = ContentUris.parseId(channelUri); 598 599 // Close the open log entry first, if any. 600 UserState userState = getUserStateLocked(resolvedUserId); 601 SessionState sessionState = userState.sessionStateMap.get(sessionToken); 602 if (sessionState.logUri != null) { 603 SomeArgs args = SomeArgs.obtain(); 604 args.arg1 = sessionState.logUri; 605 args.arg2 = currentTime; 606 mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args) 607 .sendToTarget(); 608 } 609 610 // Create a log entry and fill it later. 611 ContentValues values = new ContentValues(); 612 values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS, 613 currentTime); 614 values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, 0); 615 values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId); 616 617 sessionState.logUri = mContentResolver.insert( 618 TvContract.WatchedPrograms.CONTENT_URI, values); 619 SomeArgs args = SomeArgs.obtain(); 620 args.arg1 = sessionState.logUri; 621 args.arg2 = ContentUris.parseId(channelUri); 622 args.arg3 = currentTime; 623 mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget(); 624 } catch (RemoteException e) { 625 Slog.e(TAG, "error in tune", e); 626 return; 627 } 628 } 629 } finally { 630 Binder.restoreCallingIdentity(identity); 631 } 632 } 633 634 @Override 635 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame, 636 int userId) { 637 final int callingUid = Binder.getCallingUid(); 638 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 639 userId, "createOverlayView"); 640 final long identity = Binder.clearCallingIdentity(); 641 try { 642 synchronized (mLock) { 643 try { 644 getSessionLocked(sessionToken, callingUid, resolvedUserId) 645 .createOverlayView(windowToken, frame); 646 } catch (RemoteException e) { 647 Slog.e(TAG, "error in createOverlayView", e); 648 } 649 } 650 } finally { 651 Binder.restoreCallingIdentity(identity); 652 } 653 } 654 655 @Override 656 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) { 657 final int callingUid = Binder.getCallingUid(); 658 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 659 userId, "relayoutOverlayView"); 660 final long identity = Binder.clearCallingIdentity(); 661 try { 662 synchronized (mLock) { 663 try { 664 getSessionLocked(sessionToken, callingUid, resolvedUserId) 665 .relayoutOverlayView(frame); 666 } catch (RemoteException e) { 667 Slog.e(TAG, "error in relayoutOverlayView", e); 668 } 669 } 670 } finally { 671 Binder.restoreCallingIdentity(identity); 672 } 673 } 674 675 @Override 676 public void removeOverlayView(IBinder sessionToken, int userId) { 677 final int callingUid = Binder.getCallingUid(); 678 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 679 userId, "removeOverlayView"); 680 final long identity = Binder.clearCallingIdentity(); 681 try { 682 synchronized (mLock) { 683 try { 684 getSessionLocked(sessionToken, callingUid, resolvedUserId) 685 .removeOverlayView(); 686 } catch (RemoteException e) { 687 Slog.e(TAG, "error in removeOverlayView", e); 688 } 689 } 690 } finally { 691 Binder.restoreCallingIdentity(identity); 692 } 693 } 694 } 695 696 private static final class UserState { 697 // A list of all known TV inputs on the system. 698 private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>(); 699 700 // A mapping from the name of a TV input service to its state. 701 private final Map<ComponentName, ServiceState> serviceStateMap = 702 new HashMap<ComponentName, ServiceState>(); 703 704 // A mapping from the token of a TV input session to its state. 705 private final Map<IBinder, SessionState> sessionStateMap = 706 new HashMap<IBinder, SessionState>(); 707 } 708 709 private final class ServiceState { 710 private final List<IBinder> clients = new ArrayList<IBinder>(); 711 private final List<IBinder> sessionTokens = new ArrayList<IBinder>(); 712 private final ServiceConnection connection; 713 714 private ITvInputService service; 715 private ServiceCallback callback; 716 private boolean bound; 717 private boolean available; 718 719 private ServiceState(int userId) { 720 this.connection = new InputServiceConnection(userId); 721 } 722 } 723 724 private static final class SessionState { 725 private final ComponentName name; 726 private final ITvInputClient client; 727 private final int seq; 728 private final int callingUid; 729 730 private ITvInputSession session; 731 private Uri logUri; 732 733 private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) { 734 this.name = name; 735 this.client = client; 736 this.seq = seq; 737 this.callingUid = callingUid; 738 } 739 } 740 741 private final class InputServiceConnection implements ServiceConnection { 742 private final int mUserId; 743 744 private InputServiceConnection(int userId) { 745 mUserId = userId; 746 } 747 748 @Override 749 public void onServiceConnected(ComponentName name, IBinder service) { 750 if (DEBUG) { 751 Slog.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")"); 752 } 753 synchronized (mLock) { 754 ServiceState serviceState = getServiceStateLocked(name, mUserId); 755 serviceState.service = ITvInputService.Stub.asInterface(service); 756 757 // Register a callback, if we need to. 758 if (!serviceState.clients.isEmpty() && serviceState.callback == null) { 759 serviceState.callback = new ServiceCallback(mUserId); 760 try { 761 serviceState.service.registerCallback(serviceState.callback); 762 } catch (RemoteException e) { 763 Slog.e(TAG, "error in registerCallback", e); 764 } 765 } 766 767 // And create sessions, if any. 768 for (IBinder sessionToken : serviceState.sessionTokens) { 769 createSessionInternalLocked(serviceState.service, sessionToken, mUserId); 770 } 771 } 772 } 773 774 @Override 775 public void onServiceDisconnected(ComponentName name) { 776 if (DEBUG) { 777 Slog.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")"); 778 } 779 } 780 } 781 782 private final class ServiceCallback extends ITvInputServiceCallback.Stub { 783 private final int mUserId; 784 785 ServiceCallback(int userId) { 786 mUserId = userId; 787 } 788 789 @Override 790 public void onAvailabilityChanged(ComponentName name, boolean isAvailable) 791 throws RemoteException { 792 if (DEBUG) { 793 Slog.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable=" 794 + isAvailable + ")"); 795 } 796 synchronized (mLock) { 797 ServiceState serviceState = getServiceStateLocked(name, mUserId); 798 serviceState.available = isAvailable; 799 for (IBinder iBinder : serviceState.clients) { 800 ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder); 801 client.onAvailabilityChanged(name, isAvailable); 802 } 803 } 804 } 805 } 806 807 private final class LogHandler extends Handler { 808 private static final int MSG_OPEN_ENTRY = 1; 809 private static final int MSG_UPDATE_ENTRY = 2; 810 private static final int MSG_CLOSE_ENTRY = 3; 811 812 public LogHandler(Looper looper) { 813 super(looper); 814 } 815 816 @Override 817 public void handleMessage(Message msg) { 818 switch (msg.what) { 819 case MSG_OPEN_ENTRY: { 820 SomeArgs args = (SomeArgs) msg.obj; 821 Uri uri = (Uri) args.arg1; 822 long channelId = (long) args.arg2; 823 long time = (long) args.arg3; 824 onOpenEntry(uri, channelId, time); 825 args.recycle(); 826 return; 827 } 828 case MSG_UPDATE_ENTRY: { 829 SomeArgs args = (SomeArgs) msg.obj; 830 Uri uri = (Uri) args.arg1; 831 long channelId = (long) args.arg2; 832 long time = (long) args.arg3; 833 onUpdateEntry(uri, channelId, time); 834 args.recycle(); 835 return; 836 } 837 case MSG_CLOSE_ENTRY: { 838 SomeArgs args = (SomeArgs) msg.obj; 839 Uri uri = (Uri) args.arg1; 840 long time = (long) args.arg2; 841 onCloseEntry(uri, time); 842 args.recycle(); 843 return; 844 } 845 default: { 846 Slog.w(TAG, "Unhandled message code: " + msg.what); 847 return; 848 } 849 } 850 } 851 852 private void onOpenEntry(Uri uri, long channelId, long watchStarttime) { 853 String[] projection = { 854 TvContract.Programs.TITLE, 855 TvContract.Programs.START_TIME_UTC_MILLIS, 856 TvContract.Programs.END_TIME_UTC_MILLIS, 857 TvContract.Programs.DESCRIPTION 858 }; 859 String selection = TvContract.Programs.CHANNEL_ID + "=? AND " 860 + TvContract.Programs.START_TIME_UTC_MILLIS + "<=? AND " 861 + TvContract.Programs.END_TIME_UTC_MILLIS + ">?"; 862 String[] selectionArgs = { 863 String.valueOf(channelId), 864 String.valueOf(watchStarttime), 865 String.valueOf(watchStarttime) 866 }; 867 String sortOrder = TvContract.Programs.START_TIME_UTC_MILLIS + " ASC"; 868 Cursor cursor = null; 869 try { 870 cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection, 871 selection, selectionArgs, sortOrder); 872 if (cursor != null && cursor.moveToNext()) { 873 ContentValues values = new ContentValues(); 874 values.put(TvContract.WatchedPrograms.TITLE, cursor.getString(0)); 875 values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, cursor.getLong(1)); 876 long endTime = cursor.getLong(2); 877 values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime); 878 values.put(TvContract.WatchedPrograms.DESCRIPTION, cursor.getString(3)); 879 mContentResolver.update(uri, values, null, null); 880 881 // Schedule an update when the current program ends. 882 SomeArgs args = SomeArgs.obtain(); 883 args.arg1 = uri; 884 args.arg2 = channelId; 885 args.arg3 = endTime; 886 Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args); 887 sendMessageDelayed(msg, endTime - System.currentTimeMillis()); 888 } 889 } finally { 890 if (cursor != null) { 891 cursor.close(); 892 } 893 } 894 } 895 896 private void onUpdateEntry(Uri uri, long channelId, long time) { 897 String[] projection = { 898 TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS, 899 TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, 900 TvContract.WatchedPrograms.TITLE, 901 TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, 902 TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, 903 TvContract.WatchedPrograms.DESCRIPTION 904 }; 905 Cursor cursor = null; 906 try { 907 cursor = mContentResolver.query(uri, projection, null, null, null); 908 if (cursor != null && cursor.moveToNext()) { 909 long watchStartTime = cursor.getLong(0); 910 long watchEndTime = cursor.getLong(1); 911 String title = cursor.getString(2); 912 long startTime = cursor.getLong(3); 913 long endTime = cursor.getLong(4); 914 String description = cursor.getString(5); 915 916 // Do nothing if the current log entry is already closed. 917 if (watchEndTime > 0) { 918 return; 919 } 920 921 // The current program has just ended. Create a (complete) log entry off the 922 // current entry. 923 ContentValues values = new ContentValues(); 924 values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS, 925 watchStartTime); 926 values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, time); 927 values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId); 928 values.put(TvContract.WatchedPrograms.TITLE, title); 929 values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, startTime); 930 values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime); 931 values.put(TvContract.WatchedPrograms.DESCRIPTION, description); 932 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values); 933 } 934 } finally { 935 if (cursor != null) { 936 cursor.close(); 937 } 938 } 939 // Re-open the current log entry with the next program information. 940 onOpenEntry(uri, channelId, time); 941 } 942 943 private void onCloseEntry(Uri uri, long watchEndTime) { 944 ContentValues values = new ContentValues(); 945 values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, watchEndTime); 946 mContentResolver.update(uri, values, null, null); 947 } 948 } 949} 950