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