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