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