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