TvInputManagerService.java revision 31dc634be3610b062fbcc4afa02607ce8f4125f5
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.Log; 53import android.util.Slog; 54import android.util.SparseArray; 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 // Set up a callback to send the session token. 296 ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() { 297 @Override 298 public void onSessionCreated(ITvInputSession session) { 299 if (DEBUG) { 300 Slog.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")"); 301 } 302 synchronized (mLock) { 303 sessionState.session = session; 304 if (session == null) { 305 removeSessionStateLocked(sessionToken, userId); 306 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null, 307 sessionState.seq, userId); 308 } else { 309 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, 310 sessionToken, sessionState.seq, userId); 311 } 312 } 313 } 314 }; 315 316 // Create a session. When failed, send a null token immediately. 317 try { 318 service.createSession(callback); 319 } catch (RemoteException e) { 320 Slog.e(TAG, "error in createSession", e); 321 removeSessionStateLocked(sessionToken, userId); 322 sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null, 323 sessionState.seq, userId); 324 } 325 } 326 327 private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name, 328 IBinder sessionToken, int seq, int userId) { 329 try { 330 client.onSessionCreated(name, sessionToken, seq); 331 } catch (RemoteException exception) { 332 Slog.e(TAG, "error in onSessionCreated", exception); 333 } 334 335 if (sessionToken == null) { 336 // This means that the session creation failed. We might want to disconnect the service. 337 updateServiceConnectionLocked(name, userId); 338 } 339 } 340 341 private void removeSessionStateLocked(IBinder sessionToken, int userId) { 342 // Remove the session state from the global session state map of the current user. 343 UserState userState = getUserStateLocked(userId); 344 SessionState sessionState = userState.sessionStateMap.remove(sessionToken); 345 346 // Close the open log entry, if any. 347 if (sessionState.logUri != null) { 348 SomeArgs args = SomeArgs.obtain(); 349 args.arg1 = sessionState.logUri; 350 args.arg2 = System.currentTimeMillis(); 351 mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget(); 352 } 353 354 // Also remove the session token from the session token list of the current service. 355 ServiceState serviceState = userState.serviceStateMap.get(sessionState.name); 356 if (serviceState != null) { 357 serviceState.sessionTokens.remove(sessionToken); 358 } 359 updateServiceConnectionLocked(sessionState.name, userId); 360 } 361 362 private final class BinderService extends ITvInputManager.Stub { 363 @Override 364 public List<TvInputInfo> getTvInputList(int userId) { 365 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 366 Binder.getCallingUid(), userId, "getTvInputList"); 367 final long identity = Binder.clearCallingIdentity(); 368 try { 369 synchronized (mLock) { 370 UserState userState = getUserStateLocked(resolvedUserId); 371 return new ArrayList<TvInputInfo>(userState.inputList); 372 } 373 } finally { 374 Binder.restoreCallingIdentity(identity); 375 } 376 } 377 378 @Override 379 public boolean getAvailability(final ITvInputClient client, final ComponentName name, 380 int userId) { 381 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 382 Binder.getCallingUid(), userId, "getAvailability"); 383 final long identity = Binder.clearCallingIdentity(); 384 try { 385 synchronized (mLock) { 386 UserState userState = getUserStateLocked(resolvedUserId); 387 ServiceState serviceState = userState.serviceStateMap.get(name); 388 if (serviceState != null) { 389 // We already know the status of this input service. Return the cached 390 // status. 391 return serviceState.available; 392 } 393 } 394 } finally { 395 Binder.restoreCallingIdentity(identity); 396 } 397 return false; 398 } 399 400 @Override 401 public void registerCallback(final ITvInputClient client, final ComponentName name, 402 int userId) { 403 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 404 Binder.getCallingUid(), userId, "registerCallback"); 405 final long identity = Binder.clearCallingIdentity(); 406 try { 407 synchronized (mLock) { 408 // Create a new service callback and add it to the callback map of the current 409 // service. 410 UserState userState = getUserStateLocked(resolvedUserId); 411 ServiceState serviceState = userState.serviceStateMap.get(name); 412 if (serviceState == null) { 413 serviceState = new ServiceState(resolvedUserId); 414 userState.serviceStateMap.put(name, serviceState); 415 } 416 IBinder iBinder = client.asBinder(); 417 if (!serviceState.clients.contains(iBinder)) { 418 serviceState.clients.add(iBinder); 419 } 420 if (serviceState.service != null) { 421 if (serviceState.callback != null) { 422 // We already handled. 423 return; 424 } 425 serviceState.callback = new ServiceCallback(resolvedUserId); 426 try { 427 serviceState.service.registerCallback(serviceState.callback); 428 } catch (RemoteException e) { 429 Slog.e(TAG, "error in registerCallback", e); 430 } 431 } else { 432 updateServiceConnectionLocked(name, resolvedUserId); 433 } 434 } 435 } finally { 436 Binder.restoreCallingIdentity(identity); 437 } 438 } 439 440 @Override 441 public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) { 442 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), 443 Binder.getCallingUid(), userId, "unregisterCallback"); 444 final long identity = Binder.clearCallingIdentity(); 445 try { 446 synchronized (mLock) { 447 UserState userState = getUserStateLocked(resolvedUserId); 448 ServiceState serviceState = userState.serviceStateMap.get(name); 449 if (serviceState == null) { 450 return; 451 } 452 453 // Remove this client from the client list and unregister the callback. 454 serviceState.clients.remove(client.asBinder()); 455 if (!serviceState.clients.isEmpty()) { 456 // We have other clients who want to keep the callback. Do this later. 457 return; 458 } 459 if (serviceState.service == null || serviceState.callback == null) { 460 return; 461 } 462 try { 463 serviceState.service.unregisterCallback(serviceState.callback); 464 } catch (RemoteException e) { 465 Slog.e(TAG, "error in unregisterCallback", e); 466 } finally { 467 serviceState.callback = null; 468 updateServiceConnectionLocked(name, resolvedUserId); 469 } 470 } 471 } finally { 472 Binder.restoreCallingIdentity(identity); 473 } 474 } 475 476 @Override 477 public void createSession(final ITvInputClient client, final ComponentName name, 478 int seq, int userId) { 479 final int callingUid = Binder.getCallingUid(); 480 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 481 userId, "createSession"); 482 final long identity = Binder.clearCallingIdentity(); 483 try { 484 synchronized (mLock) { 485 // Create a new session token and a session state. 486 IBinder sessionToken = new Binder(); 487 SessionState sessionState = new SessionState(name, client, seq, callingUid); 488 sessionState.session = null; 489 490 // Add them to the global session state map of the current user. 491 UserState userState = getUserStateLocked(resolvedUserId); 492 userState.sessionStateMap.put(sessionToken, sessionState); 493 494 // Also, add them to the session state map of the current service. 495 ServiceState serviceState = userState.serviceStateMap.get(name); 496 if (serviceState == null) { 497 serviceState = new ServiceState(resolvedUserId); 498 userState.serviceStateMap.put(name, serviceState); 499 } 500 serviceState.sessionTokens.add(sessionToken); 501 502 if (serviceState.service != null) { 503 createSessionInternalLocked(serviceState.service, sessionToken, 504 resolvedUserId); 505 } else { 506 updateServiceConnectionLocked(name, resolvedUserId); 507 } 508 } 509 } finally { 510 Binder.restoreCallingIdentity(identity); 511 } 512 } 513 514 @Override 515 public void releaseSession(IBinder sessionToken, int userId) { 516 final int callingUid = Binder.getCallingUid(); 517 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 518 userId, "releaseSession"); 519 final long identity = Binder.clearCallingIdentity(); 520 try { 521 synchronized (mLock) { 522 // Release the session. 523 try { 524 getSessionLocked(sessionToken, callingUid, resolvedUserId).release(); 525 } catch (RemoteException e) { 526 Slog.e(TAG, "error in release", e); 527 } 528 529 removeSessionStateLocked(sessionToken, resolvedUserId); 530 } 531 } finally { 532 Binder.restoreCallingIdentity(identity); 533 } 534 } 535 536 @Override 537 public void setSurface(IBinder sessionToken, Surface surface, int userId) { 538 final int callingUid = Binder.getCallingUid(); 539 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 540 userId, "setSurface"); 541 final long identity = Binder.clearCallingIdentity(); 542 try { 543 synchronized (mLock) { 544 try { 545 getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface( 546 surface); 547 } catch (RemoteException e) { 548 Slog.e(TAG, "error in setSurface", e); 549 } 550 } 551 } finally { 552 Binder.restoreCallingIdentity(identity); 553 } 554 } 555 556 @Override 557 public void setVolume(IBinder sessionToken, float volume, int userId) { 558 final int callingUid = Binder.getCallingUid(); 559 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 560 userId, "setVolume"); 561 final long identity = Binder.clearCallingIdentity(); 562 try { 563 synchronized (mLock) { 564 try { 565 getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume( 566 volume); 567 } catch (RemoteException e) { 568 Slog.e(TAG, "error in setVolume", e); 569 } 570 } 571 } finally { 572 Binder.restoreCallingIdentity(identity); 573 } 574 } 575 576 @Override 577 public void tune(IBinder sessionToken, final Uri channelUri, int userId) { 578 final int callingUid = Binder.getCallingUid(); 579 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 580 userId, "tune"); 581 final long identity = Binder.clearCallingIdentity(); 582 try { 583 synchronized (mLock) { 584 try { 585 getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri); 586 587 long currentTime = System.currentTimeMillis(); 588 long channelId = ContentUris.parseId(channelUri); 589 590 // Close the open log entry first, if any. 591 UserState userState = getUserStateLocked(resolvedUserId); 592 SessionState sessionState = userState.sessionStateMap.get(sessionToken); 593 if (sessionState.logUri != null) { 594 SomeArgs args = SomeArgs.obtain(); 595 args.arg1 = sessionState.logUri; 596 args.arg2 = currentTime; 597 mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args) 598 .sendToTarget(); 599 } 600 601 // Create a log entry and fill it later. 602 ContentValues values = new ContentValues(); 603 values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS, 604 currentTime); 605 values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, 0); 606 values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId); 607 608 sessionState.logUri = mContentResolver.insert( 609 TvContract.WatchedPrograms.CONTENT_URI, values); 610 SomeArgs args = SomeArgs.obtain(); 611 args.arg1 = sessionState.logUri; 612 args.arg2 = ContentUris.parseId(channelUri); 613 args.arg3 = currentTime; 614 mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget(); 615 } catch (RemoteException e) { 616 Slog.e(TAG, "error in tune", e); 617 return; 618 } 619 } 620 } finally { 621 Binder.restoreCallingIdentity(identity); 622 } 623 } 624 625 @Override 626 public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame, 627 int userId) { 628 final int callingUid = Binder.getCallingUid(); 629 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 630 userId, "createOverlayView"); 631 final long identity = Binder.clearCallingIdentity(); 632 try { 633 synchronized (mLock) { 634 try { 635 getSessionLocked(sessionToken, callingUid, resolvedUserId) 636 .createOverlayView(windowToken, frame); 637 } catch (RemoteException e) { 638 Slog.e(TAG, "error in createOverlayView", e); 639 } 640 } 641 } finally { 642 Binder.restoreCallingIdentity(identity); 643 } 644 } 645 646 @Override 647 public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) { 648 final int callingUid = Binder.getCallingUid(); 649 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 650 userId, "relayoutOverlayView"); 651 final long identity = Binder.clearCallingIdentity(); 652 try { 653 synchronized (mLock) { 654 try { 655 getSessionLocked(sessionToken, callingUid, resolvedUserId) 656 .relayoutOverlayView(frame); 657 } catch (RemoteException e) { 658 Slog.e(TAG, "error in relayoutOverlayView", e); 659 } 660 } 661 } finally { 662 Binder.restoreCallingIdentity(identity); 663 } 664 } 665 666 @Override 667 public void removeOverlayView(IBinder sessionToken, int userId) { 668 final int callingUid = Binder.getCallingUid(); 669 final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, 670 userId, "removeOverlayView"); 671 final long identity = Binder.clearCallingIdentity(); 672 try { 673 synchronized (mLock) { 674 try { 675 getSessionLocked(sessionToken, callingUid, resolvedUserId) 676 .removeOverlayView(); 677 } catch (RemoteException e) { 678 Slog.e(TAG, "error in removeOverlayView", e); 679 } 680 } 681 } finally { 682 Binder.restoreCallingIdentity(identity); 683 } 684 } 685 } 686 687 private static final class UserState { 688 // A list of all known TV inputs on the system. 689 private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>(); 690 691 // A mapping from the name of a TV input service to its state. 692 private final Map<ComponentName, ServiceState> serviceStateMap = 693 new HashMap<ComponentName, ServiceState>(); 694 695 // A mapping from the token of a TV input session to its state. 696 private final Map<IBinder, SessionState> sessionStateMap = 697 new HashMap<IBinder, SessionState>(); 698 } 699 700 private final class ServiceState { 701 private final List<IBinder> clients = new ArrayList<IBinder>(); 702 private final List<IBinder> sessionTokens = new ArrayList<IBinder>(); 703 private final ServiceConnection connection; 704 705 private ITvInputService service; 706 private ServiceCallback callback; 707 private boolean bound; 708 private boolean available; 709 710 private ServiceState(int userId) { 711 this.connection = new InputServiceConnection(userId); 712 } 713 } 714 715 private static final class SessionState { 716 private final ComponentName name; 717 private final ITvInputClient client; 718 private final int seq; 719 private final int callingUid; 720 721 private ITvInputSession session; 722 private Uri logUri; 723 724 private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) { 725 this.name = name; 726 this.client = client; 727 this.seq = seq; 728 this.callingUid = callingUid; 729 } 730 } 731 732 private final class InputServiceConnection implements ServiceConnection { 733 private final int mUserId; 734 735 private InputServiceConnection(int userId) { 736 mUserId = userId; 737 } 738 739 @Override 740 public void onServiceConnected(ComponentName name, IBinder service) { 741 if (DEBUG) { 742 Slog.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")"); 743 } 744 synchronized (mLock) { 745 ServiceState serviceState = getServiceStateLocked(name, mUserId); 746 serviceState.service = ITvInputService.Stub.asInterface(service); 747 748 // Register a callback, if we need to. 749 if (!serviceState.clients.isEmpty() && serviceState.callback == null) { 750 serviceState.callback = new ServiceCallback(mUserId); 751 try { 752 serviceState.service.registerCallback(serviceState.callback); 753 } catch (RemoteException e) { 754 Slog.e(TAG, "error in registerCallback", e); 755 } 756 } 757 758 // And create sessions, if any. 759 for (IBinder sessionToken : serviceState.sessionTokens) { 760 createSessionInternalLocked(serviceState.service, sessionToken, mUserId); 761 } 762 } 763 } 764 765 @Override 766 public void onServiceDisconnected(ComponentName name) { 767 if (DEBUG) { 768 Slog.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")"); 769 } 770 } 771 } 772 773 private final class ServiceCallback extends ITvInputServiceCallback.Stub { 774 private final int mUserId; 775 776 ServiceCallback(int userId) { 777 mUserId = userId; 778 } 779 780 @Override 781 public void onAvailabilityChanged(ComponentName name, boolean isAvailable) 782 throws RemoteException { 783 if (DEBUG) { 784 Slog.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable=" 785 + isAvailable + ")"); 786 } 787 synchronized (mLock) { 788 ServiceState serviceState = getServiceStateLocked(name, mUserId); 789 serviceState.available = isAvailable; 790 for (IBinder iBinder : serviceState.clients) { 791 ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder); 792 client.onAvailabilityChanged(name, isAvailable); 793 } 794 } 795 } 796 } 797 798 private final class LogHandler extends Handler { 799 private static final int MSG_OPEN_ENTRY = 1; 800 private static final int MSG_UPDATE_ENTRY = 2; 801 private static final int MSG_CLOSE_ENTRY = 3; 802 803 public LogHandler(Looper looper) { 804 super(looper); 805 } 806 807 @Override 808 public void handleMessage(Message msg) { 809 switch (msg.what) { 810 case MSG_OPEN_ENTRY: { 811 SomeArgs args = (SomeArgs) msg.obj; 812 Uri uri = (Uri) args.arg1; 813 long channelId = (long) args.arg2; 814 long time = (long) args.arg3; 815 onOpenEntry(uri, channelId, time); 816 args.recycle(); 817 return; 818 } 819 case MSG_UPDATE_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 onUpdateEntry(uri, channelId, time); 825 args.recycle(); 826 return; 827 } 828 case MSG_CLOSE_ENTRY: { 829 SomeArgs args = (SomeArgs) msg.obj; 830 Uri uri = (Uri) args.arg1; 831 long time = (long) args.arg2; 832 onCloseEntry(uri, time); 833 args.recycle(); 834 return; 835 } 836 default: { 837 Log.w(TAG, "Unhandled message code: " + msg.what); 838 return; 839 } 840 } 841 } 842 843 private void onOpenEntry(Uri uri, long channelId, long watchStarttime) { 844 String[] projection = { 845 TvContract.Programs.TITLE, 846 TvContract.Programs.START_TIME_UTC_MILLIS, 847 TvContract.Programs.END_TIME_UTC_MILLIS, 848 TvContract.Programs.DESCRIPTION 849 }; 850 String selection = TvContract.Programs.CHANNEL_ID + "=? AND " 851 + TvContract.Programs.START_TIME_UTC_MILLIS + "<=? AND " 852 + TvContract.Programs.END_TIME_UTC_MILLIS + ">?"; 853 String[] selectionArgs = { 854 String.valueOf(channelId), 855 String.valueOf(watchStarttime), 856 String.valueOf(watchStarttime) 857 }; 858 String sortOrder = TvContract.Programs.START_TIME_UTC_MILLIS + " ASC"; 859 Cursor cursor = null; 860 try { 861 cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection, 862 selection, selectionArgs, sortOrder); 863 if (cursor != null && cursor.moveToNext()) { 864 ContentValues values = new ContentValues(); 865 values.put(TvContract.WatchedPrograms.TITLE, cursor.getString(0)); 866 values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, cursor.getLong(1)); 867 long endTime = cursor.getLong(2); 868 values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime); 869 values.put(TvContract.WatchedPrograms.DESCRIPTION, cursor.getString(3)); 870 mContentResolver.update(uri, values, null, null); 871 872 // Schedule an update when the current program ends. 873 SomeArgs args = SomeArgs.obtain(); 874 args.arg1 = uri; 875 args.arg2 = channelId; 876 args.arg3 = endTime; 877 Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args); 878 sendMessageDelayed(msg, endTime - System.currentTimeMillis()); 879 } 880 } finally { 881 if (cursor != null) { 882 cursor.close(); 883 } 884 } 885 } 886 887 private void onUpdateEntry(Uri uri, long channelId, long time) { 888 String[] projection = { 889 TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS, 890 TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, 891 TvContract.WatchedPrograms.TITLE, 892 TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, 893 TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, 894 TvContract.WatchedPrograms.DESCRIPTION 895 }; 896 Cursor cursor = null; 897 try { 898 cursor = mContentResolver.query(uri, projection, null, null, null); 899 if (cursor != null && cursor.moveToNext()) { 900 long watchStartTime = cursor.getLong(0); 901 long watchEndTime = cursor.getLong(1); 902 String title = cursor.getString(2); 903 long startTime = cursor.getLong(3); 904 long endTime = cursor.getLong(4); 905 String description = cursor.getString(5); 906 907 // Do nothing if the current log entry is already closed. 908 if (watchEndTime > 0) { 909 return; 910 } 911 912 // The current program has just ended. Create a (complete) log entry off the 913 // current entry. 914 ContentValues values = new ContentValues(); 915 values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS, 916 watchStartTime); 917 values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, time); 918 values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId); 919 values.put(TvContract.WatchedPrograms.TITLE, title); 920 values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, startTime); 921 values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime); 922 values.put(TvContract.WatchedPrograms.DESCRIPTION, description); 923 mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values); 924 } 925 } finally { 926 if (cursor != null) { 927 cursor.close(); 928 } 929 } 930 // Re-open the current log entry with the next program information. 931 onOpenEntry(uri, channelId, time); 932 } 933 934 private void onCloseEntry(Uri uri, long watchEndTime) { 935 ContentValues values = new ContentValues(); 936 values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, watchEndTime); 937 mContentResolver.update(uri, values, null, null); 938 } 939 } 940} 941