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